Skip to content

JavaScript/TypeScript API Reference ​

The npm package @libraz/libsonare is the WebAssembly build of the engine: analysis, mastering, mixing, editing DSP, and a realtime engine, all running in the browser. This page covers install, init(), your first call, and the conventions every function shares (request objects, errors, default sample rates). Per-subject reference pages follow from the list at the end.

Overview ​

Most functions take decoded PCM, not a file: a Float32Array of samples plus the sampleRate it was decoded at. To get from encoded bytes to samples, decode with the Web Audio API (AudioContext.decodeAudioData) or use the Audio.fromMemory* factories, which decode WAV/MP3 with the bundled WASM decoder and AAC/OGG/FLAC through the browser when needed. decodeChannelsWithBrowserFallback(bytes, options?) takes the same path but keeps every channel instead of folding to mono; see Keeping Every Channel.

Start from the task, not from the function list:

You needStart withWhy
One tempo, key, or beat valuedetectBpm, detectKey, detectBeatsDirect answers without building the all-in-one analysis object
Metadata for a whole songanalyze, analyzeWithProgress, or a focused analyze* helperanalyze gives the common summary; the helpers expose more detail
Spectral features for ML or visualizationmelSpectrogram, chroma, mfccMatrix outputs
A live visualizer or an updating BPM/key/chord UIStreamAnalyzerProcesses small blocks; the UI reads the newest frames
Remixing, practice tools, region repairhpss, timeStretch, pitchShift, spectralEditAudio effects
Vocal tuning, note edits, pitch or formant changespitchCorrectToMidi, noteStretch, voiceChange, StreamingRetune, RealtimeVoiceChangerEditing DSP changes the signal rather than analyzing it
Loudness targets, true-peak limiting, delivery previewmasterAudio*, masteringChain*, StreamingMasteringChainPresets first, named processors when you need control
Stem balance, sends, buses, or metersmixStereo or MixerOne-shot mix first; a persistent scene mixer when routing matters
Room decay, clarity, equivalent-room estimates, generated room characteranalyzeImpulseResponse, detectAcoustic, estimateRoom, synthesizeRir, roomMorph, roomMorphStereoThese describe or apply the recording space, not the music
Loading filesAudio.fromBuffer, Audio.fromMemory, Audio.fromMemoryWithBrowserFallbackDecoding plus method-style access to common functions

New to audio terms such as BPM, STFT, or chroma? See the Glossary. For a cross-binding feature map, see Feature Map; for the mastering processor registry and the mixing scene format, see Mastering Processors and Mixing Scene JSON.

Installation ​

bash
npm install @libraz/libsonare
bash
yarn add @libraz/libsonare
bash
pnpm add @libraz/libsonare

Import ​

The package has five entries. The main entry carries everything; the others are narrower bundles for specific realms.

EntryImport pathContainsUse it when
Main@libraz/libsonareThe full API: analysis, effects, mastering, mixing, projects, realtime engineAlmost always
Analysis@libraz/libsonare/analysisAnalysis and features only: no mastering, mixing, realtime, or project APIsYou want the smallest download for analysis alone
Worklet@libraz/libsonare/workletThe AudioWorklet-side runtime, including EXPECTED_ABI_VERSION, engineMixerMasterTarget, ENGINE_MIXER_PARAM_FADER_DB, ENGINE_MIXER_PARAM_PAN and SONARE_METER_RING_PROTOCOL_VERSIONCode running inside an AudioWorkletGlobalScope
Worker@libraz/libsonare/workerThe offline worker script behind OfflineWorkerClientRunning analysis off the main thread; see Workers
Vocal-edit worker@libraz/libsonare/vocal-edit-workerThe worker script behind VocalEditWorkerClientVocal-edit drafts and render jobs off the main thread

See Package Artifacts for the files each entry ships. The bundles target ECMAScript 2020.

typescript
import {
  init,
  Audio,
  detectBpm,
  detectKey,
  detectBeats,
  detectOnsets,
  analyze,
  analyzeWithProgress,
  version
} from '@libraz/libsonare';

Initialization ​

init(options?) ​

Initializes the WASM module. Call it once, before any other function that needs the module.

