New: VisionPsy-Nano, a 460M vision model that outperforms models twice its size.
QVAC Logo

Text-to-Speech

Speech synthesis for text-to-speech (TTS) — i.e., generate audio using custom voices from written input.

Overview

Text-to-Speech uses @qvac/tts-ggml (GGML) as the inference engine. Load any supported model using modelType: "tts". Then, provide text as input (with inputType: "text") to generate speech audio.

textToSpeech() returns an object containing buffer and, when streaming is enabled, a bufferStream for incremental audio output.

Functions

Use the following sequence of function calls:

  1. loadModel()
  2. textToSpeech()
  3. unloadModel()

For how to use each function, see SDK — API reference.

Audio output

The SDK returns raw, mono, signed 16-bit PCM samples as a plain number[]. With stream: false, the buffer promise resolves to the complete sample array and bufferStream is empty. With stream: true, buffer resolves to [] and bufferStream yields individual samples as they become available. The data has no WAV header or other container metadata; use the example utility to write it as a WAV file.

The response does not include a sample rate. Use the rate implied by the loaded engine and its configuration:

ConfigurationOutput sample rate
Chatterbox24,000 Hz, or modelConfig.outputSampleRate when set
Supertonic44,100 Hz, or modelConfig.outputSampleRate when set
Parler44,100 Hz, or modelConfig.outputSampleRate when native streaming is disabled; native streaming requires 44,100 Hz
LavaSR enhancer enabled48,000 Hz by default, or modelConfig.outputSampleRate when set

Models

Chatterbox

Chatterbox uses a T3 GGUF as the top-level modelSrc and an S3Gen companion GGUF via modelConfig.s3genModelSrc. Optional referenceAudioSrc supplies a WAV for voice cloning.

await loadModel({
  modelSrc: TTS_T3_TURBO_EN_CHATTERBOX_Q8_0,
  modelType: "tts",
  modelConfig: {
    ttsEngine: "chatterbox",
    language: "en",
    s3genModelSrc: TTS_S3GEN_EN_CHATTERBOX,
  },
});
Omitting ttsEngine defaults to Chatterbox.

Supertonic

Supertonic uses a single GGUF via top-level modelSrc. Set voice, ttsSpeed, and ttsNumInferenceSteps in modelConfig as needed. Multilingual output is selected by the GGUF (e.g. TTS_MULTILINGUAL_SUPERTONIC2_Q8_0) plus language.

await loadModel({
  modelSrc: TTS_EN_SUPERTONIC_Q8_0,
  modelType: "tts",
  modelConfig: {
    ttsEngine: "supertonic",
    language: "en",
    voice: "F1",
  },
});

Parler-TTS

Parler-TTS uses a single GGUF and conditions speech from either a free-text description or structured voice fields. Load-time fields provide defaults; textToSpeech() and textToSpeechStream() can override them per request. The registry includes Mini v1, Large v1, and Indic variants; the Indic model supports 21 languages and script-native digit normalization.

const modelId = await loadModel({
  modelSrc: TTS_MINI_V1_EN_PARLER_TTS_Q8_0,
  modelType: "tts",
  modelConfig: {
    ttsEngine: "parler",
    voice: "Laura",
    seed: 42,
    topK: 1,
  },
});

const result = textToSpeech({
  modelId,
  text: "Welcome to Parler text-to-speech.",
  inputType: "text",
  stream: false,
  emotion: "happy",
  pace: "moderate",
});

const pcm = await result.buffer;

For free-form conditioning, use description or its alias voiceDescription. Otherwise, compose a description from voice, emotion, pitch, pace, expressivity, noise, reverb, and quality.

description and voiceDescription cannot be combined with each other or with the structured voice fields. These per-request fields are Parler-only; using them with Chatterbox or Supertonic returns a request-validation error.

Supported emotions are command, anger, narration, conversation, disgust, fear, happy, neutral, proper noun, news, sad, and surprise.

Parler supports all three SDK streaming surfaces:

  • textToSpeech({ stream: true }) for incremental PCM samples.
  • textToSpeech({ stream: true, sentenceStream: true }) for PCM plus sentence/chunk metadata.
  • textToSpeechStream() when text itself arrives incrementally.

