Skip to content

JavaScript/TypeScript API Reference

API reference for the libsonare JavaScript/TypeScript package.

Overview

libsonare provides audio analysis, mastering, mixing, and editing DSP capabilities for web applications. The npm package is the WebAssembly build. In practice, most functions expect decoded Float32Array PCM: the raw sample values after an MP3, WAV, or other file has already been decoded. For loading, the Audio.fromMemory* factories can decode encoded bytes in memory (a native WASM decoder for WAV/MP3, plus an optional browser decoder for AAC/OGG/FLAC).

For a first browser integration, keep the path narrow:

  1. call await init() once when your app starts;
  2. decode the user file into samples and keep its sampleRate;
  3. call one small function such as detectBpm(samples, sampleRate);
  4. only then move to analyze, mastering, mixing, or streaming APIs.
CategoryFunctionsUse Cases
Quick AnalysisdetectBpm, detectKey, detectBeatsDJ apps, music players, beat sync
All-In-One Analysisanalyze, analyzeWithProgressMusic production, song metadata
Audio Effectshpss, timeStretch, pitchShift, spectralEditRemixing, practice tools, region repair
FeaturesmelSpectrogram, chroma, mfccML input, visualization
MasteringmasterAudio, masteringChain, StreamingMasteringChainLUFS (Loudness Units relative to Full Scale) targets, true-peak limiting, presets, streaming chains
MixingmixStereo, Mixer, mixingScenePresetNamesStem mixing, routing, automation, meters
Editing DSPpitchCorrectToMidi, noteStretch, spectralEdit, voiceChange, StreamingRetune, RealtimeVoiceChangerVocal tuning, note edits, pitch/formant changes
Audio ClassAudio.fromBuffer, Audio.fromMemory, Audio.fromMemoryWithBrowserFallbackFile-loading helper and method-style access for common functions

Terminology

New to audio analysis? See the Glossary for explanations of terms like BPM, STFT, Chroma, and more.

Most functions take decoded PCM, not a file path

Most browser functions do not take an MP3 or WAV path; they take decoded PCM samples plus sampleRate. To go from encoded bytes to samples, either decode with the Web Audio API (AudioContext.decodeAudioData) yourself, or use the Audio.fromMemory / Audio.fromMemoryWithBrowserFallback factories below. They decode encoded bytes in memory: WAV/MP3 with the bundled WASM decoder, and AAC/OGG/FLAC through browser decoding when needed.

For a cross-binding feature map, see Feature Map. For the mastering processor registry and mixing scene format, see Mastering Processors and Mixing Scene JSON.

How To Read This Reference

Read this page in three passes:

  1. Start with Pick The Smallest API That Solves The Job and choose one function family.
  2. Read only the section for that family, then run one recipe from Examples.
  3. Come back to the full type definitions when you need exact return shapes, optional parameters, or runtime parity.

For browser apps, keep the core rule in mind: initialize WASM with await init(), decode files to PCM first, then pass Float32Array samples plus the original sampleRate.

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.

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),
});

// Still supported for existing callers
const legacyBpm = detectBpm(samples, sampleRate);

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.

Cancelling 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 cancelled call throws SONARE_ERROR_CANCELLED (error code 8) and leaves its outputs unallocated, so there is no partial result to inspect. Python takes the same predicate as cancel=.

Inputs are validated, not coerced

Node and WASM reject values they used to quietly reshape: wrong-typed repair and dynamics options, an unknown track kind, capture source, or pitch-correction mode, a negative spectrum setting, enum spellings and ordinals that are not declared, and mastering override values that are neither number nor boolean. Instance methods also throw after destroy() instead of touching a freed handle. Where you previously got a surprising default, you now get a SonareError at the call site.

Pick The Smallest API That Solves The Job

The package is broad, so start from the task rather than the function list:

You needStart withWhy
One tempo/key/beat value for a trackdetectBpm, detectKey, detectBeatsFast, direct answers without building the all-in-one analysis object
Metadata for a whole songanalyze or the focused analyze* helpersanalyze gives the common summary; focused helpers expose more detail
A live visualizer or updating BPM/key/chord UIStreamAnalyzerProcesses small audio blocks and lets the UI read the newest frames
Browser mastering or delivery previewmasterAudio*, masteringChain*, StreamingMasteringChainUse presets first, then move to named processors when you need control
Stem balance, sends, buses, or metersmixStereo or MixerOne-shot mix first; persistent scene mixer when routing matters
Vocal/note/spectral editspitchCorrectToMidi, noteStretch, spectralEdit, voiceChange, StreamingRetune, RealtimeVoiceChangerEditing DSP changes the signal rather than analyzing it
Room decay, clarity, equivalent-room estimates, or generated room characteranalyzeImpulseResponse, detectAcoustic, estimateRoom, synthesizeRir, roomMorphThese describe or apply the recording space, not the music

Installation

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

Import

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

Initialization

init(options?)

Initialize the WASM module. Must be called before any analysis functions.

typescript
async function init(options?: {
  locateFile?: (path: string, prefix: string) => string;
}): Promise<void>

Example:

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

// Basic initialization
await init();

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

isInitialized()

Check if the module is initialized.

typescript
function isInitialized(): boolean

version()

Get the library version.

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

capabilities()

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

typescript
function capabilities(): {
  version: string;
  abi: { project: number; engine: number };
  platform: string;
  features: { mastering: boolean; mixing: boolean; fx: boolean; ffmpeg: boolean };
  decode: { builtin: string[]; ffmpeg: string[] };
  simd: string;
  hardwareConcurrency: number;
}

Branch on features instead of guessing: a build without mixing has no Mixer, and decode.builtin tells you which formats the module can open before you ask the browser to fall back.

capabilityCatalog()

Return 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.

min / max / default are always null

The registry publishes no generic bounds interface, so every parameter reports min, max and default as null rather than a guessed value. Use the catalog to discover which processors and parameters a build exposes, their types, units and realtime-safety — not to size a slider. For value ranges, consult the processor's own reference page.

typescript
function capabilityCatalog(): {
  version: string;
  abi: { project: number; engine: number };
  processors: Array<{
    id: string;
    kind: 'realtime' | 'offline' | 'pair';
    realtimeInsertable: boolean;
    stereoOnly: boolean;
    latencySamples: number;
    tailSamples: number;
    /** Coarse realtime work estimate; null exactly when the processor is not insertable. */
    realtimeCost: 'low' | 'moderate' | 'high' | null;
    channelPolicy: 'multichannel' | 'stereoPairOnly' | 'perChannel' | 'passthrough';
    category: string;
    params: Array<{
      name: string;
      id: number;
      rtSafe: boolean;
      type: 'boolean' | 'number';
      min: number | null;
      max: number | null;
      default: boolean | number | null;
      unit: string | null;
    }>;
  }>;
  presets: {
    mastering: string[];
    synth: string[];
    mixingScene: string[];
    voiceChanger: string[];
  };
}

This is what you build a generic parameter UI from: every slider's range and default comes from the catalog rather than from a table you maintain by hand. A bound the core does not know is reported as an explicit null — treat that as "unbounded/unknown", not as zero.

abiVersion()

Returns the aggregate native ABI version across the C POD surfaces. 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, limiterIspCeilingDbtp, and so on). The equivalent C and Python POD fields remain snake_case.

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.

Analysis Functions

detectBpm(samples, sampleRate)

Detect BPM (tempo) from audio samples.

Use Cases

  • DJ Software: Match tempos between tracks for seamless mixing
  • Music Players: Display tempo information, auto-generate playlists by tempo
  • Fitness Apps: Match music to workout intensity
  • Beat Sync: Synchronize visualizations or animations to music
typescript
function detectBpm(samples: Float32Array, sampleRate?: number): number
ParameterTypeDescription
samplesFloat32ArrayMono audio samples (range -1.0 to 1.0)
sampleRate?numberSample rate in Hz (default: 22050; e.g., 44100)

Always pass the real sample rate

Although sampleRate is optional here (defaulting to 22050 Hz), decoded browser audio is almost always 44100 or 48000 Hz. Pass the buffer's actual audioBuffer.sampleRate, or the reported BPM will be wrong. The same holds for detectKey, detectBeats, and analyze: their sampleRate is optional with the same 22050 Hz default, so pass the real rate to those as well.

Returns: Detected BPM as a number.

typescript
const bpm = detectBpm(samples, sampleRate);
console.log(`BPM: ${bpm}`);  // "BPM: 120"

detectKey(samples, sampleRate)

Detect musical key from audio samples. Returns the root note (C, D, E...) and mode (major/minor).

Use Cases

  • Harmonic Mixing: DJs match keys for smooth transitions (Camelot wheel)
  • Transposition: Suggest key changes to match vocal range
  • Music Recommendation: Find songs in compatible keys
  • Practice Tools: Display key for musicians to play along
typescript
function detectKey(samples: Float32Array, sampleRate?: number): Key  // sampleRate default: 22050

Returns: Key object

typescript
interface Key {
  root: PitchClass;      // 0-11 (C=0, B=11)
  mode: Mode;            // Major, Minor, or modal value; see Mode enum
  confidence: number;    // 0.0 to 1.0
  name: string;          // "C major", "A minor"
  shortName: string;     // "C", "Am"
}

const KeyProfile = {
  KrumhanslSchmuckler: 0,
  Temperley: 1,
  Shaath: 2,
  FaraldoEDMT: 3,
  FaraldoEDMA: 4,
  FaraldoEDMM: 5,
  BellmanBudge: 6,
} as const;
typescript
const key = detectKey(samples, sampleRate);
console.log(`Key: ${key.name}`);        // "C major"
console.log(`Confidence: ${(key.confidence * 100).toFixed(1)}%`);

detectBeats(samples, sampleRate)

Detect beat times from audio samples. Returns exact timestamps of each beat.

Use Cases

  • Music Visualization: Trigger effects on each beat
  • Rhythm Games: Generate note charts from audio
  • Video Editing: Auto-cut to the beat
  • Loop Creation: Find perfect loop points
typescript
function detectBeats(samples: Float32Array, sampleRate?: number): Float32Array  // sampleRate default: 22050

Returns: Float32Array of beat times in seconds

typescript
const beats = detectBeats(samples, sampleRate);
console.log(`Found ${beats.length} beats`);
for (let i = 0; i < beats.length; i++) {
  console.log(`Beat ${i + 1}: ${beats[i].toFixed(3)}s`);
}

detectOnsets(samples, sampleRate)

Detect onset times (note attacks) from audio samples. More granular than beats - captures every note/hit.

Use Cases

  • Drum Transcription: Detect individual drum hits
  • Audio-to-MIDI: Convert audio to note events
  • Sample Slicing: Automatically segment audio at transients
typescript
function detectOnsets(samples: Float32Array, sampleRate?: number): Float32Array  // sampleRate default: 22050

Returns: Float32Array of onset times in seconds

analyze(samples, sampleRate) Heavy

Perform the all-in-one music analysis. Returns BPM, key, beats, chords, sections, timbre, and more.

Use Cases

  • Music Library Management: Auto-tag songs with metadata
  • Music Production: Analyze reference tracks
  • DJ Preparation: Get all track info at once
  • Music Education: Study song structure

Performance

This is the heaviest API. For long audio files (>3 minutes), consider using analyzeWithProgress to show progress, or analyze only relevant segments.

typescript
function analyze(samples: Float32Array, sampleRate?: number): AnalysisResult  // sampleRate default: 22050

Returns: Complete AnalysisResult. A single analyze() call returns the full result — chords, sections, timbre, dynamics, rhythm, melody, form, and per-beat strength — on every binding, so you rarely need the focused helpers unless you only want one field.

typescript
const result = analyze(samples, sampleRate);
console.log(`BPM: ${result.bpm}`);
console.log(`Key: ${result.key.name}`);
console.log(`Chords: ${result.chords.length}`);
console.log(`Form: ${result.form}`);  // e.g., "IABABCO"

analyzeWithProgress(samples, sampleRate, onProgress) Heavy

Perform the same all-in-one analysis with progress reporting.

typescript
function analyzeWithProgress(
  samples: Float32Array,
  sampleRate: number | undefined,  // undefined applies the 22050 default
  onProgress: (progress: number, stage: string) => void
): AnalysisResult

sampleRate is positional (before the callback) but accepts undefined, which uses the same 22050 Hz default as analyze. Pass the buffer's real rate.

Progress Stages:

StageDescriptionProgress
"features"Feature precomputation0.0
"bpm"BPM detection0.15
"key"Key detection0.15
"beats"Beat tracking0.25
"chords"Chord recognition0.40
"sections"Section detection0.55
"timbre"Timbre analysis0.70
"dynamics"Dynamics analysis0.80
"rhythm"Rhythm analysis0.90
"melody"Melody contour extraction0.95
"complete"Finished1.0
typescript
const result = analyzeWithProgress(samples, sampleRate, (progress, stage) => {
  console.log(`${stage}: ${Math.round(progress * 100)}%`);
});

Focused analysis helpers

One call is usually enough

analyze() already returns chords, sections, timbre, dynamics, rhythm, melody, form, and per-beat strength. Reach for a focused helper only when you want a single field or need options the high-level call hides.

