Skip to content

Binding Parity

libsonare uses one C++ core and exposes it through C, Python, Node native, WASM, and CLI APIs. The feature set is intentionally close across runtimes, 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(...) takes nested objects, and accepts dot-notation leaf keys in the same objectFlat 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
Constant-Q chroma (chromaCqt / chroma_cqt)Yes — WASM, Node, Python, C ABINo
Streaming analyzer and processors (StreamAnalyzer, StreamingEqualizer, StreamingMasteringChain)YesNo
Mel/MFCC inverse reconstructionYesNo
Realtime engineYesNo
Engine lane mixer (lanes, buses, sends, channel strips) and MIDI clip scheduleYes — see Realtime EngineNo
Realtime scope and wide meter telemetryYes — see Realtime EngineNo
Per-track PFL/AFL cue monitoring (pre-fader listen / after-fader listen)Yes — see Realtime Engine; reachable from the WASM AudioWorkletNo
Mastering presets/chains/processorsYesPartial
Mastering assistant/profile/preview JSONYesNo dedicated command
Stereo assistant/profile/preview JSON (masteringAudioProfileStereo, masteringAssistantSuggestStereo, masteringStreamingPreviewStereo)Yes — WASM, Node, Python (mastering_audio_profile_stereo, …), C ABI (sonare_mastering_audio_profile_stereo, …). They measure the left/right pair instead of a downmix, which under-reports integrated loudness by about 6 dB on decorrelated material — see Stereo sourcesNo dedicated command
Stereo crest factor (meteringCrestFactorDbStereo / metering_crest_factor_db_stereo)Yes — WASM, Node, Python, C ABI (sonare_metering_crest_factor_db_stereo)No
Mixing engine and scenesYesmix (C++ CLI also exports scene presets)
Surround and multichannel mixingRealtime engine lanes are panned into 5.1/7.1 group buses from the strip's surroundPan position and expose wide-meter telemetry. The standalone offline Mixer remains stereo; sourceChannelLayout is stored but does not yet preserve a multichannel lane source. See Surround and multichannel.No
Project and arrangement editing (headless DAW)Yes — see Project EditingYes
Typed automation targets (track fader/pan)Yes — see Project EditingNo
Audio-source owning metadata (contentHash / externalStemRole)Yes — see Project EditingNo
Built-in instruments (NativeSynth presets/patches)Yes — see Built-in InstrumentsYes — project bounce --synth <preset> pins a NativeSynth preset (project synth-presets lists them), and a bare --synth follows GM programs
GM program following in a synth bounceC ABI (use_gm_programs), Python (auto_select_gm=), and the WASM/Node synth-bounce bindings (useGmPrograms) can follow incoming GM bank/program changes; an explicit patch remains the fallbackYes — the bare --synth flag
Capability catalog and build diagnosticsYes — capabilityCatalog() / capability_catalog() and capabilities() on every surface, canonical JSON through the C ABIYes — doctor
Cooperative cancellation of long offline callsYes — cancel?: () => boolean on Node and WASM, cancel= on Python, SonareCancelCallback on the C ABI; a cancelled call reports error code 8 and allocates no outputNo
SoundFont 2 playerYes — see SoundFont 2 PlayerNo (Project API only)
Realtime engine live MIDI inputYes — see MIDI InputNo
External MIDI output and clock/transport forwardingYes — WASM, Node, Python, C ABI; browser worklets deliver lowered MIDI 1.0 messages through onMidiOutNo
Web MIDI bridge (bindWebMidi) and microphone helper (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 decoding can read 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 (legacy StreamConfig.outputFormat must be 0)same as WASMread_frames_i16 / read_frames_u8 (legacy output_format must be 0)
Mixer strip referencesnumeric index; stripById(id) for lookupnumeric index or strip-id stringnumeric index or strip-id string
Stereo mix (mixStereo / mix_stereo)separate leftChannels / rightChannels arrays plus a MixOptions objectsame as WASM[(left, right), …] strips plus keyword arrays (fader_db, pan, width, input_trim_db)
timeStretch / pitchShift(samples, sampleRate, rate/semitones)same as WASM(samples, sample_rate, rate/semitones)
Metering taps (meterTap / stripMeter)meterTap(strip, tap) for an explicit pre/post-fader tap; stripMeter(strip) is the post-fader conveniencesame as WASMmeter_tap(strip, tap) / strip_meter(strip)

Config, return, and data shapes

TopicWhat differs
Mastering chain configmasteringChain(...) and StreamingMasteringChain take nested config objects; masterAudio(...) overrides use flat dot-notation keys. Both spellings are typed and supported in MasteringChainConfig — a dotted leaf key such as 'loudness.targetLufs' may sit beside or replace its nested equivalent, which is the convenient shape for overrides assembled dynamically and the form the C ABI carries. Prefer the nested spelling in hand-written code: it is checked field by field, where a dotted key is only checked at run time
Stereo assistant/metering request typesThe stereo entry points added alongside the mono ones are request-object only — no positional overload on any JS surface. Their request-type names differ: WASM shares one MasteringStereoParamsRequest between profile and suggest, while Node splits it into MasteringAssistantSuggestStereoRequest and MasteringAudioProfileStereoRequest (the latter extends the former and adds nothing). Python takes plain positional/keyword arguments (left, right, sample_rate=…), and the C ABI takes const float* left, const float* right, size_t length
StreamingMasteringChain scopeBlock-safe stages only. It rejects repair stages that need lookaround/file context. A loudness stage is supported when the caller supplies a precomputed static gain as loudnessStaticGainDb (JS) / loudness_static_gain_db (Python), with an optional source true-peak field; otherwise the constructor rejects it
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(...) and the Audio.normalize() convenience methods default to 0.0 dBFS on Python, WASM, and Node native. This means normalize the peak to full scale, not apply a gain of zero
bounceOffline(...) LUFSSame LUFS-normalization default in C API and WASM (LUFS = Loudness Units relative to Full Scale, the broadcast loudness unit — see LUFS); pass normalizeLufs / normalize_lufs explicitly when porting older code if the behavior matters
mfcc liftermfcc(...) / mfcc takes a trailing lifter / lifter argument (cepstral liftering, default 0 = no liftering) on every binding; the C-ABI explicit-range entry point is sonare_mfcc_ex
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 curvesThe mixing and engine APIs use separately named curve types. Mixing's AutomationCurve accepts 'linear', 'exponential', 'hold', 's-curve'. The engine/project API uses a distinct type — EngineAutomationPointCurve (Node) / ProjectAutomationCurve (WASM, which also accepts the ordinals 03) — and spells the s-curve value 'scurve' (no hyphen) rather than mixing's 's-curve'. Don't assume one shared name or spelling across the two surfaces
Automation target kindA separate axis from the curve shape above: SonareAutomationTargetKind / ProjectAutomationTargetKind (WASM) classifies what a project automation lane drives — the legacy opaque host-defined target, or a typed track fader (TRACK_FADER_DB) / pan (TRACK_PAN) target. Node exposes it as targetKind, Python as the target_kind keyword (or ordinals 0/1/2). Adding a typed lane bumps the project JSON to schema version 2; a project with only opaque lanes keeps schema version 1 and its existing bytes — see Project Editing
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, where ppq means pulses per quarter note. 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 (inter-sample peak) limiter settings. The document also carries its own schema version: version 1 is the flat, fixed 3-band low/mid/high multiband compressor shape; version 2 is selected once the multiband compressor needs a different cutoff count, crossover slope/mode, or band count, and serializes dynamics.multibandComp as a structured object with strict field validation — see Mastering Processors
Mastering limiter optionsreleaseMs / release_ms and applyGainAtInputRate / apply_gain_at_input_rate are available on the mastering helper APIs. 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 — Universal MIDI Packet — 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 API instead accepts a track id or name; both APIs accept strip EQ bands as EqBand objects or band JSON strings (setTrackStripEqBand / setMasterStripEqBand, with …EqBandJson variants for raw JSON)
Structural-similarity namingPython drops the segment_ prefix the JavaScript bindings use: cross_similarity / recurrence_matrix / recurrence_to_lag / lag_to_recurrence / path_enhance / subsegment / agglomerative against segmentCrossSimilarity and friends
Audio sample accessWASM's audio.data, Node's audio.getData(), and Python's audio.data all return copies; writing into a returned array does not edit the instance, and each access allocates
DistributionThe published artifacts are the WebAssembly npm package, the Python wheel, and the native CLI archives. The Node native binding is marked private and is consumed as a local dependency, so it is always built from source
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; both CLIs map failures to stable exit codes (2 usage, 3 invalid parameter, 11 cancelled; see CLI)
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 availabilitySome commands depend on whether you installed the PyPI Python CLI or built the C++ CLI from source — 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.

Row-major vs column-major

A row-major buffer stores each row's elements contiguously (one whole row after another); a column-major buffer stores each column contiguously instead. libsonare returns [rows x nFrames] matrices row-major — the entire first row, then the entire second row — so index an element as row * nFrames + frame.

Verification Sources

When checking parity, use the source files as the authoritative API sources:

  • bindings/wasm/src/index.ts
  • bindings/python/src/libsonare/analyzer.pyi
  • bindings/node/src/index.ts
  • include/sonare/sonare_c.h
  • include/sonare/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.