Skip to content

Binding Parity

libsonare uses one C++ core and exposes it through C, Python, Node native, WASM, and CLI surfaces. The feature set is intentionally close across bindings, but naming and configuration shape differ by language.

Read this page after Feature Map when you already know the feature family and need to choose a runtime or port code between bindings.

Parity does not mean identical syntax

This page compares whether the same capability exists across runtimes. It does not mean every function has the same name, argument order, return shape, or default value. When porting code, check both the feature row and the shape differences before assuming a direct copy will work.

What You Will Learn

By the end of this page you should be able to:

  • translate naming conventions between JavaScript, Python, C++, C ABI, Node native, and CLI;
  • identify which features are present in each binding and which are not available from the CLI;
  • account for shape differences such as nested vs flat configs, row-major matrices, scene JSON, and streaming frame buffers;
  • choose the authoritative source file to inspect when the docs and runtime need to be checked.

Naming Conventions

ConceptWASM / Node JSPythonC / C++
Function stylecamelCase, e.g. detectBpm, masterAudioStereosnake_case, e.g. detect_bpm, master_audio_stereoC ABI uses sonare_*; C++ uses namespaces/classes
Mastering chain configWASM masteringChain(...) uses nested objectsFlat dot-notation overrides and dict configsC++ structs; C ABI structs/JSON helpers
Preset overridesFlat dot notation for masterAudio(...)Flat dot notationFlat params or C++ config mutation
Mixer scenesJSON strings and MixerJSON strings and Mixermixing::api::Scene plus JSON helpers

Feature Availability

Every library binding exposes the same feature families: WASM, Python, Node native, C++, and the C ABI.

Most meaningful gaps are in the CLI. The table lists each feature family with library-binding status and CLI coverage. Unless a row says otherwise, assume the feature exists in all library bindings. Naming differences follow the naming conventions above.

Feature familyLibrary bindingsCLI
Batch analysisYesYes
Low-level features and librosa helpersYesCommon commands
Streaming analyzerYesNo
Mel/MFCC inverse reconstructionYesNo
Realtime engineYesNo
Engine lane mixer (lanes, buses, sends, channel strips) and MIDI clip scheduleYes — see Realtime and StreamingNo
Realtime scope and wide meter telemetryYes — see Realtime and StreamingNo
Mastering presets/chains/processorsYesPartial
Mastering assistant/profile/preview JSONYesNo dedicated command
Mixing engine and scenesYesmix (C++ CLI also exports scene presets)
Surround and multichannel mixingYes for scene/pan round-trip plus realtime surround group buses. Surround panning is staged: setSurroundPan positions save and reload in scene JSON but do not move the sound yet, because the surround DSP path is not implemented. The parts that work today are the surround group bus and wide-meter telemetry.No
Project and arrangement editing (headless DAW)Yes — see Project EditingYes
Built-in instruments (NativeSynth presets/patches)Yes — see Built-in InstrumentsPartial — project bounce --synth exposes the simple built-in synth waveform path, not the NativeSynth preset/patch catalog
SoundFont 2 playerYes — see SoundFont 2 PlayerNo (Project API only)
Realtime engine live MIDI inputYes — see MIDI InputNo
Web MIDI bridge (bindWebMidi) and microphone glue (bindMicrophoneInput)WASM / browser onlyNo
External-instrument bounce protocol (ExternalInstrument)Python only — see Project BounceNo
Editing DSPYesYes
Region-based spectral editing (spectralEdit)Yes — see Spectral EditingNo
Metering (meters, clipping/dynamic-range, stereo image, spectrum)YesC++ CLI only (meter, clipping, dynamic-range)
Scale quantizationYesNo
Room acousticsYessonare acoustic [--ir], estimate-room, synthesize-rir, room-morph
File decodingNative: WAV/MP3 (FFmpeg builds add more); WASM: most APIs take decoded samples, while Audio.fromMemory(...) decodes WAV/MP3 bytes and browser fallback can decode supported formatsSame as the native build

Known Shape Differences

The same capability can take different argument shapes, config layouts, or return values across bindings. When porting, the most common bugs come not from the math but from how matrices are flattened, whether options are passed as an object or as keyword arguments, and whether a returned field is named differently.

Function and argument shapes

These functions exist across the library bindings but take their arguments differently. Naming follows the naming conventions (camelCase vs snake_case).

FunctionWASMNode nativePython
detectChords / detect_chordsoptions objectpositional / keyword paramspositional / keyword params
Streaming readsprocess, readFrames, statsfloat Structure-of-Arrays read is readFramesSoaprocess, read_frames, stats
Quantized stream readsreadFramesI16 / readFramesU8, StreamConfig.outputFormatsame as WASMread_frames_i16 / read_frames_u8, output_format
Mixer strip referencesnumeric index; stripById(id) for lookupnumeric index or strip-id stringnumeric index or strip-id string

A few signatures don't line up across all three bindings:

  • Stereo mix: WASM mixStereo(...) takes separate leftChannels / rightChannels arrays plus a MixOptions object; Python mix_stereo(...) takes [(left, right), …] strips plus keyword arrays such as fader_db, pan, width, and input_trim_db.
  • timeStretch(...) / pitchShift(...): WASM uses samples, sampleRate, rateOrSemitones; Node native uses samples, rateOrSemitones, sampleRate?. Pass all numeric arguments explicitly when porting between the two.
  • Metering taps: use meterTap(strip, tap) for an explicit pre/post-fader tap; Node's stripMeter(strip) is the post-fader convenience path.

Config, return, and data shapes