typescript
async function init(options?: {
  locateFile?: (path: string, prefix: string) => string;
  wasmBinary?: ArrayBuffer | Uint8Array;
  moduleFactory?: (options?: {
    locateFile?: (path: string, prefix: string) => string;
    wasmBinary?: ArrayBuffer | Uint8Array;
  }) => Promise<SonareModule>;
}): Promise<void>

Options apply on the first call only. Calling init() again with no options, or with the same options (functions compared by reference), resolves immediately. A second call whose options differ throws SonareError with InvalidState, naming the option. Every function that needs the module throws InvalidState when called before init() resolves.

init() also compares the loaded module's ABI version with the one the package was built against and throws AbiMismatch (code 10) when the .wasm and the JavaScript come from different builds, which a stale cache or a custom wasmBinary / locateFile can produce. The expected version is exported as EXPECTED_ABI_VERSION from the package root and from @libraz/libsonare/worklet, so a test double passed as moduleFactory can report the version init() checks for.

Your first call ​

typescript
import { init, detectBpm } from '@libraz/libsonare';

await init();                                    // once, at app start

const bytes = await (await fetch('/song.mp3')).arrayBuffer();
const ctx = new AudioContext();
const decoded = await ctx.decodeAudioData(bytes);
const samples = decoded.getChannelData(0);       // Float32Array, first channel

const bpm = detectBpm({ samples, sampleRate: decoded.sampleRate });
console.log(`${bpm.toFixed(1)} BPM`);

Pass the sampleRate the samples were decoded at. To serve the .wasm from another path, pass locateFile:

typescript
await init({
  locateFile: (path, prefix) => `/custom/wasm/path/${path}`
});

Once that works, move to analyze, mastering, mixing, or streaming.

isInitialized() ​

Checks whether the module is initialized.

typescript
function isInitialized(): boolean

version() ​

Returns the library version.

typescript
function version(): string  // e.g., "1.10.0"

One-shot request objects ​

The top-level one-shot analysis, effects, mastering, metering, feature, mixer, and voice-changer APIs use a named request object as their canonical form. Every input is named, optional settings can grow without changing argument order, and TypeScript can guide you to the matching *Request type. Positional forms are compatibility overloads with identical defaults, validation, errors, results, and progress behavior. Every positional one-shot also takes a trailing { validate?: boolean }: { validate: false } skips the JavaScript-side scan for NaN and infinity on hot paths, and the native layer still rejects non-finite input.

typescript
// Preferred request-object form
const bpm = detectBpm({ samples, sampleRate });
const mastered = masterAudio({
  samples,
  sampleRate,
  preset: 'pop',
  overrides: { loudness: { targetLufs: -14 } },
  onProgress: (progress, stage) => console.log(stage, progress),
});

// Positional form
const legacyBpm = detectBpm(samples, sampleRate);

A request object refuses a key its entry point does not declare: detectBpm({ samples, sampleRate, bpmMin: 90 }) throws TypeError: detectBpm: unknown request key 'bpmMin', and a key that belongs in a nested object says where ('loudness' belongs under 'config'). Nested options objects and the options bag of a positional form refuse unknown options the same way. Tempo range limits belong to analyzeBpm and analyze, not detectBpm.

The request fields use the same camelCase names on the Node and WASM packages. Python remains keyword-oriented (detect_bpm(samples, sample_rate=...)), rather than adopting a JavaScript-style options object.

Canceling a long call ​

Requests that report progress also accept cancel, a predicate polled at the same native boundaries onProgress fires on. Return true and the call aborts.

typescript
import { ErrorCode, isSonareError, masterAudio } from '@libraz/libsonare';

let abandoned = false;
cancelButton.onclick = () => { abandoned = true; };

try {
  const mastered = masterAudio({
    samples,
    sampleRate,
    preset: 'pop',
    onProgress: (progress, stage) => updateUi(progress, stage),
    cancel: () => abandoned,
  });
} catch (error) {
  if (!(isSonareError(error) && error.code === ErrorCode.Cancelled)) throw error;
}

A canceled call throws SonareError with code 8 (Cancelled) and leaves its outputs unallocated, so there is no partial result to inspect. Python takes the same predicate as cancel=.

Default sample rates ​