Set streamChunkTokens above zero to enable native chunk streaming; streamFirstChunkTokens only tunes the first chunk and does not enable streaming by itself. Native streaming emits at 44.1 kHz, so omit outputSampleRate or set it to 44100 when streamChunkTokens > 0. Integer generation controls use signed 32-bit values. Parler does not support LavaSR post-processing.

The generated Python client uses the same contract:

from tetherto.qvac_sdk import TextToSpeechRequest, load_model, text_to_speech
from tetherto.qvac_sdk.models import TTS_MINI_V1_EN_PARLER_TTS_Q8_0

model_id = await load_model(
    transport,
    model_src=TTS_MINI_V1_EN_PARLER_TTS_Q8_0,
    model_config={
        "ttsEngine": "parler",
        "voice": "Laura",
        "seed": 42,
        "topK": 1,
    },
)

request = TextToSpeechRequest.model_validate({
    "type": "textToSpeech",
    "modelId": model_id,
    "text": "Welcome to Parler text-to-speech.",
    "inputType": "text",
    "stream": False,
    "emotion": "happy",
})

samples = []
async for response in text_to_speech(transport, request):
    samples.extend(response.buffer)

For model constants, see SDK — Models.

LavaSR

With either Chatterbox or Supertonic, you can choose to perform post-processing using LavaSR models. They are applied to the synthesized audio before it is returned, and each stage is enabled purely by supplying its model source — i.e., there is no separate on/off flag.

  • lavasrDenoiserModelSrc — a denoiser GGUF that cleans the speech (noise reduction). It runs first and is rate-preserving. Constants: TTS_DENOISER_LAVASR_FP16, TTS_DENOISER_LAVASR_FP32.
  • lavasrEnhancerModelSrc — an enhancer GGUF that neurally bandwidth-extends the output to 48 kHz. It runs after the denoiser. Constants: TTS_ENHANCER_LAVASR_FP16, TTS_ENHANCER_LAVASR_FP32.
await loadModel({
  modelSrc: TTS_MULTILINGUAL_SUPERTONIC3_Q8_0,
  modelType: "tts",
  modelConfig: {
    ttsEngine: "supertonic",
    language: "en",
    voice: "F1",
    // Denoiser runs first (rate-preserving)…
    lavasrDenoiserModelSrc: TTS_DENOISER_LAVASR_FP16.src,
    // …then the enhancer bandwidth-extends to 48 kHz.
    lavasrEnhancerModelSrc: TTS_ENHANCER_LAVASR_FP16.src,
  },
});

Examples

Chatterbox

The following script shows an example of Chatterbox TTS with voice cloning from a reference audio file. Use it with utils.js / utils.ts:

tts-chatterbox.js
import { loadModel, textToSpeech, unloadModel, TTS_T3_TURBO_EN_CHATTERBOX_Q8_0, TTS_S3GEN_EN_CHATTERBOX } from '@qvac/sdk';
import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './utils';
// Chatterbox TTS (GGML): voice cloning with optional reference audio.
// Uses registry model constants — downloads automatically from QVAC Registry.
// Usage: node chatterbox.ts [referenceAudioSrc]
const [referenceAudioSrc] = process.argv.slice(2);
const CHATTERBOX_SAMPLE_RATE = 24000;
try {
    const modelId = await loadModel({
        modelSrc: TTS_T3_TURBO_EN_CHATTERBOX_Q8_0,
        modelConfig: {
            ttsEngine: 'chatterbox',
            language: 'en',
            s3genModelSrc: TTS_S3GEN_EN_CHATTERBOX.src,
            streamChunkTokens: 25,
            streamFirstChunkTokens: 10,
            cfmSteps: 1,
            threads: 8,
            ...(referenceAudioSrc ? { referenceAudioSrc } : {})
        },
        onProgress: (p) => {
            const mb = (n) => (n / 1e6).toFixed(1);
            const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`;
            process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`);
            if (p.percentage >= 100)
                process.stderr.write('\n');
        }
    });
    console.log(`▸ Model loaded: ${modelId}`);
    console.log('▸ Testing Text-to-Speech...');
    const result = textToSpeech({
        modelId,
        text: `QVAC SDK is the canonical entry point to QVAC. Written in TypeScript, it provides all QVAC capabilities through a unified interface while also abstracting away the complexity of running your application in a JS environment other than Bare. Supported JS environments include Bare, Node.js, Expo and Bun.`,
        inputType: 'text',
        stream: false
    });
    const audioBuffer = await result.buffer;
    console.log(`▸ TTS complete. Total bytes: ${audioBuffer.length}`);
    console.log('▸ Saving audio to file...');
    createWav(audioBuffer, CHATTERBOX_SAMPLE_RATE, 'tts-output.wav');
    console.log('▸ Audio saved to tts-output.wav');
    console.log('▸ Playing audio...');
    const audioData = int16ArrayToBuffer(audioBuffer);
    const wavBuffer = Buffer.concat([
        createWavHeader(audioData.length, CHATTERBOX_SAMPLE_RATE),
        audioData
    ]);
    playAudio(wavBuffer);
    console.log('▸ Audio playback complete');
    await unloadModel({ modelId });
    console.log('▸ Model unloaded');
    process.exit(0);
}
catch (error) {
    console.error('✖', error);
    process.exit(1);
}

