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;
  • map one failure to the error class, code, and CLI exit status each runtime gives it;
  • 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 ​

One C++ core implements the DSP and every runtime calls it, so the same input gives the same result through Python and through WASM. What differs is the API surface around the core, which is written by hand for each binding.

The libsonare repository measures that surface instead of asserting it. tools/parity/surface-coverage.md is a generated matrix with one row per feature domain (the public C header a function is declared in) and one column per runtime: Python, Node, WASM, and the Python sonare CLI and native sonare-cli as separate columns, because their command sets differ. Each cell counts the domain's C ABI entry points the runtime reaches. The numbers are not copied here. make surface-coverage regenerates the file, and make surface-coverage-check fails CI when the tracked copy drifts from the headers.

Read a cell as reach, not quality. Both CLIs are curated subsets by design, and WASM exposes neither the host filesystem nor anything that needs threads. An entry point counts as reached when the checker finds it under the runtime's own naming (a class method, a handle-prefixed rename or a verified alias). A reviewed gap on the allowlist still counts as a gap. For one specific call, the per-domain row narrows the search and the verification sources settle it.

The tables below are the reader-facing companion. Yes means WASM, Node, Python and the C ABI, unless the cell names fewer. The CLI column names the command, or says No or Partial. Naming differences follow the naming conventions.

Analysis, features and metering ​

Feature familyLibrary bindingsCLI
Batch analysisYesYes
Low-level features and librosa helpersYesCommon commands
Constant-Q chroma (chromaCqt / chroma_cqt)YesNo
Chord functions (chordFunctions / chord_functions; chordFunctionalAnalysis returns the same entries)Yes (sonare_chord_functions)No
Chord and key annotation conversion (chordSymbolFromAnalysis / keyModeFromAnalysis)Yes (sonare_chord_symbol_from_analysis, sonare_key_mode_from_analysis)No
Measured tuning (tuning: 'auto', referenceHz: 'auto', tuningToReferenceHz / referenceHzToTuning)YesNo
Transcription (transcribe, transcribeToClip), including the polyphonic tracker limits, minNoteDivision and referenceHz: 'auto'Yes. The polyphonic limits (maxPolyphony, minFramePeakRatio, minRidgePeakRatio, reattackRatio) apply to the polyphonic path only; see Audio to MIDItranscribe on both CLIs, with --max-polyphony and --min-note-division
NMF stem separation (decomposeStems / decomposeStemsLinked)Yesdecompose-stems, mono input only
Mel/MFCC inverse reconstructionYesNative CLI only: mel-to-audio, mfcc-to-audio
Streaming analyzer and processors (StreamAnalyzer, StreamingEqualizer, StreamingMasteringChain)YesNo
Metering (meters, clipping, dynamic range, stereo image, spectrum)YesNative CLI only: meter, clipping, dynamic-range
Stereo crest factor (meteringCrestFactorDbStereo)Yes (sonare_metering_crest_factor_db_stereo)No
Scale quantization and scale masks (scaleMaskForMode)Yesscale-quantize on both CLIs; it and pitch-correct-timevarying take a mode name or a 12-bit mask. No scale-mask command

Editing, repair and mastering ​

Feature familyLibrary bindingsCLI
Editing DSPYesYes
Region-based spectral editing (spectralEdit)Yes, see Spectral EditingNo
Automatic pitch correction to a detected or named key (autoTune / auto_tune)Yes (sonare_auto_tune)No
Offline vocal editing (VocalEditSession, and the vocal edits stored in a project)VocalEditSession on WASM, Node and Python. The project-level vocal calls are Partial (5 of 10 entry points). See Offline Monophonic Vocal Editingvocal-notes on both CLIs lists a session's notes; no editing or rendering
Repair in one call (masteringRepairAnalyze / masteringRepairApply)Yes. Any channel count, fixed stage order, progress and cancel; see Audio Repair WorkflowNo (the native repair command is a separate path)
Mastering presets, chains and processorsYesPartial
Mastering assistant, profile and preview JSONYesmastering-suggest, mastering-profile, mastering-streaming on both CLIs (mono or downmix)
Stereo assistant, profile and preview JSON (masteringAudioProfileStereo, masteringAssistantSuggestStereo, masteringStreamingPreviewStereo)Yes. They measure the left/right pair; a downmix under-reports integrated loudness by about 6 dB on decorrelated material, see Stereo sourcesNo dedicated command
Streaming loudness gain (streamingLoudnessGain / streamingLoudnessGainStereo)Yes (sonare_streaming_loudness_gain[_stereo])No
Reference at its own sample rate (referenceSampleRate on pair processors, pair analyses, A/B loudness match and EQ match)Yes (reference_sample_rate argument; sonare_eq_match_ex)mastering-pair-analyze and mastering-pair-processor read a reference file at any rate
Cooperative cancellation of long offline callsYes: cancel?: () => boolean on Node and WASM, cancel= on Python, SonareCancelCallback on the C ABI. A canceled call reports error code 8 and allocates no outputNo