TopicWhat differs
Mastering chain configmasteringChain(...) and StreamingMasteringChain use nested config objects; masterAudio(...) overrides use flat dot-notation keys
StreamingMasteringChain scopeBlock-safe stages only — it rejects repair stages that need lookaround/file context and the whole-file loudness stage; use one-shot mastering APIs for those
analyze(...) returnEvery binding — C ABI, Python, Node native, and WASM — returns the complete analyze result: chords, sections, timbre, dynamics, rhythm, melody, form, and per-beat strength. The dedicated functions (detect_chords, analyze_sections, …) stay useful when you need extra parameters or just one family without running the full pipeline
normalize(...) defaultsModule-level normalize(...) (Python, WASM, Node native) defaults to 0.0 dBFS — that is, normalize the peak to full scale, not a gain of zero; the Python Audio.normalize() convenience method still defaults to target_db=-3.0
bounceOffline(...) LUFSSame LUFS-normalization default in C API and WASM; pass normalizeLufs / normalize_lufs explicitly when porting older code if the behavior matters
trim vs trimSilencetrim(...) uses a simple thresholdDb and returns audio only; trimSilence(...) / trim_silence(...) follow librosa.effects.trim with topDb, frame RMS, and original sample ranges
Automation curvesShared AutomationCurve across engine and mixing APIs (linear, exponential, hold, s-curve); update older per-module curve enum names to this shared name
Scene JSONInterchange format for persistent mixers; prefer Mixer.toSceneJson() (WASM/Node) or Mixer.to_scene_json() (Python) over hand-written JSON when preserving runtime edits
Clip loop crossfadesetClipLoop / set_clip_loop accepts loopCrossfadePpq / loop_crossfade_ppq on every binding. It is an equal-power seam crossfade, clamped by pre-roll and half the loop, ignored under warp, and serialized only when non-zero
Project bounce variantsThe headless-DAW Project bounces to audio across bindings; instrument-bound bounce (bounceWithBuiltinInstrument / bounceWithSynthInstrument / bounceWithSf2Instrument) and the take/comp arrangement model are shared — see Project Bounce and Recording and Takes. The ExternalInstrument bounce protocol is Python-only
Mastering chain JSONChain JSON and named-processor parameter maps round-trip the same field set: repair.declip lpcBlend, multiband per-band parameters, compressor detector / sidechain-HPF / PDR settings, and realtime voice-changer ISP limiter settings
Mastering limiter optionsreleaseMs / release_ms and applyGainAtInputRate / apply_gain_at_input_rate are available on the mastering helper surfaces. A zero release keeps the 50 ms library default on the simple one-shot helper; preset/chain override values are applied directly
Acoustic analysisMeasurement and blind-estimation entry points return AcousticResult; geometric room acoustics adds equivalent-room estimates, RIR synthesis, and creative room morphing (display blind estimates and equivalent-room estimates with confidence)
Engine lane mixer / MIDI clipsThe compiled shapes are identical everywhere (EngineTrackLane / EngineTrackSend / EngineBus; MIDI events carry absolute-sample renderFrame UMP words). Python exposes EngineMidiClipSchedule / EngineMidiEvent dataclasses where JS/Node take plain objects. The raw engine's setSoloMute addresses a fixed lane index; the browser SonareEngine worklet facade instead accepts a track id or name; both surfaces accept strip EQ bands as EqBand objects or band JSON strings (setTrackStripEqBand / setMasterStripEqBand, with …EqBandJson variants for raw JSON)
ErrorsEvery binding raises a structured SonareError with the same C-ABI numeric code: WASM and Node throw an Error subclass with code + codeName (exported ErrorCode enum and isSonareError guard); Python raises a RuntimeError subclass with .code; the Python CLI maps the codes to exit codes (the C++ CLI keeps plain 0/1)
WASM object returnsWASM arrays/objects returned by name-list helpers (*Names()), preset-name helpers, synthPresetPatch, section, and key-candidate helpers are re-rooted into the caller's JavaScript realm, so they survive structuredClone() / postMessage() like ordinary objects
CLI surfaceSome availability depends on whether the command is the PyPI Python CLI or the source-built C++ CLI — see CLI

Rich analysis fields

On C ABI, Python, Node native, and WASM, the analyze(...) result carries chords, sections, timbre, dynamics, rhythm, melody, form, and per-beat strength. When you only need one family — or you want to tune parameters the all-in-one call doesn't expose — the focused helpers stay available across runtimes: detectChords / detect_chords, analyzeSections / analyze_sections, analyzeTimbre / analyze_timbre, analyzeDynamics / analyze_dynamics, and analyzeRhythm / analyze_rhythm.

Porting Checklist

When moving a JavaScript example to Python, or Python verification code to C++, work through these checks in order:

  1. Rename functions using the conventions table — detectBpm becomes detect_bpm, melSpectrogram becomes mel_spectrogram, and so on.
  2. Match the input shape. Most APIs take a decoded, mono sample array plus sampleRate.
  3. Check option names and defaults — especially nFft / n_fft, hopLength / hop_length, and nMels / n_mels, which directly affect the result.
  4. Confirm matrix orientation. Returned [rows x nFrames] row-major arrays must not be read as column-major in another language.
  5. Don't expect bit-exact numbers. Allow for small differences from floating-point, windowing, and decoder behavior; verify the tolerance fits your use case.

Verification Sources

When checking parity, use the source files as the authoritative surface:

  • bindings/wasm/src/index.ts
  • bindings/python/src/libsonare/analyzer.pyi
  • bindings/node/src/index.ts
  • src/sonare_c.h
  • src/sonare_c_acoustic.h
  • src/sonare.h
  • tools/sonare_cli.cpp

The libsonare repository also includes tools/parity, which checks default values, constants/enums, and parameter names across C++, C ABI, Python, Node, and WASM.