Use the focused helpers when the default analyze(...) result is either too broad or not detailed enough. They share the same mono Float32Array input model but expose options that are hidden by the high-level call.

TaskFunctionNotes
Downbeat/bar startsdetectDownbeats(samples, sampleRate)Returns seconds for likely bar starts. Pair with detectBeats for grid displays.
Ranked key candidatesdetectKeyCandidates(samples, sampleRate, options?)Useful when the top key is ambiguous or when you want profile/mode filtering.
Detailed tempo candidatesanalyzeBpm(samples, sampleRate, ...)Returns the best BPM plus alternate candidates and tempo evidence.
Rhythm characteranalyzeRhythm(samples, sampleRate, ...)Reports groove, syncopation, and regularity style features.
DynamicsanalyzeDynamics(samples, sampleRate, ...)Dynamic range, loudness range, crest factor, and compression flag.
TimbreanalyzeTimbre(samples, sampleRate, ...)Brightness, warmth, density, roughness, and complexity.
ChordsdetectChords(samples, sampleRate, options?)Returns { chords } of chord segments; options include HMM smoothing, key context, inversions, and chromaMethod: 'stft' | 'nnls'.
SectionsanalyzeSections(samples, sampleRate, ...)Song-structure sections such as intro, verse, chorus, bridge, and outro. Long inputs keep accurate start / end times even when the internal boundary grid is pooled.
MelodyanalyzeMelody(samples, sampleRate, ...)Monophonic melody contour based on pitch tracking.
typescript
const keys = detectKeyCandidates(samples, sampleRate, {
  modes: [Mode.Major, Mode.Minor],
  profile: 'krumhansl',
  genreHint: 'pop',
});

const { chords } = detectChords(samples, sampleRate, {
  useHmm: true,
  useKeyContext: true,
  keyRoot: keys[0].key.root,
  keyMode: keys[0].key.mode,
  chromaMethod: 'nnls',
});

const sections = analyzeSections(samples, sampleRate);

chordFunctionalAnalysis(samples, keyRoot, keyMode, sampleRate?, options?)

Functional (Roman-numeral) harmonic analysis of the detected chord progression, relative to the given key. It runs chord detection internally and labels each detected chord, so pass the same keyRoot/keyMode you get from detectKey(...) and the same options you would give detectChords(...).

typescript
function chordFunctionalAnalysis(
  samples: Float32Array,
  keyRoot: PitchClass,
  keyMode?: Mode,
  sampleRate?: number,
  options?: ChordDetectionOptions,
): string[]   // one Roman-numeral label per detected chord, e.g. ["I", "IV", "V", "vi"]
typescript
const key = detectKey(samples, sampleRate);
const roman = chordFunctionalAnalysis(samples, key.root, key.mode, sampleRate);
console.log(roman);  // e.g. ["I", "IV", "V", "vi"]

detectKey(...) and detectKeyCandidates(...) accept the same KeyDetectionOptions includes:

Option groupValues
Controlsmodes, profile, genreHint, useHpss, loudnessWeighted, highPassHz
Profile namesks, krumhansl, temperley, shaath, keyfinder, faraldo-edmt / edmt, faraldo-edma / edma, faraldo-edmm / edmm, bellman-budge / bellman
Genre hintsauto, edm, electronic, dance, pop, classical, jazz

Room Acoustics

These functions describe or apply the recording space rather than the song itself.

GoalUse
Measure a clean impulse responseanalyzeImpulseResponse(...)
Estimate room decay from ordinary audiodetectAcoustic(...)
Fit a practical room model from audioestimateRoom(...)
Create a mono room impulse response from dimensionssynthesizeRir(...)
Add a target-room character as an effectroomMorph(...)

RIR and room morphing

RIR means room impulse response: samples that describe how a room reacts to a short sound. roomMorph(...) is a creative effect, not dereverberation.

typescript
const ir = analyzeImpulseResponse(impulseResponseSamples, sampleRate, 6, 30);
console.log(ir.rt60, ir.edt, ir.c50, ir.c80, ir.confidence);

const blind = detectAcoustic(roomRecording, sampleRate, {
  nOctaveBands: 6,
  nThirdOctaveSubbands: 24,
  minDecayDb: 30,
  noiseFloorMarginDb: 10,
});
console.log(blind.isBlind, blind.rt60Bands);

const estimate = estimateRoom(roomRecording, sampleRate, {
  referenceAbsorption: 0.15,
  nOctaveBands: 6,
});
console.log(estimate.volume, estimate.length, estimate.width, estimate.height);
console.log(estimate.drrDb, estimate.confidence, estimate.absorptionBands);

const rir = synthesizeRir({ lengthM: 7, widthM: 5, heightM: 3, absorption: 0.2 });
console.log(rir.sampleRate, rir.rir.length, rir.hasError);

const morphed = roomMorph(samples, sampleRate, { lengthM: 12, widthM: 9, heightM: 4, wet: 0.6 });

analyzeImpulseResponse(samples, sampleRate?, nOctaveBands?, minDecayDb?) uses minDecayDb to set the decay-fit threshold (default 30).

See Room Acoustics for how to interpret RT60, EDT, C50, C80, D50, band arrays, room estimates, generated RIRs, and confidence.

Audio Effects

hpss(samples, sampleRate, kernelHarmonic?, kernelPercussive?, nFft?, hopLength?, hardMask?) Heavy

Harmonic-Percussive Source Separation. Splits audio into tonal (vocals, synths) and transient (drums) components.

Use Cases

  • Remixing: Isolate drums or remove them
  • Karaoke: Extract instrumental by removing vocals (use harmonic)
  • Better Analysis: Use harmonic-only for cleaner chord detection
  • Drum Extraction: Get just the percussion for sampling
HPSS · FULL MIXIDLE
HPSS — splitting the tune from the drums

On a spectrogram, sustained pitched notes draw horizontal ridges while drum hits draw vertical streaks. HPSS exploits exactly that: median-filtering along time keeps the horizontal (harmonic) content, along frequency keeps the vertical (percussive) content. Switch the view — Full shows both, Harmonic keeps the ridges (the chords and bass, drums gone), Percussive keeps the streaks (the kit, tune gone) — and press play to hear each layer on its own. Separating them first often cleans up downstream beat or pitch tracking.

Layer

Performance

HPSS requires STFT (short-time Fourier transform) computation and median filtering. Processing time scales with audio duration.

typescript
function hpss(
  samples: Float32Array,
  sampleRate?: number,        // default: 22050
  kernelHarmonic?: number,    // default: 31
  kernelPercussive?: number,   // default: 31
  nFft?: number,               // default: 2048
  hopLength?: number,          // default: 512
  hardMask?: boolean           // default: false
): HpssResult

interface HpssResult {
  harmonic: Float32Array;
  percussive: Float32Array;
  sampleRate: number;
}

hpssWithResidual(...) accepts the same kernel, STFT, and mask options and also returns the residual component that is not classified as harmonic or percussive.

typescript
function hpssWithResidual(
  samples: Float32Array,
  sampleRate?: number,
  kernelHarmonic?: number,
  kernelPercussive?: number,
  nFft?: number,               // default: 2048
  hopLength?: number,          // default: 512
  hardMask?: boolean           // default: false
): HpssWithResidualResult

harmonic(samples, sampleRate) Heavy

Extract harmonic component from audio.

typescript
function harmonic(samples: Float32Array, sampleRate?: number): Float32Array  // sampleRate default: 22050

percussive(samples, sampleRate) Heavy

Extract percussive component from audio.

typescript
function percussive(samples: Float32Array, sampleRate?: number): Float32Array  // sampleRate default: 22050

timeStretch(samples, sampleRate, rate, nFft?, hopLength?) Heavy

Time-stretch audio without changing pitch. Rate < 1.0 = slower, > 1.0 = faster.

Use Cases

  • Practice Tools: Slow down music to learn difficult passages
  • DJ Mixing: Match tempos between tracks
  • Podcast Editing: Speed up/slow down speech
  • Music Production: Fit samples to project tempo
PARAM SWEEP · TIME STRETCHIDLE
Time stretch — changing length, not pitch

Time stretching is pitch shift's exact opposite: it changes how long the audio lasts while leaving the pitch alone. Drag the rate and the drum hits spread out or bunch up — the waveform fills more or less of the panel — but the spectrum below barely moves. Below 1.0 the clip slows down and grows; above 1.0 it speeds up and shrinks. Every render is peak-normalized, so a fast rate does not simply arrive quieter than a slow one; the level you hear is set by the demo, not by the stretch. Press play to hear the groove change tempo with no chipmunk effect.

Rate
1 ×

Performance

Uses phase vocoder algorithm. Processing time increases with audio duration.

typescript
function timeStretch(
  samples: Float32Array,
  sampleRate: number,
  rate: number,      // 0.5 = half speed, 2.0 = double speed
  nFft?: number,     // default: 2048
  hopLength?: number // default: 512
): Float32Array

pitchShift(samples, sampleRate, semitones, nFft?, hopLength?) Heavy

Pitch-shift audio without changing duration. Measured in semitones (+12 = one octave up).

Use Cases

  • Key Matching: Transpose songs to match for mixing
  • Vocal Tuning: Correct or adjust vocal pitch
  • Creative Effects: Create harmonies, chipmunk/deep voice effects
  • Instrument Practice: Transpose to comfortable key

Performance

Combines time stretching and resampling. Processing time increases with audio duration.

typescript
function pitchShift(
  samples: Float32Array,
  sampleRate: number,
  semitones: number,   // +12 = one octave up
  nFft?: number,        // default: 2048
  hopLength?: number    // default: 512
): Float32Array

Editing DSP

These functions change the signal itself rather than only analyzing it. They are also available as Audio instance methods, where the stored sampleRate is used automatically.

typescript
function pitchCorrectToMidi(
  samples: Float32Array,
  sampleRate: number,
  currentMidi: number,
  targetMidi: number,
): Float32Array

// Retune a tracked pitch contour to a fixed target note, frame by frame.
// f0Hz is a per-frame f0 track (e.g. from pitchYin/pitchPyin), aligned to
// hopLength. Pass the matching voiced/voicedProb arrays to skip unvoiced
// frames; unvoiced or NaN frames are left untouched.
function pitchCorrectToMidiTimevarying(
  samples: Float32Array,
  f0Hz: Float32Array,
  targetMidi: number,
  sampleRate: number,
  hopLength: number,
  voiced?: VoicedFlags,
  voicedProb?: Float32Array,
): Float32Array

// Snap a tracked pitch contour to a musical scale (auto-tune) or a fixed note.
// mode 'scale' pulls every voiced frame to the nearest enabled scale tone;
// mode 'midi' (the default) behaves like pitchCorrectToMidiTimevarying.
function pitchCorrectTimevarying(
  samples: Float32Array,
  f0Hz: Float32Array,       // per-frame f0 track aligned to hopLength
  sampleRate?: number,      // default 22050
  hopLength?: number,       // default 512
  options?: PitchCorrectOptions,
): Float32Array

interface PitchCorrectOptions {
  mode?: 'midi' | 'scale';         // default 'midi'
  targetMidi?: number;             // fixed note for 'midi' mode; default 69 (A4)
  scaleRoot?: number;              // scale root pitch class 0-11; default 0 (C)
  scaleModeMask?: number;          // 12-bit degree mask; default C major
  referenceMidi?: number;          // scale-grid anchor; default 69 (A4)
  retuneAmount?: number;           // 0 = bypass, 1 = full snap; default 1
  maxCorrectionSemitones?: number; // per-frame clamp in semitones; default 12
  retuneSpeedMs?: number;          // glide time constant; default 50
  vibratoThresholdCents?: number;  // corrections below this are bypassed; default 20
  voiced?: VoicedFlags;            // per-frame voiced flags (truthy / non-zero = voiced)
  voicedProb?: Float32Array;       // per-frame voicing probability 0-1
}

// Per-frame voicing decision, one entry per f0Hz frame.
type VoicedFlags =
  | Int32Array
  | Uint8Array
  | Float32Array
  | readonly number[]
  | readonly boolean[];

VoicedFlags is the accepted shape of the voiced argument and of PitchCorrectOptions.voiced. It covers what the analysis side hands back: PitchResult.voicedFlag is a boolean[], so a pitch track goes straight into pitch correction with no conversion step.

typescript
const pitch = pitchPyin(samples, sampleRate);
const tuned = pitchCorrectToMidiTimevarying(
  samples,
  pitch.f0,
  69,
  sampleRate,
  512,
  pitch.voicedFlag,   // boolean[] accepted as-is
  pitch.voicedProb,
);

voiced and voicedProb must each be the same length as f0Hz. A mismatch throws a RangeError ('pitchCorrectToMidiTimevarying: voiced length must match f0Hz length'), not a SonareError, so isSonareError does not catch it.

typescript
function noteStretch(
  samples: Float32Array,
  sampleRate: number,
  options?: {
    onsetSample?: number,    // note onset position in samples
    offsetSample?: number,   // note offset position in samples
    stretchRatio?: number,   // >1 lengthens the region, <1 shortens it
  },
): Float32Array