Mixing and realtime engine ​

Feature familyLibrary bindingsCLI
Mixing engine and scenesYesmix-strip on both CLIs; the Python CLI also has the scene mix; see CLI
Mixing assistant (suggestMixScene / suggest_mix_scene)Yes (sonare_mixing_assistant_suggest). A build without BUILD_MIXING_ASSISTANT (default ON; forced ON in the full Node addon, forced off by SONARE_WASM_ANALYSIS_ONLY) keeps the entry points and answers NotSupported, so probe the capability catalog instead of the symbol. See Mixing Assistantsuggest-mix
Surround and multichannel mixingProject graph, project bounce and the realtime engine support 5.1/7.1 output; engine lanes pan into 5.1/7.1 group buses from the strip's surroundPan. The standalone Mixer.processStereo is stereo-only, and an engine track lane takes a stereo source only (see Channel Formats). See Surround and multichannelproject bounce writes 1/2/6/8 channels
Realtime engineYesNo
Engine lane mixer (lanes, buses, sends, channel strips) and MIDI clip scheduleYes, see Realtime EngineNo
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
Per-lane setTrackSends / setTrackOutputBus and offline Mixer.setOutputBusYes (sonare_engine_set_track_sends, sonare_engine_set_track_output_bus, sonare_mixer_set_output_bus)No
Live MIDI input, and external MIDI output with clock and transport forwardingYes, see MIDI Input. Browser worklets deliver lowered MIDI 1.0 messages through onMidiOutNo
Live program change (pushMidiProgram, pushMidiInputProgram)Yes (sonare_engine_push_midi_program). The SoundFont player, a NativeSynth bound with GM programs and a drum-kit NativeSynth (as the GS drum-set select) follow it; the built-in synth and other NativeSynth patches ignore itNo

Projects, instruments and MIDI ​

