Python API
libsonare provides Python bindings for scripts, notebooks, batch jobs, and local desktop tools. The package calls the native libsonare library through ctypes, so Python code can use the compiled C/C++ engine without building a custom extension. PyPI wheels are available for supported Linux and macOS targets.
What is ctypes (and the C API)?
libsonare's core is compiled C/C++. ctypes is Python's built-in way to call functions in a compiled shared library (.so/.dylib) directly, with no extra C extension to build. The Python package forwards your calls to the same native code the C++ library runs, so you get native speed from plain Python. ("C API" means the flat set of C functions Python calls under the hood.)
Use this page when you want scripts, notebooks, batch analysis, or local tools that can read files directly. The Python package is usually the easiest route if you are not building a browser UI. The reference continues on three sibling pages: Python Analysis API for the analysis and feature-extraction functions, Python Effects API for audio effects, room acoustics, and the realtime voice changer, and Python Types for the result and option types.
Python Mental Model
| Step | What happens |
|---|---|
| 1. Load audio | Audio.from_file(...) reads supported file formats into samples |
| 2. Inspect or process | Call detect_bpm, analyze, feature functions, editing DSP, mastering, or mixing APIs |
| 3. Use results | Print values, save JSON, render audio, or feed features into your own pipeline |
Most Python APIs accept raw sample arrays plus sample_rate. Raw samples are the decoded audio values, not an MP3 or WAV filename. The Audio object is a convenience for file-based workflows: load once, then call analysis or processing methods on the same object.
For a first script, keep it this small:
audio = sonare.Audio.from_file("song.mp3").- call
sonare.detect_bpm(audio.data, audio.sample_rate)orsonare.analyze(audio.data, audio.sample_rate); - print the result or save it as JSON.
Start with Audio or call functions directly
If your workflow begins with an audio file, start with Audio.from_file(...). If you already have samples from NumPy or another loader, call module-level functions such as detect_bpm(samples, sample_rate) directly.
How To Read This Reference
Read this page in three passes:
- If you are loading files, start with
Audio.from_file(...); if you already have samples, call the module-level functions directly. - Use Pick The Smallest API That Solves The Job to choose a function family instead of scanning the full reference.
- Return to Types only when you need exact attribute names, row-major matrix shapes, or JS parity aliases.
A single analyze(...) call returns the all-in-one analysis result — chords, sections, timbre, dynamics, rhythm, melody, form, and per-beat strength — matching the other bindings. Reach for the focused functions below when you only need one field or want per-call options.
Default sample rate varies by family
Music-analysis and metering helpers default to sample_rate=22050; room-acoustic helpers (analyze_impulse_response, detect_acoustic, estimate_room) default to 48000. When you load with Audio.from_file(...), always pass audio.sample_rate so the per-family default never silently applies to audio recorded at a different rate. An impulse response (IR) here is a recording of how a space responds to one short burst of sound.
Pick The Smallest API That Solves The Job
| You need | Start with | Why |
|---|---|---|
| A script that reads files and prints metadata | Audio.from_file(...) + detect_bpm / detect_key / analyze | Python handles decoding and keeps the code short |
| Detailed music analysis | analyze_bpm, detect_chords, analyze_sections, analyze_timbre, analyze_dynamics, analyze_rhythm | These run a single facet of analysis with extra parameters; analyze(...) already returns all of these fields in one AnalysisResult |
| Feature arrays for notebooks or ML | mel_spectrogram, mfcc, chroma, cqt, vqt, chroma_cqt, nnls_chroma | Returns plain Python lists / result objects that can be converted to NumPy if desired |
| Editing a clip | time_stretch, pitch_shift, pitch_correct_to_midi, note_stretch, voice_change, RealtimeVoiceChanger | These transform the signal itself |
| Mastering a file | master_audio, mastering_chain, StreamingMasteringChain | Presets first, explicit chain config when you need control |
| Live or chunked analysis | StreamAnalyzer | Feed audio blocks, drain feature frames, and read BPM/key/chord estimates that update as more audio arrives |
| Stem mixing | mix_stereo or Mixer.from_scene_json(...) | One-shot arrays first; scene mixer for sends, buses, automation, and meters |
| Room decay, clarity, equivalent-room estimates, or generated room character | analyze_impulse_response, detect_acoustic, estimate_room, synthesize_rir, room_morph | These describe or apply the room, not the song |
Installation
Requires Python 3.11 or later (3.11, 3.12, 3.13).
pip install libsonareThis also installs the sonare CLI command. See CLI Reference for details.
Default PyPI wheels decode WAV and MP3. Use libsonare.has_ffmpeg_support() to check the loaded build. If you need direct M4A/AAC/FLAC/OGG/Opus decoding, clone the repository, build an FFmpeg-enabled wheel, and install that wheel:
git clone https://github.com/libraz/libsonare.git
cd libsonare
SONARE_FFMPEG=1 bash bindings/python/build_wheel.sh
python3 -m pip install bindings/python/dist/*.whlFFmpeg-enabled builds require FFmpeg development libraries. On macOS, install them with brew install ffmpeg. On Debian/Ubuntu, install libavformat-dev libavcodec-dev libavutil-dev libswresample-dev.
Building from Source (alternative)
If pre-built wheels are not available for your platform, you can build from source:
git clone https://github.com/libraz/libsonare.git
cd libsonare
cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED=ON
cmake --build build -j
cd bindings/python
pip install -e .Requirements for building from source:
- Python 3.11+
- CMake 3.16+
- C++17 compiler (GCC or Clang on the supported Linux/macOS targets)
- Optional FFmpeg development libraries when building with
SONARE_FFMPEG=1
Quick Start
from libsonare import Audio, analyze, detect_bpm, detect_key, detect_beats
# Load audio from file
audio = Audio.from_file("music.mp3")
# Individual analysis
bpm = detect_bpm(audio.data, audio.sample_rate)
key = detect_key(audio.data, audio.sample_rate)
beats = detect_beats(audio.data, audio.sample_rate)
# All-in-one analysis
result = analyze(audio.data, audio.sample_rate)
print(f"BPM: {result.bpm} ({result.bpm_confidence:.0%})")
print(f"Key: {result.key}")
print(f"Time Signature: {result.time_signature}")
print(f"Beats: {len(result.beat_times)} detected")Error handling
Two exception classes cover every failure, plus a plain TypeError for an argument of the wrong type:
| Raised when | Class | Message |
|---|---|---|
| The native library returns a non-OK code | SonareError, a RuntimeError subclass | Carries a [4] numeric prefix |
| The binding or an object's state refuses the call: the loaded library lacks the entry point, a closed or released object is used, a native result fails a sanity check | SonareError with NOT_SUPPORTED, INVALID_STATE or UNKNOWN | Same [code] prefix; never a bare RuntimeError |
| The loaded native library does not match the binding | SonareError with ABI_MISMATCH (10); the sonare CLI exits 13 | Raised on first library use, before any call is made |
An argument is refused: Python-side validation rejects it before the C ABI, or the library refuses an unknown or out-of-range id, name or value on Project, RealtimeEngine or transcribe | SonareValueError | Plain validation text, no numeric prefix |
A numeric argument or field gets something that is not a number, such as a string or a bool | TypeError | Names the field and the interval the C type holds |
SonareValueError subclasses both SonareError and ValueError, so except ValueError: and except sonare.SonareError: each catch it and neither style of handler has to know which class a given entry point picks. A fractional, non-finite or out-of-range number is a SonareValueError; a non-number is a TypeError, which neither handler catches. Its .code is ErrorCode.INVALID_PARAMETER for an argument refusal, and the C-ABI code for a project document that fails to load (INVALID_FORMAT for malformed JSON), so code that branches on the code treats it exactly like the C-ABI rejection it stands in for. SonareError.code holds the same C-ABI values the JS bindings expose as ErrorCode, and .code_name gives the cross-binding spelling. The error contract lists which failure takes which class and code here, on the other bindings, and as a CLI exit code. A library whose ABI version differs from the one the package was built for, or which lacks a symbol the binding needs, fails with ABI_MISMATCH and a message beginning libsonare ABI mismatch; Installation covers the fix.
try:
result = sonare.master_audio_stereo(left, right, sample_rate=48000, preset_name="pop")
except sonare.SonareError as e:
print(e.code, e.code_name, e)Arguments are checked before the call
Buffer-taking entry points preflight their arguments, so an empty, mis-sized, or non-finite buffer is reported against the function you called rather than against an internal helper or a bare C symbol:
master_audio_stereo: right must not be empty
spectral_centroid: samples contains NaN or Inf at index 0The names in the message are the ones in your call: cross_similarity reports x and x_rows, not the internal data / rows spelling, and the scalar and stereo meters name the facade function (metering_peak_db) rather than the sonare_-prefixed C symbol. On entry points that expose validate, passing validate=False still skips the O(n) NaN/Inf scan, but not the emptiness check.
If you match on message text
Handlers that catch SonareError or ValueError, or that branch on .code, need no change. Code that matches the text of a validation message does: the wording names the entry point and the argument.
Input some entry points refuse
trim_silence,split_silenceandfix_framesraise on an empty buffer instead of returning an empty result.tempogram_ratiorejects afactorsentry that is not finite and positive. A NaN reaches an undefined float-to-int cast in the core, and an infinity degenerates silently to the DC lag.mix_stereorefuses a scene it cannot mix — strips that are all empty, or a strip carrying NaN or Inf, named by strip index and channel (mix_stereo: strips[1] right contains NaN or Inf at index 0). A zero-frame block stays a valid no-op at the C ABI, where it means "process this block" rather than "mix these strips".
What is not checked
The element-wise conversions keep their empty-in / empty-out contract: power_to_db, amplitude_to_db, db_to_power, db_to_amplitude, preemphasis, deemphasis, vector_normalize, frame_signal, pad_center and fix_length accept an empty sequence and return one. Among the tempo helpers only tempogram_ratio guards its input matrix; tempogram, fourier_tempogram, cyclic_tempogram and plp do not.
f0_hz is never scanned for non-finite values. pYIN marks an unvoiced frame with nan, and that track representation has to pass straight into pitch correction, so note_segments, extract_notes, decompose_note_pitch, split_note and merge_notes check its shape but not its values.
"Not given" is None
An optional numeric argument that has no value is left out or passed as None; -1 is not a spelling of "not given" and is refused with SonareValueError. This covers the engine's render_frame on every transport, parameter and MIDI call (None applies at the next block head), SpectralRegionOp.end_sample (None runs to the end of the signal), fix_frames(x_max=...), set_program(bank=...) and set_program_on_channel(bank=...) (None emits no Bank Select), the percentiles of metering_dynamic_range and Audio.dynamic_range, and the gamma of vqt and vqt_to_audio (None selects the automatic bandwidth). The C ABI keeps -1 as its own spelling.
Argument-parser usage errors in the sonare CLI (an unknown flag, a missing argument, an integer option beyond int32 or a float option beyond float32) exit with code 2. A command that rejects a value raises a plain ValueError, not SonareValueError, because it reports a command-line mistake rather than an API argument; it exits with 3, the same code as a SonareValueError. An ABI mismatch exits with 13; see CLI exit codes.
What this build can do
capabilities() returns the build-diagnostics report sonare doctor prints, and capability_catalog() returns the machine-readable processor/parameter/preset catalog validated against schemas/capability-catalog.schema.json.
import libsonare as sonare
caps = sonare.capabilities()
if not caps["features"]["ffmpeg"]:
print("This wheel decodes:", caps["decode"]["builtin"])
catalog = sonare.capability_catalog()
for processor in catalog["processors"]:
for param in processor["params"]:
# min / max / default are None when the core does not declare a bound.
build_slider(processor["id"], param)Branch on the catalog rather than on a hand-maintained table: a parameter's range, default, unit, and realtime-safety all come from the loaded build. The same data is capabilities() / capabilityCatalog() on Node and WASM.
catalog["masteringPresets"] lists built-in mastering and restoration presets. Each entry has name, kind, targetLufs, truePeakCeilingDb, and maxLimiterGainReductionDb; restoration entries set the three numeric fields to None. Processor entries also expose conditional parameter groups in slots, with name, parent, activation, and minCrossoverCutoffs fields.
Canceling a long call
Analysis and mastering calls that report progress also take cancel, a predicate polled at the same native boundaries. Return True and the call aborts with SonareError code 8 (SONARE_ERROR_CANCELLED), leaving no partial output.
import threading
stop = threading.Event()
try:
result = sonare.master_audio(
samples,
sample_rate,
preset_name="pop",
on_progress=lambda p, stage: print(stage, p),
cancel=stop.is_set,
)
except sonare.SonareError as e:
if e.code == 8:
print("cancelled")
else:
raiseMastering API
Python exposes the same named mastering processors as the browser demo. Use the name-list helpers to inspect the active build, then call mono, stereo, pair, or analysis APIs with explicit parameters. A full chain runs its stages in a fixed order:
Pick an entry point by purpose:
| Purpose | Function |
|---|---|
| Apply simple loudness mastering | mastering() |
| List built-in mastering presets | mastering_preset_names() |
| Get a built-in preset's flat chain parameters | mastering_preset_params(preset) |
| Apply a preset to mono audio | master_audio() |
| Apply a preset to stereo audio | master_audio_stereo() |
| Run a full mono chain | mastering_chain() |
| Run a full stereo chain | mastering_chain_stereo() |
| Run a streaming chain (block-by-block) | StreamingMasteringChain |
| Generate an audio profile for mastering decisions | mastering_audio_profile() |
| Generate an audio profile from both stereo channels | mastering_audio_profile_stereo() |
| Generate assistant suggestions from source analysis | mastering_assistant_suggest() |
| Generate assistant suggestions from both stereo channels | mastering_assistant_suggest_stereo() |
| Get the assistant's suggestion as chain parameters instead of JSON | mastering_assistant_suggest_chain(), mastering_assistant_suggest_chain_stereo() |
| Preview delivery loudness by platform | mastering_streaming_preview() |
| Preview delivery loudness from both stereo channels | mastering_streaming_preview_stereo() |
| List the built-in delivery platforms | mastering_platform_names() |
| List mono/stereo processors | mastering_processor_names() |
| Get machine-readable processor classifications | mastering_processor_catalog() |
| List the guitar-amp presets with their parameters | mastering_amp_preset_catalog() |
| List chain insert processors | mastering_insert_names() |
| List the parameter keys an insert accepts | mastering_insert_param_names(name) |
| Describe insert construction keys and automation targets | mastering_insert_param_info(name) |
| Get configured insert latency and tail | mastering_insert_timing(name, params, sample_rate) |
| Process mono audio | mastering_process() |
| Process stereo audio | mastering_process_stereo() |
| List pair processors | mastering_pair_processor_names() |
| Process source/reference pair | mastering_pair_process() |
| Process stereo source/reference pair | mastering_pair_process_stereo() |
| List pair analyses | mastering_pair_analysis_names() |
| Analyze source/reference pair | mastering_pair_analyze() |
| Match source loudness to a reference | mastering_ab_match_loudness() |
| Match stereo source loudness to a stereo reference | mastering_ab_match_loudness_stereo() |
| List stereo analyses | mastering_stereo_analysis_names() |
| Analyze stereo channels | mastering_stereo_analyze() |
Related mastering guides: Preset selection, Delivery targets, Meter reading, Quality checklist.
Run one processor
import json
import libsonare as sonare
print(sonare.mastering_processor_names())
# e.g. ['dynamics.compressor', 'eq.parametric', 'spectral.airBand', 'stereo.imager', ...]
result = sonare.mastering_process(
"spectral.airBand",
samples,
sample_rate=sample_rate,
params={
"amount": 0.4,
"shelfFrequencyHz": 14000,
},
)
report = sonare.mastering_stereo_analyze(
"stereo.monoCompatCheck",
left,
right,
sample_rate=sample_rate,
)
print(json.loads(report))
catalog = sonare.mastering_processor_catalog()
insert_params = sonare.mastering_insert_param_info("eq.parametric")Presets and full chains
# Preset-driven chain (one-shot)
sonare.mastering_preset_names()
# -> ['pop', 'edm', 'acoustic', 'hipHop', 'aiMusic', 'speech', 'streaming', 'youtube', 'broadcast', 'podcast', 'audiobook', 'cinema', 'jpop', 'ambient', 'lofi', 'classical', 'drumAndBass', 'techno', 'metal', 'trap', 'rnb', 'jazz', 'kpop', 'trance', 'gameOst', 'vinyl', 'tapeHiss', 'fieldRecording', 'voiceMemo', 'shellac78']
preset_params = sonare.mastering_preset_params("aiMusic")
chain_result = sonare.master_audio(
samples,
sample_rate=sample_rate,
preset_name="aiMusic",
overrides={
"loudness.targetLufs": -13,
"maximizer.truePeakLimiter.releaseMs": 50,
"maximizer.truePeakLimiter.applyGainAtInputRate": False,
},
)
print(chain_result.output_lufs, chain_result.output_true_peak_dbtp, chain_result.output_lra)
print(chain_result.stage_gain_reductions)
if chain_result.report is not None:
print(chain_result.report.before, chain_result.report.after)
print(chain_result.report.band_energy_delta_db)Streaming chain
# Block-by-block streaming variant
with sonare.StreamingMasteringChain({
"eq.tilt.tiltDb": 0.5,
"dynamics.compressor.thresholdDb": -20.0,
}) as chain:
chain.prepare(sample_rate=48000, max_block_size=512, num_channels=1)
print(chain.stage_names(), chain.latency_samples) # latency_samples is a property
output = chain.process_mono([0.0] * 512)
while True:
tail = chain.flush_mono()
if not tail:
break
output.extend(tail)After prepare(), chain.set_parameter(key, value) changes one streaming-safe parameter at a block boundary. The call is serialized with processing, flushing, resetting, and preparing; unknown keys, structural keys, and keys for disabled stages are rejected before mutation, so a failed call leaves the chain and its audio history unchanged. Parameter changes take effect without automatic smoothing.
After the final streaming input block, call flush_mono() until it returns an empty list; for stereo, call flush_stereo() until it returns two empty lists.
Assistant, profile and preview
profile = json.loads(sonare.mastering_audio_profile(samples, sample_rate=sample_rate, params={
"n_fft": 2048,
"hop_length": 512,
"true_peak_oversample": 4,
}))
suggestions = json.loads(sonare.mastering_assistant_suggest(samples, sample_rate=sample_rate, params={
"target_lufs": -14,
"ceiling_db": -1,
"prefer_streaming_safe": True,
}))
preview = json.loads(sonare.mastering_streaming_preview(samples, sample_rate=sample_rate, platforms=[
{"name": "YouTube", "targetLufs": -14, "ceilingDb": -1},
{"name": "Podcast", "targetLufs": -16, "ceilingDb": -1},
]))
# Stereo entry points: same params, same JSON, measured from both channels.
stereo_profile = json.loads(sonare.mastering_audio_profile_stereo(left, right, sample_rate=sample_rate, params={
"n_fft": 2048,
"hop_length": 512,
"true_peak_oversample": 4,
}))
stereo_suggestions = json.loads(sonare.mastering_assistant_suggest_stereo(left, right, sample_rate=sample_rate, params={
"target_lufs": -14,
"ceiling_db": -1,
"prefer_streaming_safe": True,
}))
# Omitting platforms uses the built-in Spotify / Apple Music / YouTube set.
stereo_preview = json.loads(sonare.mastering_streaming_preview_stereo(left, right, sample_rate=sample_rate))Each of the three JSON helpers has a _stereo sibling that takes left and right instead of one buffer, accepts the same params / platforms, and returns the same JSON string. Use it whenever you have two channels: the mono entry points measure a 0.5 * (left + right) downmix, which reads about 6 dB low on decorrelated material and pulls integrated loudness, the normalization gain derived from it, and the ceiling-risk judgment down by the same amount.
Why the stereo entry points matter
On a decorrelated pink-noise pair (48 kHz, 4 s) the downmix path reports -22.55 LUFS against -16.44 LUFS from the stereo path, a 6.11 dB gap that turns a Spotify normalizationGainDb of +2.44 into +8.55. A correlated pair differs by only 3.01 dB, the plain downmix 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 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 on the downmix and remain directly comparable with the mono call.
Options and result fields
| Entry point / result | Keys | Notes |
|---|---|---|
mastering_audio_profile(params=) | n_fft, hop_length, true_peak_oversample | All optional. |
mastering_assistant_suggest(params=) | target_lufs, ceiling_db, enable_repair, prefer_streaming_safe, speech_mono_amount | camelCase aliases also work through the shared native parser. |
mastering(...) | release_ms, apply_gain_at_input_rate | Limiter release and static-gain staging; release_ms=0 keeps the 50 ms library default. Preset and chain overrides use the flat keys "maximizer.truePeakLimiter.releaseMs" and "maximizer.truePeakLimiter.applyGainAtInputRate", applied as given. |
| Offline chain or preset result | output_true_peak_dbtp, output_lra, stage_gain_reductions | True peak at the configured loudness oversample factor; output_lra is the EBU R128 loudness range in LU; each reduction names the dynamics or maximizer stage and its most recent gain reduction in dB (zero or negative). |
result.report | before, after, band_energy_delta_db | before and after each hold integrated_lufs, max_momentary_lufs, max_short_term_lufs, true_peak_dbtp and loudness_range. band_energy_delta_db is the 32-band logarithmic spectral-energy change. The report also holds the applied gain, the maximum gain reduction, and whether peak headroom limited the loudness target. |
Reference-track processing
Reference-track workflows use mastering_pair_processor_names(), mastering_pair_process(), mastering_pair_process_stereo(), mastering_pair_analysis_names(), and mastering_pair_analyze(). The mono pair processor accepts mastering_pair_process(processor_name, source, reference, sample_rate=22050, params=None, *, reference_sample_rate=None) and returns a MasteringResult; source and reference lengths may differ. The stereo form accepts source_left, source_right, reference_left, and reference_right and returns a MasteringStereoResult with left, right, sample_rate, input_lufs, output_lufs, applied_gain_db, latency_samples, loudness_target_limited, and non_finite_substitution_count. Each source pair and each reference pair must have equal left/right lengths, but the source and reference pair lengths may differ; the stereo match.abCrossfade result uses the shorter pair length. Every pair processor, pair analysis and A/B match takes reference_sample_rate (default sample_rate): a reference at another rate is resampled onto the source's, and a delay estimate is reported in source-rate samples.
For loudness-matched A/B comparisons, mastering_ab_match_loudness(source, reference, sample_rate=22050, *, with_match=True, reference_sample_rate=None) returns (np.ndarray, LoudnessMatch | None). mastering_ab_match_loudness_stereo(source_left, source_right, reference_left, reference_right, sample_rate=22050, *, with_match=True, reference_sample_rate=None) returns ((np.ndarray, np.ndarray), LoudnessMatch | None). Each stereo source/reference pair must have equal channel lengths, while source and reference lengths may differ. The stereo helper applies one shared gain to both source channels and does not apply a ceiling.
Standalone dynamics and repair
Every named stage is also a one-shot module-level function, so you can run a single processor without assembling a chain. Parameters are keyword-only and mirror the corresponding MasteringChainConfig keys in snake_case. The dynamics processors return (processed_samples, latency_samples), where latency_samples is an int; the repair processors return processed samples (np.ndarray).
| Function | Returns | Key parameters |
|---|---|---|
mastering_dynamics_compressor(samples, sample_rate?, *, ...) | tuple[np.ndarray, int] | threshold_db=-18.0, ratio=2.0, attack_ms=10.0, release_ms=100.0, knee_db, makeup_gain_db, auto_makeup, detector='rms', sidechain_hpf_enabled, sidechain_hpf_hz, pdr_time_ms, pdr_release_scale |
mastering_dynamics_gate(samples, sample_rate?, *, ...) | tuple[np.ndarray, int] | threshold_db=-50.0, attack_ms=2.0, release_ms=80.0, range_db=-80.0, hold_ms, close_threshold_db, key_hpf_hz |
mastering_dynamics_transient_shaper(samples, sample_rate?, *, ...) | tuple[np.ndarray, int] | attack_gain_db=3.0, sustain_gain_db, fast_attack_ms, fast_release_ms=20.0, slow_attack_ms=15.0, slow_release_ms=200.0, sensitivity=1.0, max_gain_db=12.0, gain_smoothing_ms, lookahead_ms |
mastering_repair_declick(samples, sample_rate?, *, ...) | np.ndarray | threshold=0.8, neighbor_ratio=4.0, max_click_samples=8, lpc_order=20, residual_ratio=8.0 |
mastering_repair_declip(samples, sample_rate?, *, ...) | np.ndarray | clip_threshold=0.98, lpc_order=36, iterations=2, lpc_blend=0.65 |
mastering_repair_decrackle(samples, sample_rate?, *, ...) | np.ndarray | threshold=0.4, mode='median', levels=4 |
mastering_repair_dehum(samples, sample_rate?, *, ...) | np.ndarray | fundamental_hz=50.0, harmonics=4, q=20.0, adaptive, search_range_hz, adaptation, frame_size, pll_bandwidth, mode='subtract' |
mastering_repair_denoise_classical(samples, sample_rate?, *, ...) | np.ndarray | mode='logMmse', noise_estimator='quantile', n_fft=1024, hop_length=256, dd_alpha=0.98, reduction_db=26.0, over_subtraction=2.0, spectral_floor=0.05, noise_estimation_quantile=0.1, speech_presence_gain, gain_smoothing |
mastering_repair_dereverb_classical(samples, sample_rate?, *, ...) | np.ndarray | threshold=0.0, attenuation=1.0, n_fft=1024, hop_length=256, t60_sec=0.4, late_delay_ms=50.0, over_subtraction=1.0, spectral_floor=0.08, wpe_enabled, wpe_iterations, wpe_taps, wpe_strength |
mastering_repair_trim_silence(samples, sample_rate?, *, ...) | np.ndarray | threshold=0.001, padding_samples or padding_sec (one of them), mode='peak', gate_lufs=-60.0, window_ms=400.0 |
The whole-signal repair stages (declick, declip, decrackle, dehum, dereverb) are rejected by StreamingMasteringChain. Run them with these one-shot helpers or inside mastering_chain* / master_audio*. In the realtime engine, decrackle, dehum, the classical denoiser and the classical dereverb are also available as inserts under their catalog ids; declick and declip stay offline.
Each repair helper also has a _stereo form, and the denoiser and dereverb a _linked form that treats the channels as one program. The mastering_repair_detect_* functions measure a defect (clicks, noise floor, clipping, crackle, hum, reverb, trim range) without changing the audio, and mastering_repair_dereverb_config_for_room(estimate) turns a RoomEstimate into a complete dereverb config.
To repair audio of any channel count in two calls, mastering_repair_analyze(channels, sample_rate) measures the defects per channel and returns the stages the assistant would run (recommended, ready to pass on; dereverb is never recommended), and mastering_repair_apply(channels, sample_rate, stages=[...]) runs them. Stages always run in the fixed order declip, declick, decrackle, dehum, denoise, dereverb whatever order the list gives; a repeated stage or an unknown setting is refused. The result carries the repaired channels and one report per stage (per channel, or once for the linked denoise and dereverb), and on_progress / cancel work as in Progress callbacks.
analysis = sonare.mastering_repair_analyze([left, right], 48000)
result = sonare.mastering_repair_apply([left, right], 48000, stages=analysis["recommended"])streaming_loudness_gain(samples, sample_rate, config) and streaming_loudness_gain_stereo(left, right, sample_rate, config) measure, at the loudness stage's input, the numbers a StreamingMasteringChain is constructed with. The returned StreamingLoudnessGain has loudness_static_gain_db (equal to the gain the offline chain applies, ceiling clamp included; 0 for silence), true_peak_db and integrated_lufs; pass the first two as the constructor's loudness_static_gain_db and loudness_static_gain_peak_db. See Dynamics and Repair.
Progress callbacks
mastering_chain(), mastering_chain_stereo(), master_audio(), and master_audio_stereo() accept an optional on_progress=callable keyword.
The callback receives (progress: float, stage: str) after each stage:
| Value | Meaning |
|---|---|
progress | Overall progress from 0.0 to 1.0. |
stage | The named processor that just completed, such as eq.tilt, dynamics.compressor, or loudness.targetLufs. |
Use it to drive UI progress bars or to log per-stage timing.
def on_step(progress: float, stage: str) -> None:
print(f"{progress:5.1%} {stage}")
result = sonare.mastering_chain(
samples,
sample_rate=sample_rate,
config={"loudness": {"targetLufs": -14, "ceilingDb": -1}},
on_progress=on_step,
)Config keys and insert descriptors
A config dict reaches the arbitrary-band multiband compressor too: dynamics.multibandComp takes crossover (cutoffsHz, slope, mode, firKernelSize) and bands (one dict per band), or the flat keys dynamics.multibandComp.crossover.cutoffsHz.<i> and dynamics.multibandComp.bands.<i>.<field> in overrides. The band count is 2 to 64, and cutoffsHz replaces the cutoff list and sets the band count to its length plus one. An enum-valued key takes its choices name beside the number ("repair.denoise.noiseEstimator": "spp", dynamics.compressor.detector, the multiband slope, mode and detector keys); mastering_process, mastering_process_stereo and mastering_insert_timing params accept the same names. A streaming chain with repair.denoise.enabled defaults the estimator to spp and refuses quantile by name.
mastering_insert_param_info(name, sample_rate=None) returns descriptors for the insert's construction keys and realtime automation targets, including their type, bounds, default, choices, conditional slot, and the dependsOn / coupledWith siblings that move a bound; with a sample_rate, a Nyquist-bounded ceiling is resolved for that rate, and every catalog id answers, offline repair stages included. mastering_insert_timing(name, params, sample_rate) builds that configured insert and returns its latencySamples and tailSamples; keys the insert does not read are rejected. params takes every value type the descriptor declares (numbers, booleans, enum names, strings, number lists); a value of the wrong type is a TypeError and a non-finite number a SonareValueError. mastering_preset_params(preset) returns the preset's flat override map, which can be passed to master_audio(..., overrides=...) unchanged.
Mixing API
Python also exposes the libsonare mixing engine. Use mix_stereo(...) for one-shot stem rendering, or keep a Mixer loaded from scene JSON when you need sends, buses, automation, meters, and scene serialization. List the built-in scene presets with mixing_scene_preset_names().
import libsonare as sonare
print(sonare.mixing_scene_preset_names())
scene_json = sonare.mixing_scene_preset_json("vocalReverbSend")
offline = sonare.mix_stereo(
[(vocal_l, vocal_r), (music_l, music_r)],
sample_rate=48000,
input_trim_db=[3, 0],
fader_db=[-3, -12],
pan=[0, -0.2],
width=[1, 0.9],
)
with sonare.Mixer.from_scene_json(scene_json, sample_rate=48000, block_size=512) as mixer:
print(mixer.scene_warnings()) # non-fatal: insert params no processor reads (typos)
print(mixer.latency_samples()) # compiled graph latency for dry/wet alignment
block = mixer.process_stereo([vocal_block_l, music_block_l], [vocal_block_r, music_block_r])
meter = mixer.strip_meter(0, tap="postFader")
mixer.schedule_fader_automation(0, 48000 * 8, -6, curve="s-curve")mixer.process_stereo(...) returns a MixerStereoResult named tuple with .left and .right (list[float]) and .sample_rate (int), mirroring the Node/WASM {left, right, sampleRate} shape. Fewer input channels than strips leave the remaining strips silent; more input channels than strips raise SonareValueError naming both counts.
Mixer.set_pan_law(...) and RealtimeEngine.set_track_strip_pan_law(...) accept a PanLaw enum, an integer ordinal, or a case-insensitive string alias. Accepted spellings include const3db, const-3db, -3db, const4.5db, const-4.5db, -4.5db, const6db, const-6db, -6db, linear0db, linear-0db, linear, and 0db; underscores are treated like hyphens.
See Mixing Engine for routing concepts, scene presets, and real-time notes.
Projects, Instruments & Live MIDI
The headless-DAW API is available in Python as well: author arrangements with Project, render them through the built-in instruments, and drive the realtime engine with live MIDI. The dedicated guides carry the depth — this is the Python entry-point map.
| Task | API | Guide |
|---|---|---|
| Author tracks, clips, tempo, markers, undo/redo | Project (a context manager — use with) | Project Editing |
| Render MIDI through the built-in synthesizer | Project.bounce_with_synth_instrument(...), synth_preset_names(), synth_preset_patch(name), SynthPatch | Built-in Synthesizer, Bouncing Projects |
| Render MIDI through a SoundFont | Project.load_soundfont(data), Project.bounce_with_sf2_instrument(...) | SoundFont Player |
| Host your own instrument during a bounce | Project.bounce_with_instruments(...) with the ExternalInstrument protocol — a render(channels, num_frames) callback plus optional prepare/on_event hooks and latency_samples. Node and WASM have the callback form as bounceWithInstruments / bounceWithInstrument. | Bouncing Projects |
| Play instruments live from MIDI events and replace destination MIDI FX | RealtimeEngine.set_synth_instrument(...), RealtimeEngine.load_soundfont(...), RealtimeEngine.set_midi_fx(...), the engine's MIDI input queue (each push takes offset_in_block_samples, an offset into the next processed block rather than an absolute clock position), and push_midi_program(...) / push_midi_input_program(...) (a program change with an optional bank in one call; the SoundFont player and a synth bound with GM programs follow the bank) | MIDI Input |
| Schedule MIDI clips into the live engine, sample-accurately | RealtimeEngine.set_midi_clips([...]) with EngineMidiClipSchedule / EngineMidiEvent, RealtimeEngine.sample_at_ppq(ppq) — ppq is pulses per quarter note, the musical-time unit | Realtime Engine |
| Set per-track cue monitoring | RealtimeEngine.set_track_monitor_mode(lane_index, mode, render_frame=None) with EngineTrackMonitorMode (off/pfl/afl — PFL listens pre-fader, AFL post-fader). process() folds an armed cue into the program output; offline renders, bounces and freezes never include it | Realtime Engine |
| Send a destination to external MIDI hardware | set_midi_destination_external(...), set_external_midi_clock_enabled(...), drain_external_midi(...), external_midi_dropped_count() | Project Editing: MIDI |
| Mix and automate the engine's tracks live | Lane/strip methods plus set_track_sends(track_id, sends), set_track_output_bus(track_id, bus_id), set_bus_strip_insert_param_by_name(...), set_bus_strip_insert_bypassed(...), resolve_track_insert_automation_id(...), resolve_master_insert_automation_id(...), resolve_bus_insert_automation_id(...), and set_param_smoothing_ms(...) | Realtime Engine |
| Read wide meters and scopes | drain_meter_telemetry_wide(...), configure_scope_telemetry(...), drain_scope_telemetry(...) | Realtime Engine |
import libsonare as sonare
with sonare.Project() as project:
project.set_sample_rate(48000)
track = project.add_track(kind="midi")
# ... add clips and MIDI events (see the Project Editing guide) ...
audio = project.bounce_with_synth_instrument("e-piano", num_channels=2)Every object that owns native memory (Audio, Project, Mixer, RealtimeEngine, StreamAnalyzer, RealtimeVoiceChanger, StreamingMasteringChain, StreamingEqualizer, and so on) has close() and works as a context manager. Calling a method after close() raises SonareError with INVALID_STATE.
For synth preset introspection, synth_preset_patch(name) returns a named catalog preset as a SynthPatch (it raises SonareError for unknown names and accepts a 'va:' routing prefix) so you can inspect and tweak fields before binding it. synth_enum_tables() returns the runtime enum-name tables (dict[str, tuple[str, ...]]) for validating SynthModRouting source/destination names against the loaded build. Besides the engine mode, waveform, filter and modulation tables it carries sample_loop_modes, sample_key_tracks, retriggers, lfo_shapes, part_rig_modes and sample_desc_loop_modes.
Every numeric SynthPatch field defaults to None, meaning "keep the base preset's value". A field you never set therefore reads back as None, not 0.0, and any value you do supply is a real override — including an explicit 0, so SynthPatch(preset="warm-pad", amp_sustain=0) genuinely zeroes the amp sustain instead of being indistinguishable from leaving it unset. A patch obtained from synth_preset_patch(name) comes back fully populated with the preset's concrete values, so its numeric fields are never None. Enum fields (engine_mode, waveform, filter_model, filter_output, body, retrigger, lfo_shape) keep 0 / "default" as their keep-the-base value. mod_routings=None keeps the base mod matrix, an empty tuple clears it, and a non-empty tuple replaces it. lfo_shape is "triangle", "sine" or "random" (a smoothed sample-and-hold seeded per voice) and applies to the vibrato and tremolo LFOs at every destination; filter_model="svf-4-pole" is an unsaturated 24 dB/octave lowpass. A numeric field that is non-finite or outside the range synth_patch_param_info() publishes is refused by name, not clamped.
Opaque assist sidecars
Project can carry per-project, undoable, module-owned opaque byte blobs (assist sidecars), scoped by module ID, target track, and a region. Set one with project.set_assist_sidecar(module_id, payload, *, schema_version=0, target_track_id=0, region_start_ppq=0.0, region_end_ppq=0.0); read them back with project.assist_sidecar_count(), project.get_assist_sidecar(index) -> AssistSidecar, and project.assist_sidecars(). See Project Editing for the cross-binding details.
More entry points
Functions and types for areas covered by other pages; the full signatures are in the type stubs shipped with the package.
| Task | API | Guide |
|---|---|---|
| Label detected chords with Roman numerals and functions without re-detecting | chord_functions(chords, key) returning FunctionalChord entries | Analysis |
| Let the analysis measure the tuning, and convert it to Hz | tuning="auto" on analyze, detect_chords, detect_key (KeyDetection carries the tuning used); reference_hz="auto" on transcribe; tuning_to_reference_hz, reference_hz_to_tuning | Audio To Notes |
| Tune a monophonic take to a detected or named key | auto_tune(samples, sample_rate, key="detect") returning AutoTuneResult (Python Effects API); scale_mask_for_mode(root, mode) (Python Analysis API) | Python Effects API |
| Invert a Mel or MFCC result without restating its parameters | mel_result_to_stft, mel_result_to_audio, mfcc_result_to_mel, mfcc_result_to_audio | Inverse Features |
| Convert a detected chord or key into an arrangement annotation | chord_symbol_from_analysis(chord), key_mode_from_analysis(mode) | Project Editing: MIDI |
Turn a room estimate into synthesize_rir input | room_geometry_from_estimate(estimate, source=..., listener=...) returning RoomGeometry (with source, listener and absorption derived from the estimate unless overridden) | Acoustic Analysis |
| Keep every decoded channel, or fold channels to a narrower layout | decode_channels(data) returning (channels, sample_rate), downmix(channels, target_layout) (centre and surround at -3 dB, LFE dropped) | Channel Formats |
| Ask whether a sidechain binding would be accepted, and why not | RealtimeEngine.can_set_lane_sidechain, can_set_bus_sidechain, can_set_master_sidechain returning SidechainCheck | Realtime Engine |
| Edit the engine's clip set one clip at a time | RealtimeEngine.upsert_clip, remove_clip | Realtime Engine |
| Reset processors, prime a repeat render, read tail and latency | reset_processor_state, prime_offline_parameters, tail_samples, graph_latency_samples_q8 | Realtime Engine |
| Address fader, pan and width automation by track, bus or master | resolve_track_lane_automation_id, resolve_bus_automation_id, resolve_master_automation_id ("faderDb", "pan" or "width"). Every resolve_*_automation_id, resolve_instrument_automation_id(destination_id, param_name) included, raises SonareValueError for an unknown track, bus, insert, destination or parameter, or one that is not realtime-safe | Realtime Engine |
| Read engine audio as arrays, and size the lane list | RealtimeEngine.process, process_with_monitor and render_offline return float32 NumPy arrays (one per channel, in a list), and EngineBounceResult.interleaved is a float32 array. set_track_lanes accepts at most MAX_TRACK_LANES (32) lanes and raises SonareValueError for a longer list | Realtime Engine |
| Render a buffer through an existing playback renderer | PlaybackRenderer.render_offline(samples, in_channels) resets the renderer, renders the interleaved buffer and returns an interleaved float32 array; diagnostics() afterwards describes that render. A closed renderer raises SonareError with INVALID_STATE | Playback Renderer |
| Fold the true-peak meter's pending window in at the end of a signal | Mixer.flush_meters() | Mixing Engine |
| List audio sources a project could not resolve | Project.unresolved_audio_sources() | Project Editing |
| Read the mixer scene's unrecognized keys | Mixer.scene_warnings() | Mixing Scene JSON |
Reference map
| Page | Covers |
|---|---|
| Python Analysis API | Audio, analysis functions, feature extraction, inverse reconstruction, metering, scale quantization, conversions, streaming analysis |
| Python Effects API | Audio effects, room acoustics, effects functions, the realtime voice changer, librosa-compatible helpers, the streaming equalizer |
| Python Types | Result and option types, exceptions |