Supertonic

The following script shows an example of Supertonic TTS for general-purpose speech synthesis. Use it with utils.js / utils.ts:

tts-supertonic.js
import { loadModel, textToSpeech, unloadModel, TTS_MULTILINGUAL_SUPERTONIC3_Q8_0 } from '@qvac/sdk';
import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './utils';
// Supertonic 3 TTS (GGML): fast multilingual synthesis with baked-in voices.
// Uses registry model constants — downloads automatically from QVAC Registry.
const SUPERTONIC_SAMPLE_RATE = 44100;
try {
    const modelId = await loadModel({
        modelSrc: TTS_MULTILINGUAL_SUPERTONIC3_Q8_0,
        modelConfig: {
            ttsEngine: 'supertonic',
            language: 'en',
            voice: 'F1',
            ttsSpeed: 1.05,
            ttsNumInferenceSteps: 5
        },
        onProgress: (p) => {
            const mb = (n) => (n / 1e6).toFixed(1);
            const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`;
            process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`);
            if (p.percentage >= 100)
                process.stderr.write('\n');
        }
    });
    console.log(`▸ Model loaded: ${modelId}`);
    console.log('▸ Testing Text-to-Speech...');
    const result = textToSpeech({
        modelId,
        text: `QVAC SDK is the canonical entry point to QVAC. Written in TypeScript, it provides all QVAC capabilities through a unified interface while also abstracting away the complexity of running your application in a JS environment other than Bare. Supported JS environments include Bare, Node.js, Expo and Bun.`,
        inputType: 'text',
        stream: false
    });
    const audioBuffer = await result.buffer;
    console.log(`▸ TTS complete. Total samples: ${audioBuffer.length}`);
    console.log('▸ Saving audio to file...');
    createWav(audioBuffer, SUPERTONIC_SAMPLE_RATE, 'supertonic-output.wav');
    console.log('▸ Audio saved to supertonic-output.wav');
    console.log('▸ Playing audio...');
    const audioData = int16ArrayToBuffer(audioBuffer);
    const wavBuffer = Buffer.concat([
        createWavHeader(audioData.length, SUPERTONIC_SAMPLE_RATE),
        audioData
    ]);
    playAudio(wavBuffer);
    console.log('▸ Audio playback complete');
    await unloadModel({ modelId });
    console.log('▸ Model unloaded');
    process.exit(0);
}
catch (error) {
    console.error('✖', error);
    process.exit(1);
}

Parler-TTS

The following TypeScript example loads the registry-hosted Parler Mini v1 model, applies per-request emotion conditioning, saves the PCM output, and plays it:

tts-parler.js
import { loadModel, textToSpeech, unloadModel, TTS_MINI_V1_EN_PARLER_TTS_Q8_0 } from '@qvac/sdk';
import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './utils';
// Parler-TTS (GGML): description-conditioned speech with per-call voice controls.
// Uses the registry-hosted Mini v1 Q8_0 model and its native 44.1 kHz output.
const PARLER_SAMPLE_RATE = 44100;
try {
    const modelId = await loadModel({
        modelSrc: TTS_MINI_V1_EN_PARLER_TTS_Q8_0,
        modelConfig: {
            ttsEngine: 'parler',
            voice: 'Laura',
            seed: 42
        },
        onProgress: (p) => {
            const mb = (n) => (n / 1e6).toFixed(1);
            const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`;
            process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`);
            if (p.percentage >= 100)
                process.stderr.write('\n');
        }
    });
    console.log(`▸ Model loaded: ${modelId}`);
    console.log('▸ Testing Parler Text-to-Speech...');
    const result = textToSpeech({
        modelId,
        text: 'Hey, how are you doing today?',
        inputType: 'text',
        stream: false,
        emotion: 'happy'
    });
    const audioBuffer = await result.buffer;
    console.log(`▸ TTS complete. Total samples: ${audioBuffer.length}`);
    console.log('▸ Saving audio to file...');
    createWav(audioBuffer, PARLER_SAMPLE_RATE, 'parler-output.wav');
    console.log('▸ Audio saved to parler-output.wav');
    console.log('▸ Playing audio...');
    const audioData = int16ArrayToBuffer(audioBuffer);
    const wavBuffer = Buffer.concat([
        createWavHeader(audioData.length, PARLER_SAMPLE_RATE),
        audioData
    ]);
    playAudio(wavBuffer);
    console.log('▸ Audio playback complete');
    await unloadModel({ modelId });
    console.log('▸ Model unloaded');
    process.exit(0);
}
catch (error) {
    console.error('✖', error);
    process.exit(1);
}

Chatterbox with LavaSR enhancer

The following script shows Chatterbox TTS with the LavaSR enhancer, which neurally bandwidth-extends the output to 48 kHz. Use it with utils.js / utils.ts:

tts-chatterbox-enhanced.js
import { loadModel, textToSpeech, unloadModel, TTS_T3_TURBO_EN_CHATTERBOX_Q8_0, TTS_S3GEN_EN_CHATTERBOX, TTS_ENHANCER_LAVASR_FP16 } from '@qvac/sdk';
import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './utils';
// Chatterbox TTS (GGML) with the LavaSR enhancer: synthesized audio is neurally
// bandwidth-extended to 48 kHz. Supplying the enhancer GGUF is what enables
// enhancement — there is no on/off flag — and it forces the output to 48 kHz.
// Usage: node chatterbox-enhanced.ts [referenceAudioSrc]
const [referenceAudioSrc] = process.argv.slice(2);
const ENHANCED_SAMPLE_RATE = 48000;
try {
    const modelId = await loadModel({
        modelSrc: TTS_T3_TURBO_EN_CHATTERBOX_Q8_0,
        modelConfig: {
            ttsEngine: 'chatterbox',
            language: 'en',
            s3genModelSrc: TTS_S3GEN_EN_CHATTERBOX.src,
            lavasrEnhancerModelSrc: TTS_ENHANCER_LAVASR_FP16.src,
            ...(referenceAudioSrc ? { referenceAudioSrc } : {})
        },
        onProgress: (p) => {
            const mb = (n) => (n / 1e6).toFixed(1);
            const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`;
            process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`);
            if (p.percentage >= 100)
                process.stderr.write('\n');
        }
    });
    console.log(`▸ Model loaded: ${modelId}`);
    console.log('▸ Testing Text-to-Speech (LavaSR enhancer)...');
    const result = textToSpeech({
        modelId,
        text: `QVAC SDK is the canonical entry point to QVAC. Written in TypeScript, it provides all QVAC capabilities through a unified interface while also abstracting away the complexity of running your application in a JS environment other than Bare. Supported JS environments include Bare, Node.js, Expo and Bun.`,
        inputType: 'text',
        stream: false
    });
    const audioBuffer = await result.buffer;
    console.log(`▸ TTS complete. Total samples: ${audioBuffer.length}`);
    console.log('▸ Saving audio to file...');
    createWav(audioBuffer, ENHANCED_SAMPLE_RATE, 'chatterbox-enhanced-output.wav');
    console.log('▸ Audio saved to chatterbox-enhanced-output.wav');
    console.log('▸ Playing audio...');
    const audioData = int16ArrayToBuffer(audioBuffer);
    const wavBuffer = Buffer.concat([
        createWavHeader(audioData.length, ENHANCED_SAMPLE_RATE),
        audioData
    ]);
    playAudio(wavBuffer);
    console.log('▸ Audio playback complete');
    await unloadModel({ modelId });
    console.log('▸ Model unloaded');
    process.exit(0);
}
catch (error) {
    console.error('✖', error);
    process.exit(1);
}