Feature familyLibrary bindingsCLI
Project and arrangement editing (headless DAW)Yes, see Project EditingYes
Typed automation targets (track fader and pan)Yes, see Project EditingNo
Audio-source owning metadata (contentHash / externalStemRole)Yes, see Project EditingNo
Built-in instruments (NativeSynth presets and patches)Yes, see Built-in Instrumentsproject bounce --synth <preset> pins a preset (project synth-presets lists them); a bare --synth follows GM programs
GM program following in a synth bounceC ABI use_gm_programs, Python auto_select_gm=, WASM and Node useGmPrograms; an explicit patch remains the fallbackBare --synth flag
Synth parameter descriptors (synthEngineParamInfo / synthPatchParamInfo) and patch engineParamsYes (sonare_synth_engine_param_info, sonare_synth_patch_param_info, SonareSynthPatch.engine_params)No
Sample bank (SampleBank, the sample engine's audio source)Yes, see Built-in InstrumentsPartial
Part rig (setPartRig / getPartRig / clearPartRig)Yes, see GM and GSNo
Composition assistC ABI and C++ only, by designNo
SoundFont 2 playerYes, see SoundFont 2 PlayerNo (Project API only)
External-instrument bounce protocol (ExternalInstrument)bounceWithInstruments / bounceWithInstrument on Node and WASM, bounce_with_instruments on Python, sonare_project_bounce_with_instruments on the C ABI; see Project BounceNo
Playback renderer (PlaybackRenderer, renderPlayback / render_playback)Yes; mono, stereo, 5.1, 7.1 or binaural output, see Playbackplayback on both CLIs

Rooms, files and browser-only ​

Feature familyLibrary bindingsCLI
Room acoustics (including roomGeometryFromEstimate, which turns an estimateRoom result into synthesizeRir input)Yes (sonare_room_geometry_from_estimate)acoustic [--ir], estimate-room, synthesize-rir, room-morph
Stereo room morph (roomMorphStereo / room_morph_stereo)Yes (sonare_room_morph_stereo)room-morph on both CLIs for a two-channel file
Capability catalog and build diagnosticscapabilityCatalog() / capability_catalog() and capabilities() on every surface; canonical JSON through the C ABIdoctor
File decodingNative: WAV/MP3 (FFmpeg builds add more). WASM: most APIs take decoded samples, Audio.fromMemory(...) decodes WAV/MP3 bytes, and browser decoding can read supported formatsSame as the native build
Multichannel decode and fold (decodeChannels / decode_channels, downmix)Yes (sonare_decode_channels, sonare_downmix); the fold rules are in Channel FormatsNo
Web MIDI bridge (bindWebMidi) and microphone helper (bindMicrophoneInput)WASM / browser onlyNo
OPFS clip write and import (writeOpfsClip, importOpfsClip)WASM / browser onlyNo
Stream analyzer as an audio-graph node (SonareStreamAnalyzerNode)WASM / browser only, in @libraz/libsonare/workletNo

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 objectoptions object or legacy positional paramspositional / keyword params
decomposeStemsLinked / decompose_stems_linkedrequest object with planar channelsrequest object with planar channelsplanar channel sequence or 2-D array, positional/keyword options
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)
Peak/RMS normalizerstwo functions, each selecting the statistic through mode: 'peak' | 'rms': normalize(...) (request object or positional) and normalizeStereo(request) (request only). The request takes validate?, and a mismatched channel pair is rejected in JavaScript with a RangeError before the call reaches the corethe same two functions and the same mode, but the stereo request carries no validate and no JavaScript-side length check — a mismatched pair comes back from the core as a SonareError with ErrorCode.InvalidParameterfour functions and no mode argument: normalize / normalize_rms for mono and normalize_stereo / normalize_rms_stereo for a pair, each taking validate= as a keyword

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
Normalizer defaults and resultsPeak normalization defaults to 0.0 dBFS and RMS normalization to -20.0 dBFS on WASM, Node, and Python, including the mono normalize and stereo normalizeStereo calls on Node and WASM. C ABI calls require an explicit target. A stereo call measures one level across the pair and applies one shared gain, preserving its balance. The result reports one appliedGainDb / applied_gain_db; a silent pair is unchanged with gain 0. Python's NormalizeStereoResult also carries the shared length
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 0–3) — 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 shared by Node, WASM, and Python
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
ABI version checksEvery binding checks the loaded library's interface versions itself; see ABI Versions
Parameters you do not giveIn the JavaScript and Python bindings a parameter you leave out is absent (undefined, None, an omitted field), never a -1 placeholder
Room band arraysbandAbsorption / bandScattering in JavaScript, band_absorption / band_scattering in Python, absorption_bands / scattering_bands in the C ABI and in CLI --json output
Edit positionsEvery edit request and the punch-in window accept seconds beside samples (startSec / endSec, onsetSec / offsetSec, timeOffsetSec, paddingSec; snake_case in Python). Giving both spellings is refused
ErrorsOne numeric code per failure on every binding. The class that carries it, and whether the binding refuses an argument before the library sees it, differ by runtime — see the error contract
WASM object returnsWASM arrays/objects returned by name-list helpers (*Names()), preset-name helpers, synthPresetPatch, section, and key-candidate helpers, plus mixer meter snapshots (meterTap, stripMeter, busMeter) and goniometer samples (readGoniometerLatest), are re-rooted into the caller's JavaScript realm, so they survive structuredClone() / postMessage() like ordinary objects. Handles with native methods, such as Mixer and StreamAnalyzer, remain non-cloneable; send their plain data results instead
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:

FamilyHelper
ChordsdetectChords / detect_chords
SectionsanalyzeSections / analyze_sections
TimbreanalyzeTimbre / analyze_timbre
DynamicsanalyzeDynamics / analyze_dynamics
RhythmanalyzeRhythm / analyze_rhythm

ABI Versions ​

Every binding can read the loaded library's interface versions. Four of them are packed into one aggregate value.

InterfaceVersionRead with
Feature (flat analysis and feature structs, the general C function contracts)7packed into abiVersion()
Project2packed; projectAbiVersion()
Voice changer3packed; voiceChangerAbiVersion()
Acoustic5packed
Aggregate0x05030207abiVersion(); EXPECTED_ABI_VERSION on WASM; sonare_abi_version() against the compile-time SONARE_ABI_VERSION in C
Engine3engineAbiVersion(); not part of the packed value, so each RealtimeEngine constructor checks it separately