When sampleRate is optional, the default depends on the function family. Pass the buffer's real rate whenever you know it.

FamilyDefault
Analysis, features, loudness, pitch, and effects (detectBpm, stft, melSpectrogram, hpss, timeStretch, ...)22050
Audio.fromBuffer, voiceChangeRealtime, roomMorphStereo48000
StreamAnalyzer (StreamConfig.sampleRate)44100

Error Handling ​

Failures arrive as one of three classes, depending on who refused the input:

ClassRaised byMatched by isSonareError
TypeErrorThe JavaScript binding: a wrong type or a missing argumentNo
RangeErrorA refused value: outside its domain, or an unknown id, name or parameter, whether the binding or the library refused itNo
SonareErrorA failure the arguments did not cause: the state of an object, the build, or the dataYes

SonareError is an Error subclass carrying a numeric code and its canonical codeName, mirroring the C ABI error enum. The same failure reports the same code on every binding (WASM, Node native, Python, C ABI), so branch on the code rather than the message. The package exports the ErrorCode enum, the SonareError class, and the isSonareError(value) type guard.

typescript
import { ErrorCode, isSonareError, Mixer } from '@libraz/libsonare';

try {
  const mixer = Mixer.fromSceneJson(sceneJson, 48000, 512);
} catch (error) {
  if (error instanceof TypeError || error instanceof RangeError) {
    // The binding refused an argument; fix the call site.
    console.error(`bad argument: ${error.message}`);
  } else if (isSonareError(error) && error.code === ErrorCode.InvalidFormat) {
    console.error(`scene is not valid JSON: ${error.message}`);
  } else if (isSonareError(error) && error.code === ErrorCode.InvalidParameter) {
    // 'failed to build mixer from scene JSON: unknown insert processor: nope (strip a)'
    console.error(`scene field rejected: ${error.message}`);
  } else {
    throw error;
  }
}

Which failure raises which error ​

FailureRaises
Calling a function before init() resolvesSonareError InvalidState (7)
A second init() with different optionsSonareError InvalidState (7), naming the option
.wasm and JavaScript from different buildsSonareError AbiMismatch (10)
Any method on a released or destroyed handleSonareError InvalidState (7)
A mixer scene that is not valid JSON (Mixer.fromSceneJson, Project.fromJson)SonareError InvalidFormat (2)
A scene or preset field the library rejects: an unknown preset or insert, a bad send timingSonareError InvalidParameter (4)
An argument a Project, RealtimeEngine, Mixer setter or synth patch call refuses: an out-of-range value, an unknown or zero id, an unknown name or engineParams key, a malformed patchRangeError (TypeError for a wrong type), never a SonareError
A refusal caused by earlier configuration: a full binding, strip or sidechain table, a master-strip setter before a master strip exists, a bounce whose derived length does not fitSonareError InvalidState (7)
An automation id for an unknown target or parameter (resolve*AutomationId), or a parameter that is not realtime-safeRangeError; no call returns -1
A rejected stream or STFT configurationSonareError with the code the core reports
A canceled callSonareError Cancelled (8)
A null or wrongly typed argument, such as a samples that is not a Float32ArrayTypeError
Empty or non-finite audio; a sampleRate that is not an integer in [8000, 384000]; a fractional integer argument; two buffers whose lengths must match; a binding-resolved name that is unknown (such as a keyRoot)RangeError
A string with an embedded NUL passed to an entry point that checks for it (mixingScenePresetJson, realtimeVoiceChangerPresetJson, validateRealtimeVoiceChangerPresetJson, realtimeVoiceChangerPresetConfig, noteToHz, Project.fromJson)RangeError naming the field

Mixer.fromSceneJson reports InvalidFormat for a document that is not valid JSON and InvalidParameter for a field the mixer rejects; the field is named at the tail of the message, failed to build mixer from scene JSON: <inner>.

Every one-shot that takes samples and a sample rate checks them before the core is reached, so no raw embind BindingError reaches a caller. The facades reject non-finite numbers, invalid enum or index values, and oversized resources instead of clamping them; treat these as invalid input. Because a refused argument is a RangeError, a handler written only for isSonareError does not catch it: catch RangeError and TypeError for bad arguments, and branch on the code for the rest. The offline and vocal-edit worker clients and the AudioWorklet processors keep the class and any SonareError code of the failure across the thread boundary.