Supertonic with LavaSR denoiser + enhancer

The following script shows Supertonic TTS with the full LavaSR pipeline: the denoiser cleans the signal first, then the enhancer bandwidth-extends it to 48 kHz. Use it with utils.js / utils.ts:

tts-supertonic-enhanced.js
import { loadModel, textToSpeech, unloadModel, TTS_MULTILINGUAL_SUPERTONIC3_Q8_0, TTS_DENOISER_LAVASR_FP16, TTS_ENHANCER_LAVASR_FP16 } from '@qvac/sdk';
import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './utils';
// Supertonic 3 TTS (GGML) with LavaSR post-processing: the denoiser cleans the
// synthesized signal first, then the enhancer bandwidth-extends it to 48 kHz.
// Supplying the enhancer GGUF is what enables enhancement — there is no on/off
// flag — and it forces the output to 48 kHz regardless of the engine's native
// rate.
const ENHANCED_SAMPLE_RATE = 48000;
try {
    const modelId = await loadModel({
        modelSrc: TTS_MULTILINGUAL_SUPERTONIC3_Q8_0,
        modelConfig: {
            ttsEngine: 'supertonic',
            language: 'en',
            voice: 'F1',
            // Denoiser runs first (rate-preserving)…
            lavasrDenoiserModelSrc: TTS_DENOISER_LAVASR_FP16.src,
            // …then the enhancer bandwidth-extends to 48 kHz.
            lavasrEnhancerModelSrc: TTS_ENHANCER_LAVASR_FP16.src
        },
        onProgress: (p) => {
            const mb = (n) => (n / 1e6).toFixed(1);
            const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`;
            process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`);
            if (p.percentage >= 100)
                process.stderr.write('\n');
        }
    });
    console.log(`▸ Model loaded: ${modelId}`);
    console.log('▸ Testing Text-to-Speech (LavaSR denoiser + enhancer)...');
    const result = textToSpeech({
        modelId,
        text: `QVAC SDK is the canonical entry point to QVAC. Written in TypeScript, it provides all QVAC capabilities through a unified interface while also abstracting away the complexity of running your application in a JS environment other than Bare. Supported JS environments include Bare, Node.js, Expo and Bun.`,
        inputType: 'text',
        stream: false
    });
    const audioBuffer = await result.buffer;
    console.log(`▸ TTS complete. Total samples: ${audioBuffer.length}`);
    console.log('▸ Saving audio to file...');
    createWav(audioBuffer, ENHANCED_SAMPLE_RATE, 'supertonic-enhanced-output.wav');
    console.log('▸ Audio saved to supertonic-enhanced-output.wav');
    console.log('▸ Playing audio...');
    const audioData = int16ArrayToBuffer(audioBuffer);
    const wavBuffer = Buffer.concat([
        createWavHeader(audioData.length, ENHANCED_SAMPLE_RATE),
        audioData
    ]);
    playAudio(wavBuffer);
    console.log('▸ Audio playback complete');
    await unloadModel({ modelId });
    console.log('▸ Model unloaded');
    process.exit(0);
}
catch (error) {
    console.error('✖', error);
    process.exit(1);
}

Utils

The following helper script is used by the examples above to convert the raw PCM samples returned by textToSpeech() into a WAV file and play it back:

utils.js
import { writeFileSync, unlinkSync } from 'fs';
import { spawn, spawnSync } from 'child_process';
import { platform, tmpdir } from 'os';
import { join } from 'path';
/**
 * Create WAV header for 16-bit PCM audio
 */
export function createWavHeader(dataLength, sampleRate) {
    const header = Buffer.alloc(44);
    // RIFF header
    header.write('RIFF', 0);
    header.writeUInt32LE(36 + dataLength, 4);
    header.write('WAVE', 8);
    // fmt chunk
    header.write('fmt ', 12);
    header.writeUInt32LE(16, 16); // fmt chunk size
    header.writeUInt16LE(1, 20); // PCM format
    header.writeUInt16LE(1, 22); // mono
    header.writeUInt32LE(sampleRate, 24);
    header.writeUInt32LE(sampleRate * 2, 28); // byte rate
    header.writeUInt16LE(2, 32); // block align
    header.writeUInt16LE(16, 34); // bits per sample
    // data chunk
    header.write('data', 36);
    header.writeUInt32LE(dataLength, 40);
    return header;
}
/**
 * Convert Int16Array to Buffer
 */
