Skip to content
Jan Janak edited this page Jan 28, 2021 · 17 revisions

PulseAudio provides a sample cache that allows the client to upload audio files to the server where they will be stored in memory. The audio files can be played using a single command sent to the PulseAudio server. This page illustrates how a JavaScript application can upload a wav file to the sample cache and play it.

The wav module provides a convenient streaming API to wav files. Import the library, initialize a PulseAudio client and connect to the server. First, create a PulseAudio client object and connect to the server.

import * as fs from 'fs';
import * as wav from 'wav';
import { PulseAudio, PA_SAMPLE_FORMAT } from 'pulseaudio.js';

const pa = new PulseAudio();
await pa.connect();

The following JavaScript snippet shows how to upload a wav file into the sample cache on the PulseAudio server. We create a filesystem stream reader for the wav file (beep.wav) and read the entire file into memory using a reader provided by the wav module. We then create a new upload stream using the method createUploadStream and write the data from memory to the stream. Note that error checking is omitted for brevity.

const name = 'beep.wav';

// Create a new filesystem read stream for the wav file and a new wav format reader
const file = fs.createReadStream(name);
const reader = new wav.Reader();

// The contents of the wav file will be stored in the following variable in the form
// of an array of buffers.
const data = [];

// The wav format reader emits a "format" event once it has read the entire wav header.
// We do all our processing in the callback for the event.
reader.once('format', ({ bitDepth, channels, sampleRate }) => {
    // PulseAudio server needs to know the total length of each sample file. Since
    // the total length isn't present in the wav header, we need to load the entire
    // file into memory first.
    reader.on('data', c => data.push(c));

    // The following event is emitted once the entire wav file has been read
    reader.once('end', async () => {
        // Calculate the total length of the wav file in bytes
        const maximumLength = data.reduce((a, v) => a + v.length, 0);
        // Create an object describing the format of the sample file in a format
        // understood by PulseAudio server API
        const sampleSpec = {
            format : bitDepth === 16 ? PA_SAMPLE_FORMAT.S16LE : PA_SAMPLE_FORMAT.U8,
            rate   : sampleRate,
            channels
        };

        // Create a new upload stream. Use the filename as the name of the sample
        const output = await pa.createUploadStream({ name, maximumLength, sampleSpec });
        // Write all WAV data from the memory buffer into the upload stream and close it
        for(const chunk of data) output.write(chunk);
        output.end();
    });
});

// Connect the file reader stream to the wav reader stream to get the upload started
file.pipe(reader);

Since the PulseAudio server needs to know the total size of the audio file in advance, the wav file cannot be simply streamed to the server. Instead, we load the entire wav file into memory and calculate its size. This is necessary because the wav file header is not guaranteed to provide the length of the file contents. The method createUploadStream needs be provided with the name of the sample to be created, its format, and the length of data in bytes. In the above code, we calculate all the parameters from the data passed to the 'format' event callback by the wav module.

To play the sample, invoke the method playSample with the name of the sample as argument. The method returns a number that identifies the created playback stream (not very useful at the moment).

const num = await pa.playSample('beep.wav');

Audio samples remain in the cache on the server until removed or until the server is restarted. Use the method removeSample to remove the audio sample manually:

await pa.removeSample('beep.wav');