Error codes ​

ErrorCodeValue
Ok0
FileNotFound1
InvalidFormat2
DecodeFailed3
InvalidParameter4
OutOfMemory5
NotSupported6
InvalidState7
Cancelled8
EncodeFailed9
AbiMismatch10
Unknown99

AbiMismatch is made by the bindings, never by the core: it reports a native module whose ABI version differs from the one the package expects (WASM in init(), Node when the addon is required, Python on first library use); Installation lists the causes and fixes. The other codes match Python's SonareError.code and the C ABI SonareError enum, and the Python CLI maps them onto its exit codes. For the same failures on Node, Python, the C ABI, and the CLI, see the error contract.

Build and Environment ​

capabilities() ​

Describes the build that is actually loaded, the same report the CLIs print as doctor. Synchronous, and valid only after init().

typescript
function capabilities(): {
  version: string;
  abi: { project: number; engine: number };
  platform: string;
  features: {
    mastering: boolean;
    mixing: boolean;
    mixingAssistant: boolean;
    fx: boolean;
    ffmpeg: boolean;
    instrumentParamAutomation: boolean;
    arrangement: boolean;
    acousticSim: boolean;
    pitchEditor: boolean;
    vocalEdit: boolean;
    voiceChanger: boolean;
    playback: boolean;
  };
  decode: { builtin: string[]; ffmpeg: string[] };
  simd: string;
  hardwareConcurrency: number;
}

Branch on features instead of guessing. decode.builtin lists the formats the module can open before you ask the browser to fall back, and simd reads "wasm-simd128" for the shipped build.

FlagGuards
masteringmasterAudio*, masteringChain*, StreamingMasteringChain, the named mastering processors
mixingMixer, mixStereo, and the mixing scene functions
mixingAssistantsuggestMixScene
fxThe effects subsystem
ffmpegFFmpeg decoding (false in the browser package)
instrumentParamAutomationInstrument-parameter automation targets
arrangementProject, clips, and the arrangement subsystem
acousticSimanalyzeImpulseResponse, detectAcoustic, estimateRoom, synthesizeRir, roomMorph*
pitchEditorNote and pitch editing
vocalEditVocal-edit drafts and render jobs
voiceChangerRealtimeVoiceChanger, voiceChange*
playbackPlaybackRenderer (upmix, binaural, speaker output)

mixingAssistant and instrumentParamAutomation are the two probes a host is most likely to need and least likely to reach for, because the entry points they guard stay registered whether or not the subsystem was built. suggestMixScene and the instrument-parameter automation targets exist as functions in every module and throw when the subsystem is absent, so typeof fn === 'function' is not a capability check. instrumentParamAutomation reports false in a build without the arrangement subsystem; mixingAssistant is compiled out of the analysis-only module, so that build reports false even though the symbol is there.

capabilityCatalog() ​

Returns a machine-readable catalog of every processor, its parameter descriptors, and the built-in preset lists. It is the same canonical JSON the C ABI publishes and Python exposes as capability_catalog, validated against schemas/capability-catalog.schema.json.

typescript
function capabilityCatalog(): {
  version: string;
  abi: { project: number; engine: number };
  processors: CapabilityCatalogProcessor[];   // id, kind, latency, tail, params, slots, ...
  chain: CapabilityCatalogChain;              // how every flat mastering chain key is described
  presets: {
    mastering: string[];
    synth: string[];
    mixingScene: string[];
    voiceChanger: string[];
    playbackRoom: string[];                   // always present; empty when playback is compiled out
  };
  masteringPresets: Array<{
    name: string;
    kind: 'mastering' | 'restoration';
    targetLufs: number | null;
    truePeakCeilingDb: number | null;
    maxLimiterGainReductionDb: number | null;
  }>;
}

This is what you build a generic parameter UI from: every slider's range, default, unit, and axis (uiMin / uiMax, scale) comes from the catalog instead of a table you maintain by hand. Each descriptor field is documented in Insert Effects. A null bound means the core knows of no limit on that side; treat it as "unbounded or unknown", not as zero.

A published bound is a hard constraint, not a recommended UI range