export function int16ArrayToBuffer(samples) {
    const buffer = Buffer.alloc(samples.length * 2);
    for (let i = 0; i < samples.length; i++) {
        const value = Math.max(-32768, Math.min(32767, Math.round(samples[i] ?? 0)));
        buffer.writeInt16LE(value, i * 2);
    }
    return buffer;
}
/**
 * Create and save WAV file
 */
export function createWav(audioBuffer, sampleRate, filename) {
    const audioData = int16ArrayToBuffer(audioBuffer);
    const wavHeader = createWavHeader(audioData.length, sampleRate);
    const wavFile = Buffer.concat([wavHeader, audioData]);
    writeFileSync(filename, wavFile);
    console.log(`▸ WAV file saved as: ${filename}`);
}
/**
 * Play a WAV buffer by streaming it into ffplay over stdin.
 *
 * ffplay ships with ffmpeg and is cross-platform (macOS/Linux/Windows), so
 * we avoid the old "write to /tmp then shell out to afplay/aplay/powershell"
 * dance — no temp files, no platform switch, no hardcoded /tmp path (which
 * doesn't exist on Windows). Requires ffplay on PATH.
 */
/**
 * Play one mono s16le PCM chunk (as a minimal WAV) and wait for the player to finish.
 * Chunks are played sequentially when awaited in order — suitable for streaming TTS output.
 */
export function playPcmInt16Chunk(samples, sampleRate) {
    if (samples.length === 0) {
        return Promise.resolve();
    }
    const audioData = int16ArrayToBuffer(samples);
    const wavHeader = createWavHeader(audioData.length, sampleRate);
    const wavFile = Buffer.concat([wavHeader, audioData]);
    // `os.tmpdir()` resolves to the OS-specific temp directory (e.g. `%TEMP%`
    // on Windows), so the Windows branch below no longer tries to read a
    // POSIX-only `/tmp/...` path.
    const tempFile = join(tmpdir(), `qvac-tts-chunk-${Date.now()}-${Math.random().toString(16).slice(2)}.wav`);
    writeFileSync(tempFile, wavFile);
    const currentPlatform = platform();
    let audioPlayer;
    let args;
    switch (currentPlatform) {
        case 'darwin':
            audioPlayer = 'afplay';
            args = [tempFile];
            break;
        case 'linux':
            audioPlayer = 'aplay';
            args = [tempFile];
            break;
        case 'win32':
            audioPlayer = 'powershell';
            args = [
                '-Command',
                `Add-Type -AssemblyName presentationCore; (New-Object Media.SoundPlayer).LoadStream([System.IO.File]::ReadAllBytes('${tempFile}')).PlaySync()`
            ];
            break;
        default:
            audioPlayer = 'aplay';
            args = [tempFile];
    }
    return new Promise(function (resolve, reject) {
        const proc = spawn(audioPlayer, args, { stdio: 'ignore' });
        proc.on('error', function (err) {
            try {
                unlinkSync(tempFile);
            }
            catch {
                // ignore
            }
            reject(err);
        });
        proc.on('close', function (code) {
            try {
                unlinkSync(tempFile);
            }
            catch {
                // ignore
            }
            if (code === 0) {
                resolve();
            }
            else {
                reject(new Error(`Audio player exited with code ${code}`));
            }
        });
    });
}
export function playAudio(audioBuffer) {
    const result = spawnSync('ffplay', ['-hide_banner', '-loglevel', 'error', '-autoexit', '-nodisp', '-i', 'pipe:0'], {
        input: audioBuffer,
        stdio: ['pipe', 'inherit', 'inherit']
    });
    if (result.error) {
        const code = result.error.code;
        if (code === 'ENOENT') {
            throw new Error('ffplay not found on PATH. Install ffmpeg (ffplay ships with it) and retry.');
        }
        throw new Error(`ffplay failed: ${result.error.message}`);
    }
    if (result.status !== 0) {
        throw new Error(`ffplay exited with code ${result.status}`);
    }
}

Tip: all examples throughout this documentation are self-contained and runnable. For instructions on how to run them, see the JS/TS quickstart or the Python quickstart.

On this page

Ask anything about QVAC.