// Move a note region to a new onset without changing its duration
// (complements noteStretch, which changes duration but not onset).
function noteMove(
  samples: Float32Array,
  sampleRate?: number,
  options?: {
    onsetSample?: number,        // note onset position in samples
    offsetSample?: number,       // note offset position in samples; defaults to the input length
    targetOnsetSample?: number,  // where the region's onset moves to
  },
): Float32Array

Audio.noteStretch(options?) and Audio.noteMove(options?) are the equivalent instance methods on an Audio wrapper (sample rate taken from the instance).

typescript
function spectralEdit(
  samples: Float32Array,
  sampleRate: number,
  ops?: Array<{
    startSample?: number;
    endSample?: number;
    lowHz?: number;
    highHz?: number;
    gainDb?: number;
    mode?: 'gain' | 'attenuate' | 'mute' | 'heal';
  }>,
  options?: {
    nFft?: number;
    hopLength?: number;
    window?: 'hann' | 'hamming' | 'blackman' | 'rectangular';
    healRadiusFrames?: number;
  },
): Float32Array

function voiceChange(
  samples: Float32Array,
  sampleRate?: number,        // default: 22050
  options?: {
    pitchSemitones?: number,  // negative shifts down; default 0
    formantFactor?: number,   // >1 brightens, <1 darkens; default 1.0
  },
): Float32Array

CLI equivalents:

bash
sonare pitch-correct vocal.wav --current-midi 68.7 --target-midi 69 -o corrected.wav
sonare note-stretch take.wav --onset 12000 --offset 24000 --ratio 1.25 -o held.wav
sonare voice-change vocal.wav --pitch-semitones 3 --formant-factor 1.05 -o voice.wav

pitchCorrectTimevarying(...) is the scale-snap auto-tune path; see Editing DSP for the scale masks, mode, and retune-feel options in full. See Spectral Editing for region examples and option notes.

normalize(samples, sampleRate, targetDb?, mode?)

Normalize audio to the requested target level. mode is 'peak' by default; use 'rms' to target RMS level instead.

typescript
function normalize(
  samples: Float32Array,
  sampleRate: number,
  targetDb?: number,        // default: 0.0 (full scale)
  mode?: 'peak' | 'rms'     // default: 'peak'
): Float32Array

trim(samples, sampleRate, thresholdDb?, frameLength?, hopLength?)

Trim silence from beginning and end of audio.

typescript
function trim(
  samples: Float32Array,
  sampleRate: number,
  thresholdDb?: number,   // default: -60.0
  frameLength?: number,   // default: 2048
  hopLength?: number      // default: 512
): Float32Array

This is the simple Audio-level threshold trim. For librosa-compatible frame/RMS silence detection that also returns the original start/end sample range, use trimSilence(...) below.

Feature Extraction

stft(samples, sampleRate, nFft?, hopLength?) Medium

Compute Short-Time Fourier Transform.

typescript
function stft(
  samples: Float32Array,
  sampleRate?: number, // default: 22050
  nFft?: number,      // default: 2048
  hopLength?: number  // default: 512
): StftResult

interface StftResult {
  nBins: number;
  nFrames: number;
  nFft: number;
  hopLength: number;
  sampleRate: number;
  magnitude: Float32Array;
  power: Float32Array;
}

stftDb(samples, sampleRate, nFft?, hopLength?) Medium

Compute STFT and return in dB scale.

typescript
function stftDb(
  samples: Float32Array,
  sampleRate?: number, // default: 22050
  nFft?: number,      // default: 2048
  hopLength?: number  // default: 512
): { nBins: number; nFrames: number; db: Float32Array }

melSpectrogram(samples, sampleRate, nFft?, hopLength?, nMels?) Medium

Compute Mel spectrogram. Frequency representation that matches human pitch perception.

typescript
function melSpectrogram(
  samples: Float32Array,
  sampleRate?: number, // default: 22050
  nFft?: number,      // default: 2048
  hopLength?: number, // default: 512
  nMels?: number,     // default: 128
  fmin?: number,      // default: 0 (librosa default)
  fmax?: number,      // default: 0 = sampleRate / 2
  htk?: boolean       // default: false = Slaney formula; true = HTK
): MelSpectrogramResult

interface MelSpectrogramResult {
  nMels: number;
  nFrames: number;
  sampleRate: number;
  hopLength: number;
  power: Float32Array;
  db: Float32Array;
}

mfcc(samples, sampleRate, nFft?, hopLength?, nMels?, nMfcc?) Medium

Compute MFCC (Mel-Frequency Cepstral Coefficients). Compact representation of spectral envelope.

typescript
function mfcc(
  samples: Float32Array,
  sampleRate?: number, // default: 22050
  nFft?: number,      // default: 2048
  hopLength?: number, // default: 512
  nMels?: number,     // default: 128
  nMfcc?: number,     // default: 20
  fmin?: number,      // default: 0 (librosa default)
  fmax?: number,      // default: 0 = sampleRate / 2
  htk?: boolean,      // default: false = Slaney formula; true = HTK
  lifter?: number     // default: 0 = no liftering
): MfccResult

interface MfccResult {
  nMfcc: number;
  nFrames: number;
  coefficients: Float32Array;
}

Set fmin/fmax to bound the Mel band edges, and pass htk: true to use the HTK Mel formula instead of Slaney. lifter matches librosa's lifter argument (cepstral/sinusoidal liftering that de-emphasizes higher cepstral coefficients); 0 disables liftering. The inverse helpers (melToStft, melToAudio, mfccToAudio) take matching fmin/fmax/htk arguments, so a round-trip stays consistent when you keep the same values on both sides.

chroma(samples, sampleRate, nFft?, hopLength?) Medium