Bounds are measured with every other parameter of the processor at its default, so two parameters that constrain each other each report the other's default; a sample-rate-derived bound such as an EQ band*.frequencyHz ceiling reads as the un-prepared 24000; and an exclusive bound is reported as the limit it excludes. Reading a catalog bound explains each case.

abiVersion() ​

Returns the aggregate native ABI version across the C POD surfaces (the value EXPECTED_ABI_VERSION holds). Persist or compare it when loading a prebuilt binary so an incompatible JS/native artifact pair fails early.

typescript
function abiVersion(): number

projectAbiVersion() ​

ABI version of the project/editing POD API used by Project serialization, bounce, and realtime-engine clip exchange.

typescript
function projectAbiVersion(): number

voiceChangerAbiVersion() ​

ABI version of the realtime voice-changer POD config used by native and FFI APIs. This is separate from preset JSON schemaVersion, currently 1. Check user-authored presets with validateRealtimeVoiceChangerPresetJson(...) before accepting them.

typescript
function voiceChangerAbiVersion(): number

Voice Preset Accessors ​

Use these when you need the canonical voice-character preset ID or the resolved flat POD config without parsing preset JSON.

typescript
function voiceCharacterPresetId(preset: VoicePresetId | number): VoicePresetId | null
function realtimeVoiceChangerPresetConfig(preset: VoicePresetId | number): RealtimeVoiceChangerPodConfig

voiceCharacterPresetId(...) returns null for an unknown numeric ordinal. Unknown string IDs throw. realtimeVoiceChangerPresetConfig(...) throws for an invalid ordinal or unknown ID because it must return a resolved POD config.

The resolved RealtimeVoiceChangerPodConfig uses camelCase keys on both JavaScript surfaces (inputGainDb, wetMix, formantFactor, formantMode, limiterIspCeilingDbtp, and so on). The equivalent C and Python POD fields remain snake_case. A POD config handed back to the engine must carry every field, formantMode ('relative' or 'absolute') included.

Realtime environment helpers ​

These helpers describe the runtime capabilities used by RealtimeEngine. Use them before wiring AudioWorklet/SharedArrayBuffer paths, especially when the page may run under different browser isolation policies.

typescript
function engineAbiVersion(): number
function engineCapabilities(): {
  engineAbiVersion: number;
  expectedEngineAbiVersion: number;
  abiCompatible: boolean;
  sharedArrayBuffer: boolean;
  atomics: boolean;
  audioWorklet: boolean;
  mode: 'sab' | 'postMessage';
}
function hasFfmpegSupport(): boolean

hasFfmpegSupport() reports whether the loaded build can decode through FFmpeg. The browser/WASM npm package works on decoded PCM and normally returns false; Python/native builds are the intended place to decode files directly.

Browser support and bundle size ​

The shipped module is built with WebAssembly SIMD, so SIMD support sets the browser minimums. Browser Compatibility has the table and Bundle Size has the file sizes.

Releasing Native Handles ​

Every class that owns a native handle (Mixer, StreamAnalyzer, Project, ProjectTimeline, RealtimeEngine, ClipPageProvider, ClipPageStreamer, StreamingEqualizer, StreamingMasteringChain, StreamingRetune, RealtimeVoiceChanger, PlaybackRenderer, and the others) has an idempotent destroy() (with delete() as an alias) and implements [Symbol.dispose](), so the using declaration releases it when the scope ends, even on an exception. Audio holds no native handle, so its [Symbol.dispose]() does nothing and using still works on it.

typescript
import { init, Mixer, mixingScenePresetJson } from '@libraz/libsonare';

await init();
{
  using mixer = Mixer.fromSceneJson(mixingScenePresetJson('vocalReverbSend'), 48000, 512);
  // ... process blocks; the mixer is released at the closing brace.
}

The package installs Symbol.dispose when the host lacks it, so the methods exist everywhere. The using syntax itself needs TypeScript 5.2 or later (with esnext.disposable in lib), a bundler that lowers it, or a runtime with native support; otherwise call destroy() in a finally block. A method called on a released handle throws SonareError with InvalidState.

Reference Pages ​

