meowcaller-js / Docs / Audio

Audio

meowcaller-js works with 16 kHz mono float32 PCM audio. Each frame is 960 samples (60 ms).

Constants

import { SampleRate, FrameSamples } from 'meowcaller-js';

console.log(SampleRate);   // 16000
console.log(FrameSamples); // 960

Sources

A source provides audio frames to the player. It implements:

interface AudioSource {
  readFrame(): Promise<Float32Array | null>;
  close(): Promise<void>;
}

Return null from readFrame() to signal end-of-stream.

WAVFile(path)

Read and play a WAV file. Handles resampling to 16 kHz and mono downmix automatically.

import { WAVFile } from 'meowcaller-js';

const source = await WAVFile('audio.wav');
call.play(source);

Supports:

  • 16-bit PCM WAV files
  • Any sample rate (auto-resampled to 16 kHz)
  • Mono or stereo (downmixed to mono)

PCMStream(readable)

Read raw signed 16-bit little-endian PCM from a Node.js readable stream.

import { createReadStream } from 'fs';
import { PCMStream } from 'meowcaller-js';

const stream = createReadStream('raw_audio.pcm');
const source = PCMStream(stream);
call.play(source);

SourceFunc(provider)

Create a source from a simple async function. The function is called once per frame and should return a Float32Array of 960 samples, or null for EOF.

import { SourceFunc } from 'meowcaller-js';

// Generate silence
const silence = SourceFunc(async () => new Float32Array(960));

// Generate a sine wave
let phase = 0;
const tone = SourceFunc(async () => {
  const frame = new Float32Array(960);
  for (let i = 0; i < 960; i++) {
    frame[i] = Math.sin(phase) * 0.3;
    phase += (2 * Math.PI * 440) / 16000;
  }
  return frame;
});

call.play(tone);

MP3File(path) / OpusFile(path)

Not yet implemented. These throw an error directing you to use PCMStream with an external decoder.

// Use a decoder like @breezystack/lamejs or opus-recorder
// to decode to PCM, then pipe through PCMStream

Sinks

A sink receives audio frames from incoming calls. It implements:

interface AudioSink {
  writeFrame(frame: Float32Array): Promise<void>;
  close(): Promise<void>;
}

SinkFunc(fn)

Create a sink from a callback function.

import { SinkFunc } from 'meowcaller-js';

call.receive(SinkFunc((frame) => {
  // frame: Float32Array of 960 samples at 16 kHz
  // do something with the audio data
}));

Player

The Player manages audio playback state.

import { NewPlayer, PlayerState } from 'meowcaller-js';

const player = NewPlayer();
player.play(source);

NewPlayer()

Creates a new player instance.

Player methods

MethodDescription
player.play(source)Start playing from a source. Closes any previous source.
player.pause()Pause playback (state → Paused)
player.resume()Resume playback (state → Playing)
player.stop()Stop playback and close the source (state → Idle)
player.state()Returns current PlayerState
player.onFinish(fn)Register a callback for when playback ends
player.nextFrame()Get the next frame (used internally by the engine)

Player states

StateDescription
PlayerState.IdleNo source or source ended
PlayerState.PlayingActively reading frames
PlayerState.PausedPaused, will not read frames

Using call.play() vs manual player

call.play() is a convenience that creates a player, subscribes it, and starts playback:

// This:
call.play(source);

// Is equivalent to:
const player = NewPlayer();
call.subscribe(player);
player.play(source);

Use the manual approach when you need to control playback (pause, resume, stop) later:

const player = NewPlayer();
call.subscribe(player);
player.play(source);

// Later:
player.pause();
// ...
player.resume();

Audio format

All audio in meowcaller-js is:

  • Sample rate: 16,000 Hz
  • Channels: Mono
  • Bit depth: 32-bit float
  • Frame size: 960 samples (60 ms at 16 kHz)
  • Encoding: Float32Array where 1.0 = maximum amplitude

The MLow codec (WhatsApp's default) operates on this format. The encoder/decoder is currently a passthrough stub — the raw PCM bytes are sent as-is until a WASM port of the Go MLow codec is available.

Sample sounds

Ready-to-use WAV files for testing your bot:

FileDescriptionDownload
greeting.wavShort greeting tone for answering callsgreeting.wav
tone.wavExample audio for testing playbacktone.wav
// Use in your bot:
const greeting = await WAVFile('greeting.wav');
call.play(greeting);