Node, WASM and Python compare the packed value themselves and refuse a mismatch with AbiMismatch (10); the error contract says where each one raises it. Node and WASM export EXPECTED_PROJECT_ABI_VERSION. WASM also exports EXPECTED_ENGINE_ABI_VERSION, and its engineCapabilities().abiCompatible reports the engine comparison; the Node addon checks the engine ABI when a RealtimeEngine is constructed. A mismatched Python CLI exits 13. See also ABI Versions.

Error Contract ​

Every binding reports a failure with the same numeric code, the C ABI's SonareError value, so a handler can branch on the cause instead of the message text. Two things differ by runtime: the class that carries the code, and whether an argument is refused by the binding before the library sees it. The table lists each kind of failure once. In a coded row, WASM and Node throw a SonareError whose codeName is the name shown, and Python raises a SonareError whose .code is the ErrorCode member shown.

FailureCodeWASM and NodePythonC ABICLI exit
An argument of the wrong type, or a missing one—TypeErrorSonareValueError, code 4—2
An argument the binding refuses: empty or non-finite audio, a fractional number where an integer is read (a sample rate included), a name the binding resolves itself (keyRoot: 'H'), lengths that must match, a string with an embedded NUL—RangeErrorSonareValueError, code 4SONARE_ERROR_INVALID_PARAMETER2 or 3
A sample rate outside 8000–384000 Hz4RangeErrorINVALID_PARAMETERSONARE_ERROR_INVALID_PARAMETER3
A value the library refuses, such as nFft 0, an unknown preset name, or a scene that names an unknown insert4InvalidParameterINVALID_PARAMETERSONARE_ERROR_INVALID_PARAMETER3
A file that does not exist1FileNotFound (Node; WASM has no file paths)FILE_NOT_FOUNDSONARE_ERROR_FILE_NOT_FOUND4
A malformed project, scene or preset document2InvalidFormatINVALID_FORMAT; a project document raises it as SonareValueErrorSONARE_ERROR_INVALID_FORMAT5
Audio that will not decode3DecodeFailedDECODE_FAILEDSONARE_ERROR_DECODE_FAILED6, or 5 in a build without FFmpeg
An allocation failure5OutOfMemoryOUT_OF_MEMORYSONARE_ERROR_OUT_OF_MEMORY7
A feature this build does not carry6NotSupportedNOT_SUPPORTEDSONARE_ERROR_NOT_SUPPORTED8
A released, closed or uninitialised object, or a WASM call before init()7InvalidStateINVALID_STATESONARE_ERROR_INVALID_STATE9
A run canceled from its progress callback8CancelledCANCELLEDSONARE_ERROR_CANCELLED11
An output file that cannot be written9EncodeFailedENCODE_FAILEDSONARE_ERROR_ENCODE_FAILED12
Native code from a different build than the binding10AbiMismatch: Node when the addon loads, WASM in init()ABI_MISMATCH on first library usenever returned13 (Python CLI only)
Anything else99UnknownUNKNOWNSONARE_ERROR_UNKNOWN10

Reading the columns:

  • WASM and Node. TypeError and RangeError are the binding's own refusals and carry no code, so isSonareError does not match them; a handler that validates user input catches both shapes. Error Handling has the ErrorCode enum and a worked example.
  • Python. SonareValueError subclasses both SonareError and ValueError, so either except catches it, and its .code makes a branch on the code treat it like the library rejection it stands in for. Python passes an out-of-range sample rate on to the library, which is why that row is a coded SonareError there and a RangeError in JavaScript. See Python Exceptions.
  • C ABI. A function returning SonareError reports through its return value. A validation early return records no message, so sonare_error_message(code) is the readable text; a function returning a handle reports NULL and leaves the code in sonare_last_error_code() and the detail in sonare_last_error_message(). See C++ Error Handling.
  • CLI. Both CLIs use this mapping. Exit 2 is the argument parser's, so an argument it rejects exits 2 while one a command handler rejects exits 3. Exit 13 exists only in the Python CLI, because the native CLI links its library statically. SONARE_LEGACY_EXIT=1 folds every failure to 1. See Exit Codes.

The detail text differs by runtime even when the code agrees: a malformed scene reads failed to build mixer from scene JSON: … on WASM and Node and [2] failed to build mixer from scene JSON: … in Python, where the message carries the code as a prefix.

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, and generates the runtime capability matrix at tools/parity/surface-coverage.md.