The reference is split by subject. Every page below assumes the installation, import, and initialization steps above.

  • Analysis: song-level analysis, feature extraction, and Roman-numeral chord analysis.
  • Helpers and Conversions: librosa-compatible helpers, scale quantization, unit conversion, resampling, and chord functions.
  • Effects: audio effects, editing DSP, and room-acoustics measurement.
  • Mastering: the mastering chain, named processors, presets, and the one-shot dynamics and repair processors.
  • Streaming and Realtime: block-by-block processors, the streaming mastering chain, and the incremental StreamAnalyzer.
  • Audio, Metering and Mixing: the Audio class, metering, take alignment, mixer strips, and the mixing API.
  • Types and Enumerations: type definitions, enumerations, and the type export index.
  • Workers: OfflineWorkerClient and VocalEditWorkerClient for running offline work off the main thread.

Projects, instruments & live MIDI ​

The package also exposes the project, synthesis, and live-input APIs used to turn MIDI/clip arrangements into audio. These are summarized here; each topic has a dedicated guide.

GoalUseGuide
Start an empty projectProject.create() (or new Project())Project Editing
Build/load a clip + MIDI arrangement and edit itProject (Project.fromJson, toSceneJson, MIDI event helpers)Project Editing
Preserve opaque analysis/assist metadataproject.setAssistSidecar(...), assistSidecars()Project Editing
Classify automation lanesProjectAutomationTargetKind, targetKind on ProjectAutomationLaneDescProject Editing
Render a project to audioproject.bounceWithSynthInstrument(s)Project Bounce
Pick a built-in synth voicesynthPresetNames(), synthPresetPatch(name), engine.setSynthInstrument(...)Native Synth
Play through a SoundFontproject.loadSoundFont(bytes) / engine.loadSoundFont(bytes)SoundFont Player
Schedule MIDI clips into the live engine, sample-accuratelyengine.setMidiClips(...), engine.sampleAtPpq(ppq)Realtime Engine
Set per-track cue monitoring (heard in live process() output; offline renders, bounces and freezes exclude it)`engine.setTrackMonitorMode(laneIndex, 'off''pfl'
Mix the engine's tracks live with lanes, buses, sends, and stripsengine.setTrackLanes(...) (at most MAX_TRACK_LANES, 32), engine.setTrackBuses(...), strip JSON settersRealtime Engine
Send a track to external MIDI hardware and optionally forward clock/transportengine.setMidiDestinationExternal(...), engine.setExternalMidiClockEnabled(...), engine.drainExternalMidi(...); Worklet facade: onMidiOut(...)Realtime Engine
Drive the engine from a hardware/Web MIDI devicebindWebMidi(engine, ...) Browser onlyMIDI Input
Feed a live microphone into the enginebindMicrophoneInput(context, engine, ...) Browser onlyRecording and Takes
typescript
import { Project, synthPresetNames } from '@libraz/libsonare';

const project = Project.fromJson(projectJson);
const audio = project.bounceWithSynthInstrument(synthPresetNames()[0]);

bounceWithSynthInstrument(...) accepts either one instrument or an array of instruments, one per destination. Each entry may be a preset name (a "va:" routing prefix is allowed), an explicit SynthPatch, or null for the init patch.

bindWebMidi(...) and bindMicrophoneInput(...) are browser-only helpers that wire Web MIDI / a MediaStream into a live RealtimeEngine. See Realtime Engine for the engine itself.

Performance Summary ​

APILoadNotes
StreamAnalyzerReal-timePer-chunk processing, ~2ms/frame, updating BPM/key/chord estimation
MixerReal-timeScene-based block processing with automation and meters
analyze / analyzeWithProgressHeavyAll-in-one analysis pipeline
hpss / harmonic / percussiveHeavySTFT + median filtering
timeStretchHeavyPhase vocoder
pitchShiftHeavyTime stretch + resample
stft / stftDbMediumMultiple FFT operations
melSpectrogram / mfccMediumSTFT + filterbank
chromaMediumSTFT + chroma filterbank
pitchYin / pitchPyinMediumPer-frame pitch detection
resampleMediumHigh-quality resampling
detectBpm / detectKeyLightSingle result
detectBeats / detectOnsetsLightFrame-based detection
Unit conversion functionsLightPure computation
normalize / trimLightSimple processing