Compute chromagram (pitch class distribution). Maps all frequencies to 12 pitch classes (C, C#, D, ..., B).

CHROMA · PITCH CLASSIDLE
Chromagram — harmony folded into 12 bins

Every frequency is folded onto one of twelve pitch classes, so octave is forgotten and only the harmony remains. This clip walks a C–Am–F–G turnaround: watch the lit rows shift as each chord changes, then play to follow the progression.

typescript
function chroma(
  samples: Float32Array,
  sampleRate?: number, // default: 22050
  nFft?: number,      // default: 2048
  hopLength?: number  // default: 512
): ChromaResult

interface ChromaResult {
  nChroma: number;        // 12
  nFrames: number;
  sampleRate: number;
  hopLength: number;
  features: Float32Array;
  meanEnergy: number[];   // [12] per pitch class
}

Spectral Features

typescript
// Spectral centroid (center of mass) in Hz
function spectralCentroid(
  samples: Float32Array,
  sampleRate?: number,  // default: 22050
  nFft?: number,
  hopLength?: number
): Float32Array

// Spectral bandwidth in Hz
function spectralBandwidth(
  samples: Float32Array,
  sampleRate?: number,  // default: 22050
  nFft?: number,
  hopLength?: number,
  p?: number             // Minkowski exponent, default: 2
): Float32Array

// Spectral rolloff frequency in Hz
function spectralRolloff(
  samples: Float32Array,
  sampleRate?: number,  // default: 22050
  nFft?: number,
  hopLength?: number,
  rollPercent?: number  // default: 0.85
): Float32Array

// Spectral flatness (0 = tonal, 1 = noise-like)
function spectralFlatness(
  samples: Float32Array,
  sampleRate?: number,  // default: 22050
  nFft?: number,
  hopLength?: number
): Float32Array

// Spectral contrast matrix, shape (nBands + 1) x nFrames
function spectralContrast(
  samples: Float32Array,
  sampleRate?: number,
  nFft?: number,
  hopLength?: number,
  nBands?: number,
  fmin?: number,
  quantile?: number
): Matrix2dResult

// Per-frame polynomial spectral coefficients, shape (order + 1) x nFrames
function polyFeatures(
  samples: Float32Array,
  sampleRate?: number,
  nFft?: number,
  hopLength?: number,
  order?: number
): Matrix2dResult

// Zero crossing rate
function zeroCrossingRate(
  samples: Float32Array,
  sampleRate?: number,  // default: 22050
  frameLength?: number,
  hopLength?: number
): Float32Array

// Sample indices where the waveform crosses zero
function zeroCrossings(
  samples: Float32Array,
  threshold?: number,
  refMagnitude?: boolean,
  pad?: boolean,
  zeroPos?: boolean
): Int32Array

// RMS energy
function rmsEnergy(
  samples: Float32Array,
  sampleRate?: number,  // default: 22050
  frameLength?: number,
  hopLength?: number
): Float32Array

Waveform Peaks WASM/Node

Per-channel min/max buckets for drawing a waveform overview without shipping the full sample array to the UI. samplesPerBucket sets the bucket width (default 512); waveformPeakPyramid returns one report per zoom level.

typescript
function waveformPeaks(
  samples: Float32Array,   // interleaved when channels > 1
  channels: number,
  options?: { samplesPerBucket?: number },  // default 512
): WaveformPeaksReport

function waveformPeakPyramid(
  samples: Float32Array,
  channels: number,
  options?: { samplesPerBucketLevels?: number[] },  // default [512, 1024, 2048, 4096]
): WaveformPeaksReport[]

interface WaveformPeaksReport {
  min: Float32Array;        // channel-major
  max: Float32Array;        // channel-major
  channels: number;
  bucketCount: number;
  samplesPerBucket: number;
}

CQT, VQT, NNLS chroma, inverse features, and loudness

These functions are not just "more features"; they solve different modeling problems:

NeedUseWhy
Log-frequency pitch representationcqt(...), pseudoCqt(...), hybridCqt(...)Constant-Q bins align well with musical pitch over octaves; pseudo/hybrid variants trade accuracy and speed across bins.
Variable bandwidth pitch representationvqt(...)Like CQT, but with a bandwidth offset for low-frequency stability.
Chord-friendly chromachromaCqt(...), nnlsChroma(...), chromaCens(...), bassChroma(...)Constant-Q, NNLS, CENS, and low-register chroma variants can be cleaner for chord or bass-register work than plain STFT chroma.
Spectral shape detailspectralContrast(...), polyFeatures(...), zeroCrossings(...), onsetStrengthMulti(...)Librosa-compatible contrast bands, polynomial coefficients, zero-crossing indices, and multi-band onset strength.
Pitch/tuning offsetpitchTuning(...), estimateTuning(...)Estimate tuning in fractions of a bin from detected frequencies or directly from audio.
Decomposition and remixingdecompose(...), decomposeWithInit(...), nnFilter(...), remix(...), phaseVocoder(...), hpssWithResidual(...)NMF factorization, selectable NMF initialization, nearest-neighbor filtering, interval remixing, time scaling, and HPSS residual output.
Reconstruct approximate audio/featuresmelToStft, melToAudio, mfccToMel, mfccToAudio, cqtToAudio, vqtToAudioGriffin-Lim based inverse paths for visualization, debugging, and feature round-trips. CQT/VQT inputs are magnitude matrices.
Delivery loudness measurementslufs, lufsInterleaved, momentaryLufs, shortTermLufs, ebur128LoudnessRangeITU-R BS.1770 / EBU R128 style loudness values, including multichannel integrated loudness and LRA (loudness range — how much the loudness varies over the program).
typescript
const cqtResult = cqt(samples, sampleRate, 512, 32.7, 84, 12);
const vqtResult = vqt(samples, sampleRate, 512, 32.7, 84, 12, -1);
const pseudo = pseudoCqt(samples, sampleRate);
const hybrid = hybridCqt(samples, sampleRate);
const cqtChroma = chromaCqt(samples, sampleRate);
const nnls = nnlsChroma(samples, sampleRate, { hopLength: 512 });
const cens = chromaCens(samples, sampleRate);
const bass = bassChroma(samples, sampleRate);
const loudness = lufs(samples, sampleRate);

const contrast = spectralContrast(samples, sampleRate);
const poly = polyFeatures(samples, sampleRate);
const crossings = zeroCrossings(samples);
const onsetBands = onsetStrengthMulti(samples, sampleRate);
const tuning = estimateTuning(samples, sampleRate);
const offset = pitchTuning(pitch.f0);
const { w, h } = decompose(spectrogram, nFeatures, nFrames, 8);
const warmStarted = decomposeWithInit(spectrogram, nFeatures, nFrames, 8, 50, 2.0, 'nndsvd');
const filtered = nnFilter(spectrogram, nFeatures, nFrames);
const remixed = remix(samples, Int32Array.from([0, sampleRate, sampleRate, 2 * sampleRate]));
const stretched = phaseVocoder(samples, sampleRate, 1.5);
const hpssResidual = hpssWithResidual(samples, sampleRate);
const multichannel = lufsInterleaved(interleavedStereo, 2, sampleRate);
const lra = ebur128LoudnessRange(samples, sampleRate);
const reconstructed = melToAudio(mel.power, mel.nMels, mel.nFrames, sampleRate);
const cqtPreview = cqtToAudio(cqtResult.magnitude, cqtResult.nBins, cqtResult.nFrames, sampleRate, 512, 32.7, 12);
const vqtPreview = vqtToAudio(vqtResult.magnitude, vqtResult.nBins, vqtResult.nFrames, sampleRate, 512, 32.7, 12, 0, 32);

chromaCqt(samples, sampleRate?, hopLength?, nChroma?) is the direct librosa.feature.chroma_cqt equivalent (log-frequency / constant-Q pitch folding), while nnlsChroma(samples, sampleRate?, options?) is a distinct note-activation chroma built on NNLS (non-negative least squares) that suppresses harmonic leakage — often cleaner for chord or bass-register work. Its options.hopLength defaults to 512.

Closest CLI equivalents from the source-built C++ CLI:

bash
sonare cqt song.wav
sonare vqt song.wav
sonare nnls-chroma song.wav
sonare lufs song.wav --json
sonare mel-to-audio song.wav -o mel-preview.wav

For reconstruction limits and parameter notes, see Inverse Features. For librosa-parity details, see librosa Compatibility.

Pitch Detection Medium

typescript
// YIN algorithm
function pitchYin(
  samples: Float32Array,
  sampleRate?: number,   // default: 22050
  frameLength?: number,  // default: 2048
  hopLength?: number,    // default: 512
  fmin?: number,         // default: 65 Hz
  fmax?: number,         // default: 2093 Hz
  threshold?: number,    // default: 0.1
  fillNa?: boolean       // retained for compatibility; YIN always returns finite f0
): PitchResult

// pYIN algorithm (probabilistic YIN with HMM smoothing)
function pitchPyin(
  samples: Float32Array,
  sampleRate?: number,   // default: 22050
  frameLength?: number,
  hopLength?: number,
  fmin?: number,
  fmax?: number,
  threshold?: number,
  fillNa?: boolean       // default: false; true writes 0 for unvoiced f0 frames
): PitchResult

interface PitchResult {
  f0: Float32Array;
  voicedProb: Float32Array;
  voicedFlag: boolean[];
  nFrames: number;
  medianF0: number;
  meanF0: number;
}

YIN returns a finite estimate for every frame, including frames marked unvoiced by voicedFlag.

pYIN keeps NaN for unvoiced frames by default. Set its fillNa: true when a downstream numeric pipeline should use 0 instead.

Unit Conversion

These functions are lightweight and fast.

typescript
// Hz <-> Mel (Slaney formula)
function hzToMel(hz: number): number
function melToHz(mel: number): number

// Hz <-> MIDI note number (A4 = 440 Hz = 69)
function hzToMidi(hz: number): number
function midiToHz(midi: number): number

// Hz <-> Note name
function hzToNote(hz: number): string      // "A4", "C#5"
function noteToHz(note: string): number

// Time <-> Frames
function framesToTime(frames: number, sr: number, hopLength: number): number
function timeToFrames(time: number, sr: number, hopLength: number): number

// Frames <-> Samples (librosa.frames_to_samples / samples_to_frames)
function framesToSamples(frames: number, hopLength?: number, nFft?: number): number
function samplesToFrames(samples: number, hopLength?: number, nFft?: number): number

// dB conversions (vectorised)
function powerToDb(values: Float32Array, ref?: number, amin?: number, topDb?: number): Float32Array
function amplitudeToDb(values: Float32Array, ref?: number, amin?: number, topDb?: number): Float32Array
function dbToPower(values: Float32Array, ref?: number): Float32Array
function dbToAmplitude(values: Float32Array, ref?: number): Float32Array

Metering

Standalone meters report level, dynamics, and stereo-image statistics from a decoded buffer. They are independent of the mastering chain and the streaming engine: pass a Float32Array or a left/right pair and get back a value or report. Every function accepts optional options with a validate flag (default true); set validate: false on hot paths to skip the O(n) JavaScript-side NaN/Inf pre-scan. It is not a way to push non-finite samples into the core — the native layer always re-validates, so a NaN/Inf buffer still throws, just with a generic native message instead of one naming the offending index. Empty-buffer checks always run.

Single-channel level meters

typescript
// Sample peak, dBFS
function meteringPeakDb(samples: Float32Array, sampleRate?: number, options?: ValidateOptions): number
// RMS level, dBFS
function meteringRmsDb(samples: Float32Array, sampleRate?: number, options?: ValidateOptions): number
// Crest factor (peak − RMS), dB
function meteringCrestFactorDb(samples: Float32Array, sampleRate?: number, options?: ValidateOptions): number
// Mean (DC) offset, linear amplitude
function meteringDcOffset(samples: Float32Array, sampleRate?: number, options?: ValidateOptions): number
// Inter-sample (true) peak, dBFS. oversampleFactor is a power of two in 1..16 (0 / omit = 4)
function meteringTruePeakDb(samples: Float32Array, sampleRate?: number, oversampleFactor?: number, options?: ValidateOptions): number
// Fraction of frames below thresholdDb, in [0, 1]. thresholdDb default -45,
// frameLength default 1024, hopLength default 256.
function meteringSilenceRatio(
  samples: Float32Array,
  sampleRate?: number,
  thresholdDb?: number,
  frameLength?: number,
  hopLength?: number,
  options?: ValidateOptions
): number

Stereo level meters

A level meter that reads both channels instead of the 0.5 * (left + right) downmix the single-channel meters need. Unlike the meters above, it is request-object only — there is no positional overload, and a positional call throws.

typescript
// Crest factor over a channel pair, dB. Peak is taken across both channels
// and RMS is measured over the two together.
function meteringCrestFactorDbStereo(request: MeteringStereoRequest): number

interface MeteringStereoRequest extends ValidateOptions {
  left: Float32Array;
  right: Float32Array;
  sampleRate?: number;
}
typescript
const crestDb = meteringCrestFactorDbStereo({ left, right, sampleRate });

Reach for it whenever the two channels may be out of phase. An inverted pair cancels in the downmix, which understates RMS and so overstates crest factor: on a fully inverted pair the stereo meter reads 11.64 dB while the downmix path reads 0.00 dB.

meteringStereoCorrelation and meteringStereoWidth accept the same MeteringStereoRequest shape alongside their positional forms.

Clipping and dynamic range

typescript
function meteringDetectClipping(
  samples: Float32Array,
  sampleRate?: number,
  options?: MeteringDetectClippingOptions
): ClippingReport

interface MeteringDetectClippingOptions extends ValidateOptions {
  threshold?: number;        // linear absolute threshold, default 0.999
  minRegionSamples?: number; // minimum run length to report, default 1
}

function meteringDynamicRange(
  samples: Float32Array,
  sampleRate?: number,
  options?: MeteringDynamicRangeOptions
): DynamicRangeReport

interface MeteringDynamicRangeOptions extends ValidateOptions {
  windowSec?: number;      // 0 / omit = 3 s
  hopSec?: number;         // 0 / omit = 1 s
  lowPercentile?: number;  // omit or negative = 0.10 (0 is a literal 0th percentile)
  highPercentile?: number; // omit or negative = 0.95
}

interface ClippingReport {
  clippedSamples: number;
  clippingRatio: number;
  maxClippedPeak: number;
  regions: ClippingRegion[];
}
interface ClippingRegion {
  startSample: number;
  endSample: number;
  length: number;
  peak: number;
}
interface DynamicRangeReport {
  dynamicRangeDb: number;
  lowPercentileDb: number;
  highPercentileDb: number;
  windowRmsDb: Float32Array;
}

Stereo image

typescript
// Uncentered channel correlation (cosine similarity), −1..1
function meteringStereoCorrelation(left: Float32Array, right: Float32Array, sampleRate?: number, options?: ValidateOptions): number
// Mid/side stereo width: 0 = mono, ~1 = wide stereo; unbounded above
// (Infinity when the mid signal is silent, such as fully out-of-phase audio)
function meteringStereoWidth(left: Float32Array, right: Float32Array, sampleRate?: number, options?: ValidateOptions): number
// Mid/side point series. One point per sample by default; pass maxPoints for a
// display-sized, deterministically decimated point set (0 / >= length = one point per sample).
function meteringVectorscope(left: Float32Array, right: Float32Array, sampleRate?: number, options?: ScopeOptions): VectorscopeReport
// Phase-scope point series plus summary stats. maxPoints decimates the point cloud the same way;
// the summary stats are always computed over the full-resolution signal.
function meteringPhaseScope(left: Float32Array, right: Float32Array, sampleRate?: number, options?: ScopeOptions): PhaseScopeReport

interface ScopeOptions extends ValidateOptions {
  maxPoints?: number;   // 0 / omit / >= length = one point per input sample
}

// Deprecated aliases: pass maxPoints to meteringVectorscope / meteringPhaseScope instead.
// They simply delegate and are kept for backward compatibility.
function meteringVectorscopeDecimated(left: Float32Array, right: Float32Array, sampleRate?: number, maxPoints?: number, options?: ValidateOptions): VectorscopeReport
function meteringPhaseScopeDecimated(left: Float32Array, right: Float32Array, sampleRate?: number, maxPoints?: number, options?: ValidateOptions): PhaseScopeReport

interface VectorscopeReport {
  mid: Float32Array;
  side: Float32Array;
}
interface PhaseScopeReport {
  mid: Float32Array;
  side: Float32Array;
  radius: Float32Array;
  angleRad: Float32Array;
  correlation: number;
  averageAbsAngleRad: number;
  maxRadius: number;
}

meteringStereoCorrelation, meteringStereoWidth, meteringVectorscope, and meteringPhaseScope require left and right to be the same length.

meteringStereoWidth is a side-to-mid energy ratio, not a normalized percentage: 0 is pure mono, around 1 is a wide stereo signal, and larger finite values mean increasingly decorrelated or out-of-phase content. Do not clamp it to 2; when the mid channel is silent it deliberately returns Infinity.

Spectrum snapshot

meteringSpectrum is Welch-averaged over the whole signal (split into 50%-overlapping Hann frames whose power spectra are averaged). For a true single-frame snapshot that is not time-averaged, use meteringSpectrumFrame, whose frameOffset positional argument selects where the analysis frame starts.

typescript
function meteringSpectrum(
  samples: Float32Array,
  sampleRate?: number,
  options?: SpectrumOptions & ValidateOptions
): SpectrumReport

// True single-frame snapshot (one Hann-windowed nFft FFT), NOT time-averaged like meteringSpectrum.
// The analysis frame spans [frameOffset, frameOffset + nFft); samples past the end are zero-padded.
function meteringSpectrumFrame(
  samples: Float32Array,
  sampleRate?: number,
  frameOffset?: number,
  options?: SpectrumOptions & ValidateOptions
): SpectrumReport

interface SpectrumOptions {
  nFft?: number;                 // 0 / omit = 2048
  applyOctaveSmoothing?: boolean;
  octaveFraction?: number;       // e.g. 3 = 1/3-octave; 0 / omit = 3
  dbRef?: number;                // 0 / omit = 1.0
  dbAmin?: number;               // 0 / omit = library floor
}
interface SpectrumReport {
  frequencies: Float32Array;
  magnitude: Float32Array;
  power: Float32Array;
  db: Float32Array;
  nFft: number;
  sampleRate: number;
}

Scale Quantization

12-TET (twelve-tone equal temperament) scale helpers for building pitch-correction targets. modeMask is a 12-bit mask where bit i enables the i-th pitch class relative to root (a PitchClass, C = 0); natural major is 0b101010110101. referenceMidi is the tuning anchor (pass 0 for A4 = 69).

typescript
// Snap a (possibly fractional) MIDI number to the nearest enabled pitch class
function scaleQuantizeMidi(root: number, modeMask: number, midi: number, referenceMidi?: number): number
// Correction (quantized − input), in semitones
function scaleCorrectionSemitones(root: number, modeMask: number, midi: number, referenceMidi?: number): number
// Is pitchClass (0..11) enabled by modeMask relative to root?
function scalePitchClassEnabled(root: number, modeMask: number, pitchClass: number): boolean

Pair scaleQuantizeMidi(...) with pitchCorrectToMidi(...) to retune a detected note to the nearest scale degree.

librosa-Compatible Helpers

These librosa-parity helpers match the corresponding librosa functions and are exposed across the WASM, Node, and Python bindings. The signatures are below; for the librosa function each one maps to argument-for-argument, and when to reach for it, see librosa Compatibility.

Pre-emphasis / De-emphasis

typescript
function preemphasis(samples: Float32Array, coef?: number, zi?: number): Float32Array  // coef default 0.97
function deemphasis(samples: Float32Array, coef?: number, zi?: number): Float32Array

zi provides an initial condition (a previous frame's tail) when streaming.

Test-signal generation

Deterministic signals for fixtures, calibration, and click tracks — no asset files needed.

typescript
function tone(request?: ToneRequest): Float32Array
function chirp(request?: ChirpRequest): Float32Array
function clicks(request: ClicksRequest): Float32Array

Spectral reconstruction and pitch candidates

typescript
function griffinLim(request: GriffinLimRequest): Float32Array
function reassignedSpectrogram(request: ReassignedSpectrogramRequest): ReassignedSpectrogramResult
function piptrack(request: PiptrackRequest): PiptrackResult
function melDelta(request: MelDeltaRequest): Float32Array
function spectralFlux(request: SpectralFrameRequest & { lag?: number }): Float32Array
function onsetBacktrack(request: OnsetBacktrackRequest): Int32Array

griffinLim reconstructs audio from an STFT magnitude matrix; melToAudio and mfccToAudio are the mel-domain wrappers around it. onsetBacktrack moves detected onset frames back to the preceding energy minimum, which is what you want before slicing at an onset.

spectralBandwidth takes a configurable Minkowski exponent p (positional argument 5, or p on the request object) rather than assuming p = 2.

Structure and self-similarity

The segmentation family builds the matrices structural analysis is made of.

typescript
function segmentCrossSimilarity(request: SegmentCrossSimilarityRequest): SegmentMatrix
function segmentRecurrenceMatrix(request: SegmentRecurrenceMatrixRequest): SegmentMatrix
function segmentRecurrenceToLag(request: SegmentRecurrenceToLagRequest): SegmentMatrix
function segmentLagToRecurrence(request: SegmentLagToRecurrenceRequest): SegmentMatrix
function segmentPathEnhance(request: SegmentPathEnhanceRequest): SegmentMatrix
function segmentSubsegment(request: SegmentSubsegmentRequest): Int32Array
function segmentAgglomerative(request: SegmentAgglomerativeRequest): Int32Array

analyzeSections(...) is the packaged answer for "where are the sections". Reach for these when you want the intermediate matrices — to draw a self-similarity plot, or to run your own boundary detection over an enhanced recurrence matrix.

Note segmentation

Turn a monophonic F0 track into stable note regions. Pass a track you already have (from pitchYin / pitchPyin, or from your own tracker) together with the frame cadence that produced it.

typescript
interface NoteSegmentsRequest {
  f0Hz: Float32Array;
  voicedProb: Float32Array;
  /** Frames per second of the supplied track. */
  frameRate: number;
  segmentationThresholdCents?: number;
  minNoteMs?: number;
  referenceHz?: number;
}

function noteSegments(request: NoteSegmentsRequest): Array<{
  frameStart: number;
  frameEnd: number;
  startSeconds: number;
  endSeconds: number;
  medianCents: number;
}>

f0Hz and voicedProb must be the same non-zero length. Zero-Hz frames and probabilities below 0.5 count as unvoiced and break a note.

Silence Trim / Split

typescript
function trimSilence(
  samples: Float32Array,
  topDb?: number,        // default 60
  frameLength?: number,  // default 2048
  hopLength?: number,    // default 512
): { audio: Float32Array; startSample: number; endSample: number }

function splitSilence(
  samples: Float32Array,
  topDb?: number,
  frameLength?: number,
  hopLength?: number,
): Int32Array  // flat [start0, end0, start1, end1, ...]

trimSilence (librosa.effects.trim) uses frame RMS and a topDb distance below the peak RMS, returning the trimmed audio plus the original [startSample, endSample) range — distinct from the simpler trim(samples, sampleRate, thresholdDb). splitSilence (librosa.effects.split) returns non-silent intervals as sample-index pairs.

Frame / Pad / Length Helpers

typescript
function frameSignal(
  samples: Float32Array,
  frameLength: number,
  hopLength: number,
): { nFrames: number; frames: Float32Array }  // row-major

function padCenter(values: Float32Array, targetSize: number, padValue?: number): Float32Array
function fixLength(values: Float32Array, targetSize: number, padValue?: number): Float32Array
function fixFrames(frames: Int32Array, xMin?: number, xMax?: number, pad?: boolean): Int32Array

frameSignal is librosa.util.frame; padCenter, fixLength, and fixFrames mirror the librosa.util helpers of the same names.

Peak Picking / Vector Normalize

typescript
function peakPick(
  values: Float32Array,
  preMax: number,
  postMax: number,
  preAvg: number,
  postAvg: number,
  delta: number,
  wait: number,
): Int32Array  // peak indices

function vectorNormalize(
  values: Float32Array,
  normType?: number,  // 0 = inf, 1 = L1, 2 = L2, 3 = power (default 0)
  threshold?: number, // default 1e-12
): Float32Array

peakPick is librosa.util.peak_pick (post-processing for 1-D signals such as onset envelopes); vectorNormalize is librosa.util.normalize. See librosa Compatibility for the peakPick window parameters and each normType.

PCEN (Per-Channel Energy Normalization)

typescript
function pcen(
  values: Float32Array,
  nBins: number,
  nFrames: number,
  options?: {
    sampleRate?: number;
    hopLength?: number;
    timeConstant?: number;  // default 0.4
    gain?: number;          // default 0.98
    bias?: number;          // default 2.0
    power?: number;         // default 0.5
    eps?: number;           // default 1e-6
  },
): Float32Array

pcen matches librosa.pcen. Input is a row-major [nBins x nFrames] mel spectrogram; output uses the same layout.

Tonnetz / Tempogram / PLP

typescript
function tonnetz(
  chromagram: Float32Array,   // row-major [nChroma x nFrames]
  nChroma: number,
  nFrames: number,
): Float32Array               // [6 x nFrames]

function tempogram(
  onsetEnvelope: Float32Array,
  sampleRate: number,
  hopLength?: number,         // default 512
  winLength?: number,         // default 384
  mode?: 'autocorrelation' | 'auto' | 'ac' | 'cosine' | 0 | 1,  // default 'autocorrelation'
): { nFrames: number; winLength: number; data: Float32Array }

function fourierTempogram(
  onsetEnvelope: Float32Array,
  sampleRate?: number,
  hopLength?: number,
  winLength?: number,
): { nBins: number; nFrames: number; data: Float32Array }

function cyclicTempogram(
  onsetEnvelope: Float32Array,
  sampleRate: number,
  hopLength?: number,
  winLength?: number,
  bpmMin?: number,            // default 60
  nBins?: number,             // default 60
): { nFrames: number; nBins: number; data: Float32Array }

function tempogramRatio(
  tempogramData: Float32Array,
  winLength?: number,
  sampleRate?: number,
  hopLength?: number,
  factors?: Float32Array | number[], // default [0.5, 1, 2, 3, 4]
): Float32Array

function plp(
  onsetEnvelope: Float32Array,
  sampleRate: number,
  hopLength?: number,
  tempoMin?: number,          // default 30
  tempoMax?: number,          // default 300
  winLength?: number,
): Float32Array

For tempogram, mode: 'cosine' selects the window-local cosine-similarity variant ('auto', 'ac', 0, and 1 aliases are also accepted). See librosa Compatibility for the librosa feature each helper maps to, and Realtime and Streaming for when to use each.

Resampling

resample(samples, srcSr, targetSr) Medium

High-quality resampling using r8brain algorithm.

typescript
function resample(
  samples: Float32Array,
  srcSr: number,
  targetSr: number
): Float32Array

Audio Class

The Audio class is the method-style entry point for common one-shot functions. It stores the samples and sample rate internally, so you do not need to pass them to every call. More specialized helpers, such as section/melody/timbre/dynamics analysis and room-acoustic estimation, remain standalone functions in the WASM package.

Audio.fromBuffer(samples, sampleRate)

Create an Audio instance from raw sample data.

typescript
const audio = Audio.fromBuffer(samples, 44100);

sampleRate is optional and defaults to 48000. Always pass the buffer's actual sample rate, since the stored value feeds every instance method.

Audio.fromMemory(bytes)

Decode encoded audio bytes (Uint8Array) such as WAV or MP3 with the native WASM decoder and return an Audio instance. Throws a SonareError when the format is not supported by the bundled decoder.

typescript
const audio = Audio.fromMemory(new Uint8Array(await file.arrayBuffer()));

Audio.fromMemoryWithBrowserFallback(bytes, options?)

async; returns Promise<Audio>. Tries Audio.fromMemory first. If the bundled decoder cannot read the format, it uses the browser codec stack (AudioContext.decodeAudioData) for formats such as AAC, OGG, and FLAC. Browser-decoded multi-channel audio is mixed down to mono so the returned Audio object still contains one sample stream. Accepts an optional BrowserAudioDecodeOptions (audioContext / createAudioContext / targetSampleRate); a context this helper creates itself is closed afterward.

typescript
const audio = await Audio.fromMemoryWithBrowserFallback(
  new Uint8Array(await file.arrayBuffer()),
);

Properties

PropertyTypeDescription
audio.dataFloat32ArrayRaw audio samples
audio.lengthnumberNumber of samples
audio.sampleRatenumberSample rate (Hz)
audio.durationnumberDuration (seconds)

Instance Methods

Common one-shot helpers are available as instance methods: samples and sampleRate are supplied automatically. Focused helpers such as analyzeSections(...), analyzeMelody(...), analyzeDynamics(...), analyzeTimbre(...), and the room-acoustic functions remain standalone calls.

typescript
import {
  init,
  Audio,
  analyzeSections,
  analyzeMelody,
  analyzeDynamics,
  analyzeTimbre,
  detectAcoustic,
} from '@libraz/libsonare';

await init();

const audio = Audio.fromBuffer(samples, 44100);

// Analysis
const bpm = audio.detectBpm();
const key = audio.detectKey();
const keyCandidates = audio.detectKeyCandidates();
const beats = audio.detectBeats();
const downbeats = audio.detectDownbeats();
const onsets = audio.detectOnsets();
const result = audio.analyze();
const chords = audio.detectChords({ useHmm: true });
const sections = analyzeSections(audio.data, audio.sampleRate);
const melody = analyzeMelody(audio.data, audio.sampleRate);
const dynamics = analyzeDynamics(audio.data, audio.sampleRate);
const timbre = analyzeTimbre(audio.data, audio.sampleRate);
const acoustic = detectAcoustic(audio.data, audio.sampleRate);

// Effects
const { harmonic, percussive } = audio.hpss();
const corrected = audio.pitchCorrectToMidi(68.7, 69);
const held = audio.noteStretch({ onsetSample: 12000, offsetSample: 24000, stretchRatio: 1.25 });
const voice = audio.voiceChange({ pitchSemitones: 3, formantFactor: 1.05 });
const stretched = audio.timeStretch(1.5);
const shifted = audio.pitchShift(2);
const normalized = audio.normalize(-3.0);
const trimmed = audio.trim(-60.0);

// Feature extraction
const stftResult = audio.stft();
const mel = audio.melSpectrogram();
const mfcc = audio.mfcc();
const chroma = audio.chroma();
const nnls = audio.nnlsChroma();
const env = audio.onsetEnvelope();
const loudness = audio.lufs();
const centroid = audio.spectralCentroid();
const bandwidth = audio.spectralBandwidth();
const rolloff = audio.spectralRolloff();
const flatness = audio.spectralFlatness();
const zcr = audio.zeroCrossingRate();
const rms = audio.rmsEnergy();
const pitch = audio.pitchPyin();

// Resampling
const resampled = audio.resample(22050);

All parameters (e.g., nFft, hopLength, nMels) have the same defaults as the standalone functions.

Streaming API

The Streaming API enables real-time audio analysis for visualizations and live monitoring. Unlike batch analysis, streaming processes audio chunk by chunk with minimal latency.

When to Use

  • Batch API: Pre-recorded files, all-in-one analysis (BPM, key, chords, sections)
  • Streaming API: Live audio, visualizations, real-time feedback

This section is the StreamAnalyzer type/class reference. For the runnable recipe, the AudioWorklet bridge, output-format details, and the progressive-estimate walkthrough, see Realtime and Streaming.

StreamConfig

Configuration options for StreamAnalyzer.

typescript
interface StreamConfig {
  sampleRate?: number;         // default: 44100 (stream default, not 22050)
  nFft?: number;               // default: 2048
  hopLength?: number;          // default: 512
  nMels?: number;              // default: 128
  fmin?: number;               // default: 0
  fmax?: number;               // default: 0 (= sr/2)
  tuningRefHz?: number;        // default: 440
  computeMel?: boolean;        // default: true
  computeChroma?: boolean;     // default: true
  computeOnset?: boolean;      // default: true
  computeSpectral?: boolean;   // default: true
  emitEveryNFrames?: number;   // default: 1 (no throttling)
  magnitudeDownsample?: number;// default: 1
  maxPendingFrames?: number;   // default: 4096; overflow drops newly produced output frames
  maxProgressionEntries?: number; // default: 4096; cap for each retained chord/bar progression, overflow drops oldest
  keyUpdateIntervalSec?: number;  // default: 5
  bpmUpdateIntervalSec?: number;  // default: 10
  window?: number;             // 0=Hann (default), 1=Hamming, 2=Blackman, 3=Rectangular
  outputFormat?: 0;            // legacy; omit it or use Float32 (0)
}

outputFormat is retained only for source compatibility and must be 0 when provided. Choose a quantized read explicitly with readFramesU8 or readFramesI16; analysis itself always runs in float. See Realtime and Streaming.

The legacy computeMagnitude flag is no longer supported; passing it makes the constructor throw. The flag was removed because magnitude frames are not exposed by the StreamAnalyzer read paths; use stft/stftDb offline or the spectrum metering helpers for magnitude data.

streamAnalyzerConfigDefaults() returns a fully-populated StreamConfigDefaults object (a Required<StreamConfig>) holding the library's default values for every field above. Use it to seed a settings UI or to compute a diff against a user-supplied config; StreamAnalyzer itself applies these same defaults for any field you omit.

StreamAnalyzer Class

typescript
class StreamAnalyzer {
  constructor(config: StreamConfig);

  // Process audio chunk (internal offset tracking)
  process(samples: Float32Array): void;

  // Process with an explicit, contiguous sample offset. A gap, seek, or switch
  // from process() requires reset() first.
  processWithOffset(samples: Float32Array, sampleOffset: number): void;

  // Number of frames ready to read
  availableFrames(): number;

  // Read processed frames (full float precision)
  readFrames(maxFrames: number): FrameBuffer;

  // Quantized reads for bandwidth-reduced transfer / visualization
  // (optional quantizeConfig widens quantization ranges for unusually loud/quiet streams;
  // see Realtime and Streaming → custom quantization ranges)
  readFramesU8(maxFrames: number, quantizeConfig?: StreamQuantizeConfig): StreamFramesU8;   // Uint8 feature arrays
  readFramesI16(maxFrames: number, quantizeConfig?: StreamQuantizeConfig): StreamFramesI16; // Int16 feature arrays

  // Reset state for new stream
  reset(baseSampleOffset?: number): void;

  // Get statistics and estimates that update as audio arrives
  stats(): AnalyzerStats;

  // Total frames processed
  frameCount(): number;

  // Current time position (seconds)
  currentTime(): number;

  // Get the sample rate
  sampleRate(): number;

  // Set expected total duration for pattern lock timing
  setExpectedDuration(durationSeconds: number): void;

  // Set normalization gain for loud/compressed audio
  setNormalizationGain(gain: number): void;

  // Set tuning reference frequency (default: 440 Hz)
  setTuningRefHz(refHz: number): void;

  // Release resources (call when done). `delete()` is canonical; `dispose()` is an alias.
  delete(): void;
  dispose(): void;
}

FrameBuffer

Structure-of-Arrays format for efficient transfer via postMessage.

typescript
interface FrameBuffer {
  nFrames: number;
  nMels: number;
  nChroma: number;             // 12 when chroma is present; otherwise 0
  featureFlags: number;        // MEL=1, CHROMA=2, ONSET=4, SPECTRAL=8
  timestamps: Float32Array;      // [nFrames]
  mel: Float32Array;             // [nFrames * nMels], empty if MEL is absent
  chroma: Float32Array;          // [nFrames * nChroma], empty if CHROMA is absent
  onsetStrength: Float32Array;   // [nFrames], empty if ONSET is absent
  rmsEnergy: Float32Array;       // [nFrames]
  spectralCentroid: Float32Array;// [nFrames], empty if SPECTRAL is absent
  spectralFlatness: Float32Array;// [nFrames], empty if SPECTRAL is absent
  chordRoot: Int32Array;         // [nFrames], empty if CHROMA is absent
  chordQuality: Int32Array;      // [nFrames], empty if CHROMA is absent
  chordConfidence: Float32Array; // [nFrames], empty if CHROMA is absent
}

ChordChange

A detected chord change in the progression.

typescript
interface ChordChange {
  root: PitchClass;
  quality: ChordQuality;
  startTime: number;
  confidence: number;
}

BarChord

A chord detected at bar boundary (beat-synchronized).

typescript
interface BarChord {
  barIndex: number;
  root: PitchClass;
  quality: ChordQuality;
  startTime: number;
  confidence: number;
}

PatternScore

Match score for a known chord progression pattern.

typescript
interface PatternScore {
  name: string;   // pattern name (e.g., "royalRoad", "pop")
  score: number;  // match score (0-1)
}

AnalyzerStats

typescript
interface AnalyzerStats {
  totalFrames: number;
  totalSamples: number;
  durationSeconds: number;
  pendingFrames: number;       // unread frames currently buffered
  droppedOutputFrames: number; // newly produced frames dropped at the configured cap
  droppedChordProgressionEntries: number; // oldest chord-history entries dropped at the configured cap
  droppedBarProgressionEntries: number;   // oldest bar-history entries dropped at the configured cap
  estimate: ProgressiveEstimate;
}

ProgressiveEstimate

BPM, key, and chord estimates that improve over time as more audio is processed.

typescript
interface ProgressiveEstimate {
  // BPM estimation
  bpm: number;              // 0 if not yet estimated
  bpmConfidence: number;    // 0-1, increases over time
  bpmCandidateCount: number;

  // Key estimation
  key: PitchClass;          // 0-11 (C-B)
  keyMinor: boolean;
  keyConfidence: number;    // 0-1, increases over time

  // Chord estimation (current)
  chordRoot: PitchClass;
  chordQuality: ChordQuality;
  chordConfidence: number;
  chordStartTime: number;
  chordProgression: ChordChange[];     // detected chord changes
  barChordProgression: BarChord[];     // bar-synchronized chords
  currentBar: number;                  // current bar index
  barDuration: number;                 // bar duration in seconds

  // Pattern detection
  votedPattern: BarChord[];            // voted chord for each pattern position
  patternLength: number;              // length of repeating pattern (default: 4 bars)
  detectedPatternName: string;        // best matching pattern name (e.g., "royalRoad")
  detectedPatternScore: number;       // match score (0-1)
  allPatternScores: PatternScore[];   // all known pattern scores

  // Statistics
  accumulatedSeconds: number;
  usedFrames: number;
  updated: boolean;         // true if estimate changed this frame
}

Usage, AudioWorklet integration, and timing

The runnable StreamAnalyzer recipe — feeding blocks from an AudioWorklet, reading frames, throttling with emitEveryNFrames, and mapping the FrameBuffer stream-time timestamps onto AudioContext.currentTime — lives on Realtime and Streaming, with the AudioWorklet handshake and data-flow diagrams.

Releasing WASM objects

StreamAnalyzer, Mixer, StreamingEqualizer, and StreamingMasteringChain are embind handles onto WASM heap memory that the JavaScript garbage collector cannot reclaim — call delete() when done (StreamAnalyzer also accepts dispose(), and some classes expose destroy() as an alias). Plain functions like analyze() return ordinary JS values and need no cleanup. Node native cleanup differs; see Native Bindings.

Types

AnalysisResult

typescript
interface AnalysisResult {
  bpm: number;
  bpmConfidence: number;
  bpmCandidates: BpmHypothesis[];          // Ranked, best first
  key: Key;
  timeSignature: TimeSignature;
  timeSignatureCandidates: TimeSignature[]; // Ranked, best first
  beatTimes: Float32Array;  // Convenience copy of beats[].time, useful for librosa-style code
  beats: Beat[];            // Beat objects with per-beat strength
  chords: Chord[];
  sections: Section[];
  timbre: Timbre;
  dynamics: Dynamics;
  rhythm: RhythmFeatures;
  melody: MelodyContour;
  form: string;  // e.g., "IABABCO"
}

interface BpmHypothesis {
  value: number;
  confidence: number;
  /** How this hypothesis relates to the reported `bpm`. */
  relation: 'primary' | 'half' | 'double' | 'other';
}

bpm and timeSignature are the winners; the two *Candidates arrays are the ranked field behind them. They matter because tempo is genuinely ambiguous — a half-time feel and its double are both defensible readings of the same track. Rather than showing one number and hoping, offer the alternates:

typescript
const { bpm, bpmCandidates } = analyze({ samples, sampleRate });
const halfTime = bpmCandidates.find((c) => c.relation === 'half');
if (halfTime && halfTime.confidence > 0.4) {
  offerAlternative(halfTime.value);   // "or 84 BPM?"
}

The same arrays are on the C ABI, Node, and Python.

Beat

typescript
interface Beat {
  time: number;      // seconds
  strength: number;  // 0.0 to 1.0
}

Chord

typescript
interface Chord {
  root: PitchClass;
  bass: PitchClass;     // bass note for inversions
  quality: ChordQuality;
  start: number;       // seconds
  end: number;         // seconds
  confidence: number;
  name: string;        // "C", "Am", "G7"
}

Section

typescript
interface Section {
  type: SectionType;
  start: number;
  end: number;
  energyLevel: number;
  confidence: number;
  name: string;  // "Intro", "Verse 1", "Chorus"
}

TimeSignature

typescript
interface TimeSignature {
  numerator: number;    // e.g., 4
  denominator: number;  // e.g., 4
  confidence: number;
}

Timbre

typescript
interface Timbre {
  brightness: number;   // 0.0 to 1.0
  warmth: number;
  density: number;
  roughness: number;
  complexity: number;
}

interface TimbreFrame {
  brightness: number;
  warmth: number;
  density: number;
  roughness: number;
  complexity: number;
}

interface TimbreAnalysisResult extends TimbreFrame {
  spectralCentroid: Float32Array;
  spectralFlatness: Float32Array;
  spectralRolloff: Float32Array;
  timbreOverTime: TimbreFrame[];
}

Dynamics

typescript
interface Dynamics {
  dynamicRangeDb: number;
  peakDb: number;
  rmsDb: number;
  loudnessRangeDb: number;
  crestFactor: number;
  isCompressed: boolean;
}

RhythmFeatures

typescript
interface RhythmFeatures {
  syncopation: number;
  grooveType: string;  // "straight", "shuffle", "swing"
  patternRegularity: number;
  tempoStability: number;
  timeSignature: TimeSignature;
}

MelodyContour

typescript
interface MelodyContour {
  pitchRangeOctaves: number;
  pitchStability: number;
  meanFrequency: number;
  vibratoRate: number;     // Hz
  pitches: MelodyPoint[];  // per-frame pitch trajectory
}

MelodyPoint

typescript
interface MelodyPoint {
  time: number;        // frame time in seconds
  frequency: number;   // estimated f0 in Hz (0 when unvoiced)
  confidence: number;  // voicing confidence, 0.0 to 1.0
}

Enumerations

PitchClass

typescript
const PitchClass = {
  C: 0, Cs: 1, D: 2, Ds: 3, E: 4, F: 5,
  Fs: 6, G: 7, Gs: 8, A: 9, As: 10, B: 11
} as const;

Mode

typescript
const Mode = {
  Major: 0,
  Minor: 1,
  Dorian: 2,
  Phrygian: 3,
  Lydian: 4,
  Mixolydian: 5,
  Locrian: 6
} as const;

ChordQuality

typescript
const ChordQuality = {
  Major: 0, Minor: 1, Diminished: 2, Augmented: 3,
  Dominant7: 4, Major7: 5, Minor7: 6, Sus2: 7, Sus4: 8,
  Unknown: 9, Add9: 10, MinorAdd9: 11, Dim7: 12,
  HalfDim7: 13, Major9: 14, Dominant9: 15, Sus2Add4: 16
} as const;

SectionType

typescript
const SectionType = {
  Intro: 0, Verse: 1, PreChorus: 2, Chorus: 3,
  Bridge: 4, Instrumental: 5, Outro: 6, Unknown: 7
} as const;

Error Handling

All functions throw if the module is not initialized — call await init() first.

Native (C++) failures throw a structured SonareError: an Error subclass carrying a numeric code and its canonical codeName, mirroring the C ABI error enum. The same failure reports the same numeric code on every binding (WASM, Node native, Python, C ABI), so you can branch on the cause instead of matching message text. The package exports the ErrorCode enum, the SonareError class, and an isSonareError(value) type guard.

The facades consistently reject non-finite numbers, invalid enum/index values, and oversized resources before they reach DSP or serialization. Treat these failures as invalid input; do not rely on a binding silently clamping or accepting malformed values.

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

try {
  const mixer = Mixer.fromSceneJson(sceneJson, 48000, 512);
} catch (error) {
  if (isSonareError(error) && error.code === ErrorCode.InvalidParameter) {
    // e.g. 'send timing must be a string ("pre" or "post")'
    console.error(`scene rejected: ${error.codeName}: ${error.message}`);
  } else {
    throw error;
  }
}
ErrorCodeValue
Ok0
FileNotFound1
InvalidFormat2
DecodeFailed3
InvalidParameter4
OutOfMemory5
NotSupported6
InvalidState7
Cancelled8
Unknown99

The codes match Python's SonareError.code and the C ABI SonareError enum, and the Python CLI maps them onto its exit codes.

Mastering API

The browser package includes the same named mastering processors used by the /mastering demo. Decode audio with the Web Audio API, pass Float32Array channel buffers to libsonare, then export the returned samples as WAV in your application.

This section lists the JS entry points and their result/config types. For what each processor does, the preset list, and the analysis/assistant JSON, see Mastering Processors and Mastering Assistant.

typescript
import { init, masterAudioStereo, masteringChainStereo } from '@libraz/libsonare'

await init()

// Full chain with explicit stage config
const result = masteringChainStereo(left, right, sampleRate, {
  spectral: { airBand: { amount: 0.35, shelfFrequencyHz: 14000 } },
  maximizer: { truePeakLimiter: { ceilingDb: -1, oversampleFactor: 4 } },
  loudness: { targetLufs: -14, ceilingDb: -1, truePeakOversample: 4 },
})
console.log(result.outputLufs, result.outputTruePeakDbtp, result.outputLra)
if (result.loudnessTargetLimited) {
  console.warn('The true-peak ceiling prevented the requested LUFS target.')
}
console.log(result.stageGainReductions)

// Preset with nested overrides (the typed MasteringChainConfig shape)
const presetResult = masterAudioStereo(left, right, sampleRate, 'pop', {
  loudness: { targetLufs: -14 },
  maximizer: { truePeakLimiter: { releaseMs: 50 } },
})

Each of these has a *WithProgress variant taking an (progress, stage) => void callback. masteringProcess(...) / masteringProcessStereo(...) run one named processor, and masteringStereoAnalyze(...) returns a JSON report.

Offline chain and preset results include outputTruePeakDbtp, outputLra, loudnessTargetLimited, and stageGainReductions.

When loudnessTargetLimited is true, the true-peak ceiling prevented the requested LUFS target. Report outputLufs, not the requested target. Each StageGainReduction gives the most recent gain reduction for one dynamics or maximizer stage.

report — before and after in one object

Every offline chain result also carries a report, which is the "what did this actually do" summary you would otherwise assemble by measuring the input yourself:

typescript
interface MasteringReport {
  before: MasteringLoudnessSummary;
  after: MasteringLoudnessSummary;
  appliedGainDb: number;
  maxGainReductionDb: number;
  loudnessTargetLimited: boolean;
  /** 32 logarithmically spaced after-minus-before energy deltas, in dB. */
  bandEnergyDeltaDb: Float32Array;
}

interface MasteringLoudnessSummary {
  integratedLufs: number;
  maxMomentaryLufs: number;
  maxShortTermLufs: number;
  truePeakDbtp: number;
  loudnessRange: number;
}
typescript
const { report } = masteringChainStereo(left, right, sampleRate, config);
console.log(report.before.integratedLufs, '→', report.after.integratedLufs);
console.log(report.after.loudnessRange - report.before.loudnessRange, 'LU of range change');
drawTiltCurve(report.bandEnergyDeltaDb);   // 32 bands, positive = brighter after

The same object is mirrored on the C ABI, ctypes, Node, Python, and both CLI report files, so a report exported from the CLI and one read in the browser have the same shape.

The explainable-mastering helpers — masteringAudioProfile(...), masteringAssistantSuggest(...), and masteringStreamingPreview(...) — return JSON strings; see Mastering Assistant for their exact shapes, accepted options, and how to turn a suggestion into a rendered master. Reference-track workflows use masteringPairProcessorNames() and masteringPairAnalyze() (matched sample rate and comparable duration).

Stereo entry points for the explainable helpers

Each of the three has a stereo counterpart that measures the channel pair directly. They are request-object only — there is no positional overload, and a positional call throws.

typescript
function masteringAudioProfileStereo(request: MasteringStereoParamsRequest): string
function masteringAssistantSuggestStereo(request: MasteringStereoParamsRequest): string
function masteringStreamingPreviewStereo(request: MasteringStreamingPreviewStereoRequest): string

interface MasteringStereoParamsRequest {
  left: Float32Array;
  right: Float32Array;
  sampleRate?: number;
  params?: MasteringProcessorParams;
}

interface MasteringStreamingPreviewStereoRequest {
  left: Float32Array;
  right: Float32Array;
  sampleRate?: number;
  platforms?: StreamingPlatform[];
}
typescript
const profile = JSON.parse(masteringAudioProfileStereo({ left, right, sampleRate }));
const suggestion = JSON.parse(masteringAssistantSuggestStereo({ left, right, sampleRate }));
const preview = JSON.parse(
  masteringStreamingPreviewStereo({
    left,
    right,
    sampleRate,
    platforms: [{ name: 'Spotify', targetLufs: -14, ceilingDb: -1 }],
  }),
);

Use them for anything stereo. The mono helpers measure a 0.5 * (left + right) downmix, and on decorrelated material that downmix reads about 6 dB low — so the integrated loudness, the normalization gain derived from it, and the ceiling-risk judgement are all under-reported by the same amount. Measured on a decorrelated pink-noise pair (48 kHz, 4 s), the downmix path reported -22.55 LUFS against the stereo path's -16.44 LUFS, a 6.11 dB gap, and Spotify normalizationGainDb came out at +8.55 through the downmix versus +2.44 through the stereo path. On a correlated pair the gap shrinks to 3.01 dB, which is just the halving; the remaining ~3 dB is the decorrelation.

Only the loudness block of the stereo profile is measured from both channels: integrated LUFS and LRA come from the channel-summed program, and the true peak is the larger of the two. The spectral, dynamics, and tempo fields describe shape and timing rather than absolute level, so they stay measured on the downmix and remain directly comparable with masteringAudioProfile.

masteringStreamingPreviewStereo treats platforms exactly as the mono helper does: omit it or pass an empty array and the preview falls back to the built-in Spotify / Apple Music / YouTube set, returning three rows rather than throwing.

StreamingEqualizer

StreamingEqualizer is the block-by-block EQ object used for realtime-safe processing: up to 24 bands, zero-latency/natural/linear phase modes, dynamic EQ, mid/side processing, external sidechain input, spectrum snapshots, and offline reference matching. In the WASM package, call init() first and delete() when done.

typescript
import { init, StreamingEqualizer } from '@libraz/libsonare';
await init();

const eq = new StreamingEqualizer({ sampleRate: 48000, maxBlockSize: 512 });
try {
  eq.setBand(0, {
    type: 'HighShelf',
    frequencyHz: 8000,
    gainDb: 4,
    q: 0.7,
    enabled: true,
  });
  eq.setPhaseMode(1); // 1 = zero-latency, 2 = natural, 3 = linear
  eq.setAutoGain(true);

  const { left, right } = eq.processStereo(leftBlock, rightBlock);
  console.log(eq.spectrum(), eq.latencySamples(), left, right);
} finally {
  eq.delete();
}

Source-built C++ CLI equivalents for file-based EQ and filtering:

bash
sonare eq track.wav --type 2 --frequency-hz 8000 --gain-db 4 --q 0.7 -o eq.wav
sonare filter track.wav --type hp --cutoff 80 -o filtered.wav

StreamingRetune

StreamingRetune is the block-by-block mono pitch retune object. It maintains grain and delay state across calls, so use prepare() before the first block and delete() when done.

typescript
import { init, StreamingRetune } from '@libraz/libsonare';
await init();

const retune = new StreamingRetune({ semitones: 3, mix: 1, grainSize: 0 });
retune.prepare(48000, 512);

try {
  const out = retune.processMono(inputBlock);
  retune.setConfig({ semitones: -2, mix: 0.75 });
  console.log(out, retune.config(), retune.grainSize());
} finally {
  retune.delete();
}

Closest CLI equivalents for offline files from the source-built C++ CLI:

bash
sonare pitch-shift vocal.wav --semitones 3 -o shifted.wav
sonare voice-change vocal.wav --pitch-semitones 3 --formant-factor 1.0 -o voice.wav

RealtimeVoiceChanger

RealtimeVoiceChanger is the preset-based live voice chain (high-pass, gate, retune, formant, EQ, compressor, de-esser, reverb, and limiter stages) that keeps state across audio blocks. Use it for monitoring, AudioWorklet-style processing, or chunked voice rendering where voiceChange(...) is too simple. Factory preset IDs come from realtimeVoiceChangerPresetNames(); preset JSON is fetched with realtimeVoiceChangerPresetJson(...) and checked with validateRealtimeVoiceChangerPresetJson(...) (schema version 1). RealtimeVoiceChangerConfigInput is strict: use one of the six VoicePresetId strings or a preset object with either a dsp object or a macros object, never both.

typescript
import { init, RealtimeVoiceChanger, realtimeVoiceChangerPresetNames } from '@libraz/libsonare';
await init();

const changer = new RealtimeVoiceChanger(realtimeVoiceChangerPresetNames()[1]); // e.g. "bright-idol"
changer.prepare(48000, /*maxBlockSize=*/128, /*channels=*/1);
try {
  const out = changer.processMono(inputBlock);
  const realtime = changer.createRealtimeMonoBuffer(128); // zero-copy WASM heap view
  realtime.input.set(inputBlock.subarray(0, 128));
  realtime.process();
  console.log(out, realtime.output, changer.latencySamples());
} finally {
  changer.delete();
}

The zero-copy buffer helpers (createRealtimeMonoBuffer, createRealtimeInterleavedBuffer, createRealtimePlanarBuffer) return changer-owned WASM heap views; reuse them inside a realtime loop and discard after delete(). See Realtime Voice Changer for the preset list and chain stages.

voiceChangeRealtime(samples, sampleRate?, preset?, options?)

voiceChangeRealtime(...) is the offline whole-buffer convenience function around RealtimeVoiceChanger. It internally constructs and prepares a changer, runs the per-block render loop for you, then disposes it — matching the Python voice_change_realtime and Node equivalents — so callers do not manage the stateful object themselves.

typescript
function voiceChangeRealtime(
  samples: Float32Array,
  sampleRate?: number, // default 48000
  preset?: RealtimeVoiceChangerConfigInput,
  options?: {
    channels?: 1 | 2;   // default 1 (mono); 2 = interleaved stereo (L0,R0,L1,R1,...)
    /** @deprecated Ignored — the shared C-ABI renderer uses a fixed block size. */
    blockSize?: number;
  },
): Float32Array  // same layout/length as the input

Use this when you already have the full buffer. Reach for RealtimeVoiceChanger for manual block-by-block live use, and voiceChange(...) when you only need a one-shot pitch/formant change without the full preset chain.

StreamingMasteringChain

For real-time or memory-constrained use cases, such as processing audio block-by-block from AudioWorklet or a stream, the WASM module exposes StreamingMasteringChain. It accepts a StreamingMasteringChainConfig, which extends masteringChain()'s MasteringChainConfig with two optional streaming-only fields:

  • loudnessStaticGainDb — a precomputed static loudness gain in dB (e.g. targetLufs - measuredIntegratedLufs), applied per block so a preset's streaming preview matches its offline render with a loudness stage enabled.
  • loudnessStaticGainPeakDb — the offline-measured source true-peak in dBFS. When set, the static gain is clamped to loudness.ceilingDb - loudnessStaticGainPeakDb so the streaming limiter does not receive a hotter input than the offline chain.

It otherwise prepares processor state for a fixed block size and applies the chain incrementally.

typescript
import { init, StreamingMasteringChain } from '@libraz/libsonare';
await init();

const chain = new StreamingMasteringChain({
  eq: { tilt: { tiltDb: 0.5 } },
  dynamics: { compressor: { thresholdDb: -20 } },
  maximizer: { truePeakLimiter: { ceilingDb: -1, oversampleFactor: 4 } },
});

chain.prepare(48000, /*maxBlockSize=*/512, /*numChannels=*/2);

// Use the path that matches the prepared channel count: processMono() /
// flushMono() after prepare(..., 1), processStereo() / flushStereo() after
// prepare(..., 2). Mixing them throws a num_channels mismatch.
const { left, right } = chain.processStereo(leftBlock, rightBlock);

console.log(chain.stageNames());      // ['eq.tilt', 'dynamics.compressor', ...]
console.log(chain.latencySamples());  // total latency reported by active stages

// After the last input block, drain the chain latency and the finite tails.
let tail: { left: Float32Array; right: Float32Array };
while ((tail = chain.flushStereo()).left.length > 0) {
  write(tail.left, tail.right);
}

chain.reset();   // clear processor state without re-preparing
chain.delete();  // release the WASM handle (call when done)

flushMono() / flushStereo() emit the delayed audio plus finite processor tails once you have no more input. Call until an empty result comes back. Without the flush, a bounce built from a streaming chain loses its last latencySamples() samples and any reverb or limiter tail. The first latencySamples() samples of the concatenated stream are the chain's delay and should be dropped for a time-aligned result.

Stereo-only stages are skipped when numChannels === 1. The chain-config repair stages (repair.declick, repair.dereverb, repair.denoise, repair.declip, repair.decrackle, repair.dehum) are offline-only and throw if enabled on the streaming constructor — run them through masteringChain* / masterAudio*, or the one-shot masteringRepair* helpers. The loudness stage also throws unless you supply loudnessStaticGainDb (optionally with loudnessStaticGainPeakDb), since the streaming chain cannot measure whole-signal integrated LUFS. Call reset() between independent songs and delete() to free the handle.

The named mastering API families are:

PurposeFunction
Apply simple loudness masteringmastering()
List built-in mastering presetsmasteringPresetNames()
Apply a preset to mono audiomasterAudio()
Apply a preset to stereo audiomasterAudioStereo()
Apply a preset to mono audio with progressmasterAudioWithProgress()
Apply a preset to stereo audio with progressmasterAudioStereoWithProgress()
Run a full mono chainmasteringChain()
Run a full stereo chainmasteringChainStereo()
Run a full mono chain with progressmasteringChainWithProgress()
Run a full stereo chain with progressmasteringChainStereoWithProgress()
Run block-by-block EQStreamingEqualizer
Run a streaming chain (block-by-block)StreamingMasteringChain
Summarize source audio for mastering decisionsmasteringAudioProfile()
Summarize a stereo pair for mastering decisionsmasteringAudioProfileStereo()
Suggest mastering moves from source analysismasteringAssistantSuggest()
Suggest mastering moves from a stereo pairmasteringAssistantSuggestStereo()
Preview loudness targets for delivery platformsmasteringStreamingPreview()
Preview delivery loudness for a stereo pairmasteringStreamingPreviewStereo()
List mono/stereo processorsmasteringProcessorNames()
Get machine-readable processor classificationsmasteringProcessorCatalog()
List chain insert processorsmasteringInsertNames()
List the parameter keys an insert acceptsmasteringInsertParamNames(name)
List realtime-automatable insert parametersmasteringInsertParamInfo(name)
Process mono audiomasteringProcess()
Process stereo audiomasteringProcessStereo()
List pair processorsmasteringPairProcessorNames()
Process source/reference pairmasteringPairProcess()
List pair analysesmasteringPairAnalysisNames()
Analyze source/reference pairmasteringPairAnalyze()
List stereo analysesmasteringStereoAnalysisNames()
Analyze stereo channelsmasteringStereoAnalyze()

Related mastering guides: Processing chain, Tone and air, Dynamics, Stereo, limiter, and loudness, Reference match.

Standalone dynamics and repair processors

Every named stage is also a one-shot function, so you can run a single processor without assembling a chain. The dynamics processors return a DynamicsResult (the processed samples plus latencySamples, the processor's look-ahead latency in samples); the repair processors return a Float32Array.

typescript
// Offline dynamics
function masteringDynamicsCompressor(samples: Float32Array, sampleRate: number, options?: CompressorOptions): DynamicsResult
function masteringDynamicsGate(samples: Float32Array, sampleRate: number, options?: GateOptions): DynamicsResult
function masteringDynamicsTransientShaper(samples: Float32Array, sampleRate: number, options?: TransientShaperOptions): DynamicsResult

// Offline repair
function masteringRepairDeclick(samples: Float32Array, sampleRate: number, options?: DeclickOptions): Float32Array
function masteringRepairDeclip(samples: Float32Array, sampleRate: number, options?: DeclipOptions): Float32Array
function masteringRepairDecrackle(samples: Float32Array, sampleRate: number, options?: DecrackleOptions): Float32Array
function masteringRepairDehum(samples: Float32Array, sampleRate: number, options?: DehumOptions): Float32Array
function masteringRepairDenoiseClassical(samples: Float32Array, sampleRate: number, options?: DenoiseClassicalOptions): Float32Array
function masteringRepairDereverbClassical(samples: Float32Array, sampleRate: number, options?: DereverbClassicalOptions): Float32Array
function masteringRepairTrimSilence(samples: Float32Array, sampleRate: number, options?: TrimSilenceOptions): Float32Array

The repair stages are offline-only and are rejected by StreamingMasteringChain — run them with these one-shot helpers or inside masteringChain*/masterAudio*. See Dynamics and Repair.

MasteringChainConfig

masteringChain* and StreamingMasteringChain use the nested config schema below. Every key is optional. Only the stages you set are activated.

Stages always run in a fixed order:

Mastering chain order
RepairEQDynamicsSaturationSpectralStereoMaximizerLoudness
Only the stages you configure are activated, but whichever are enabled run in this order.

masterAudio* starts from a preset and accepts overrides using the same key names in flat dot-notation form, such as "dynamics.compressor.thresholdDb".

maximizer.truePeakLimiter.releaseMs controls the post-limiter release time. Omit it to keep the preset/config default of 50 ms; if you provide a flat override, the value is applied directly. maximizer.truePeakLimiter.applyGainAtInputRate applies static loudness gain before oversampling when set, which is useful when you need that gain staged at the source rate for host parity.

Full interface (click to expand)
typescript
interface MasteringChainConfig {
  repair?: {
    denoise?: boolean;
    nFft?: number; hopLength?: number; ddAlpha?: number; gainFloor?: number;
    declip?: { enabled?: boolean; clipThreshold?: number; lpcOrder?: number;
               iterations?: number; lpcBlend?: number; };
    decrackle?: { enabled?: boolean; threshold?: number;
                  /** 0 = median, 1 = wavelet shrinkage. */
                  mode?: number; levels?: number; };
    dehum?: { enabled?: boolean; fundamentalHz?: number; harmonics?: number;
              q?: number; adaptive?: boolean; searchRangeHz?: number;
              adaptation?: number; frameSize?: number; pllBandwidth?: number; };
    declick?: { threshold?: number; neighborRatio?: number; maxClickSamples?: number;
                lpcOrder?: number; residualRatio?: number; };
    dereverb?: { threshold?: number; attenuation?: number; nFft?: number;
                 hopLength?: number; t60Sec?: number; lateDelayMs?: number;
                 overSubtraction?: number; spectralFloor?: number;
                 wpeEnabled?: boolean; wpeIterations?: number; wpeTaps?: number;
                 wpeStrength?: number; };
  };
  eq?: {
    /** Canonical nested tilt stage. */
    tilt?: { enabled?: boolean; tiltDb?: number; pivotHz?: number };
    /** @deprecated Use `eq.tilt.tiltDb`. */
    tiltDb?: number;
    /** @deprecated Use `eq.tilt.pivotHz`. */
    pivotHz?: number;
  };
  dynamics?: {
    compressor?: { thresholdDb?: number; ratio?: number; attackMs?: number;
                   releaseMs?: number; kneeDb?: number; makeupGainDb?: number;
                   autoMakeup?: boolean; };
    deesser?: { frequencyHz?: number; thresholdDb?: number; ratio?: number;
                attackMs?: number; releaseMs?: number; rangeDb?: number;
                bandpassQ?: number; };
    transientShaper?: { attackGainDb?: number; sustainGainDb?: number;
                        fastAttackMs?: number; fastReleaseMs?: number;
                        slowAttackMs?: number; slowReleaseMs?: number;
                        sensitivity?: number; maxGainDb?: number;
                        gainSmoothingMs?: number; lookaheadMs?: number; };
    multibandComp?: { lowCutoffHz?: number; highCutoffHz?: number;
                      lowThresholdDb?: number;  lowRatio?: number;
                      lowAttackMs?: number;     lowReleaseMs?: number;
                      midThresholdDb?: number;  midRatio?: number;
                      midAttackMs?: number;     midReleaseMs?: number;
                      highThresholdDb?: number; highRatio?: number;
                      highAttackMs?: number;    highReleaseMs?: number; };
  };
  saturation?: {
    tape?: { driveDb?: number; saturation?: number; hysteresis?: number;
             outputGainDb?: number; speedIps?: number; headBumpDb?: number;
             bias?: number; gapLoss?: number; };
    exciter?: { frequencyHz?: number; driveDb?: number; amount?: number;
                q?: number; evenOddMix?: number; };
  };
  spectral?: {
    airBand?: { amount?: number; shelfFrequencyHz?: number;
                dynamicThresholdDb?: number; dynamicRangeDb?: number; };
  };
  stereo?: {
    imager?: { width?: number; outputGainDb?: number;
               decorrelationAmount?: number; preserveEnergy?: boolean; };
    monoMaker?: { amount?: number; frequencyHz?: number };
  };
  maximizer?: {
    truePeakLimiter?: { ceilingDb?: number; lookaheadMs?: number;
                        releaseMs?: number; oversampleFactor?: number;
                        applyGainAtInputRate?: boolean; };
  };
  loudness?: { targetLufs?: number; ceilingDb?: number;
               truePeakOversample?: number; };
}

interface MasteringResult {
  samples: Float32Array;
  sampleRate: number;
  inputLufs: number;
  outputLufs: number;
  appliedGainDb: number;
  loudnessTargetLimited?: boolean;
  latencySamples?: number;
}
interface MasteringChainResult extends MasteringResult {
  stages: string[];
  outputTruePeakDbtp: number;
  outputLra: number;
  loudnessTargetLimited: boolean;
  stageGainReductions: StageGainReduction[];
  report: MasteringReport;
}
interface MasteringStereoResult {
  left: Float32Array;
  right: Float32Array;
  sampleRate: number;
  inputLufs: number;
  outputLufs: number;
  appliedGainDb: number;
  latencySamples: number;
}
// Returned by masteringChainStereo / masterAudioStereo (and their
// WithProgress variants); MasteringStereoResult is the return type of
// masteringProcessStereo. There is no latencySamples field — the offline
// chain output is already latency-compensated.
interface MasteringChainStereoResult {
  left: Float32Array;
  right: Float32Array;
  sampleRate: number;
  inputLufs: number;
  outputLufs: number;
  appliedGainDb: number;
  stages: string[];
  outputTruePeakDbtp: number;
  outputLra: number;
  loudnessTargetLimited: boolean;
  stageGainReductions: StageGainReduction[];
  report: MasteringReport;
}
// MasteringStereoChainResult is a @deprecated alias for
// MasteringChainStereoResult, retained for source compatibility with the
// Node and Python bindings.

The glossary mastering guides explain when to reach for each section: Repair, Tone and Air, Dynamics, Stereo, Limiter, Loudness.

Mixing API

The WASM package exposes the libsonare mixing engine. mixStereo(...) is a compact one-shot renderer for stem arrays. Mixer is a persistent scene-based mixer with channel strips, buses, sends, VCA groups, automation, strip meters, and goniometer buffers.

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

mixingScenePresetNames(); // ['vocalReverbSend', ...]

const offline = mixStereo([vocalL, musicL], [vocalR, musicR], sampleRate, {
  inputTrimDb: [3, 0],
  faderDb: [-3, -12],
  pan: [0, -0.2],
  width: [1, 0.9],
  muted: [false, false],
});

const mixer = Mixer.fromSceneJson(mixingScenePresetJson('vocalReverbSend'), sampleRate, 512);
mixer.sceneWarnings(); // non-fatal scene-load warnings: insert params no processor reads (typos)
const latency = mixer.latencySamples(); // compiled graph latency for dry/wet alignment
const block = mixer.processStereo([vocalBlockL, musicBlockL], [vocalBlockR, musicBlockR]);
const meter = mixer.stripMeter(0, 'postFader');

mixer.scheduleFaderAutomation(0, sampleRate * 8, -6, 's-curve');
mixer.schedulePanAutomation(0, sampleRate * 8, -0.25, 'linear');
mixer.scheduleSendAutomation(0, 0, sampleRate * 12, -12, 'hold');

const goniometer = mixer.readGoniometerLatest(0, 256);
const sceneJson = mixer.toSceneJson();
mixer.delete();

Mixer.createRealtimeBuffer() and processStereoInto(...) are intended for AudioWorklet-style render loops where avoiding per-block allocation matters. See Mixing Engine for scene and routing details.

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`engine.setTrackMonitorMode(laneIndex, 'off''pfl'
Mix the engine's tracks live with lanes, buses, sends, and stripsengine.setTrackLanes(...), 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.

Type Export Index

The WASM package exports TypeScript helper types in addition to functions and classes. Use these when typing options, realtime buffers, and callback payloads.

AreaExported types/constants
Environment and engineEXPECTED_ENGINE_ABI_VERSION, EXPECTED_PROJECT_ABI_VERSION, EngineCapabilities, ProgressCallback
Engine lane mixer, markers, and MIDI clipsEngineTrackLane, EngineTrackSend, EngineBus, EngineMarker, EngineMidiClipSchedule, EngineMidiEvent, ExternalMidiEvent, MarkerKind, ProjectMarker
Key/chord/rhythm/timbre analysisChordDetectionOptions, KeyProfileName, RhythmAnalysisResult, TimbreAnalysisResult, TimbreFrame, DynamicsAnalysisResult
Spectral, pitch, and feature transformsMelPowerResult, StftPowerResult, PitchCorrectOptions, VoicedFlags, SpectralRegionOp, SpectralEditOptions, TempogramMode
Paged clip streamingClipPageStreamerEngine, ClipPageStreamerOptions, ClipPageStreamSource, OpfsClipStream, OpfsClipStreamOptions, OpfsClipPageProviderOptions
MasteringMasteringProcessorParams, MasteringProcessorCatalogEntry, MasteringInsertParamInfo, MasteringChannelPolicy, MasteringChainStereoResult, MasteringStereoParamsRequest, MasteringStreamingPreviewStereoRequest
Metering requestsMeteringStereoRequest, MeteringStereoDecimatedRequest
Streaming retuneStreamingRetuneConfig
Streaming EQStreamingEqualizerConfig, EqBandType, EqBandPhase, EqCoeffMode, EqMatchOptions, EqStereoPlacement
Realtime voiceVoicePresetId, RealtimeVoiceChangerConfigInput, RealtimeVoiceChangerPodConfig, RealtimeVoiceChangerMonoBuffer, RealtimeVoiceChangerInterleavedBuffer, RealtimeVoiceChangerPlanarBuffer
Mixing and Worklet realtime buffersMixerRealtimeBuffer, SonareScopeRingBuffer, SonareScopeRingReadResult, SonareWorkletScopeSnapshot
Project and engine automationProjectAssistSidecar, ProjectAssistSidecarInput, ProjectAutomationTargetKind, EngineTrackMonitorMode, TrackMonitorMode
Pan-law inputsPanLaw, PanLawName, PanLawInput

SurroundPan (the parameter type of Mixer.setSurroundPan) is not part of the package's public export list — type it inline or with a local alias rather than importing it.

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

Bundle Size

FileSizeGzipped
sonare.js~58 KB~14 KB
index.js~254 KB~51 KB
sonare.wasm~4,059 KB~1,376 KB
Total~4,372 KB~1,442 KB

Browser Support

BrowserMinimum Version
Chrome57+
Firefox52+
Safari11+
Edge16+

Requirements: WebAssembly, ES2017+ (async/await), Web Audio API