Skip to content

Python Effects API ​

Audio effects, room acoustics, the realtime voice changer, and librosa-compatible helpers of the libsonare Python package; installation, quick start, and the other API families are on the Python API index.

Audio Effects ​

python
from libsonare import Audio

audio = Audio.from_file("music.mp3")

# Harmonic-Percussive Source Separation
hpss_result = audio.hpss()
harmonic = audio.harmonic()
percussive = audio.percussive()

# Time stretch / pitch shift
stretched = audio.time_stretch(rate=1.5)       # 1.5x speed
shifted = audio.pitch_shift(semitones=2.0)     # Up 2 semitones

# Normalize and trim silence
normalized = audio.normalize(target_db=-3.0)
trimmed = audio.trim(threshold_db=-60.0)

# Resample
resampled = audio.resample(target_sr=44100)
HPSS · FULL MIXIDLE
HPSS — splitting the tune from the drums

Sustained harmonic sounds tend to form horizontal ridges in a spectrogram; transients tend to form vertical streaks. HPSS uses median filters and masks to separate these patterns. Switch between the full mix, harmonic and percussive layers and audition them. This separates spectral structure rather than instrument identities: pitched attacks can enter the percussive layer, and sustained cymbals can enter the harmonic layer.

Layer

For region-based time/frequency edits, use spectral_edit(samples, sample_rate, [SpectralRegionOp(...)]); see Spectral Editing.

Room Acoustics ​

Use these functions for the room or playback space, not for song structure.

GoalUse
Measure a clean impulse responseanalyze_impulse_response(...)
Estimate room decay from ordinary audiodetect_acoustic(...)
Fit a practical room model from audioestimate_room(...)
Create a mono room impulse response from dimensionssynthesize_rir(...)
Add a target-room character as an effectroom_morph(...)
Add the same character to a stereo recording, heard at two spaced receiversroom_morph_stereo(left, right, sample_rate, length_m, width_m, height_m, ...)

Defaults and terms

analyze_impulse_response(...) and detect_acoustic(...) return AcousticResult with RT60, EDT, C50, C80, D50, per-band arrays, confidence, and is_blind. Their sample_rate default is 48000, unlike most music-analysis helpers that default to 22050. RIR means room impulse response. RT60 is the reverberation time — how long a tail takes to decay by 60 dB — while C50 and C80 are clarity ratios between early and late energy.

python
ir = sonare.analyze_impulse_response(ir_samples, sample_rate, n_octave_bands=6, min_decay_db=30.0)
print(ir.rt60, ir.edt, ir.c50, ir.c80, ir.confidence)

blind = sonare.detect_acoustic(
    room_recording,
    sample_rate,
    n_octave_bands=6,
    n_third_octave_subbands=24,
    min_decay_db=30.0,
    noise_floor_margin_db=10.0,
)
print(blind.is_blind, blind.rt60_bands)

estimate = sonare.estimate_room(room_recording, sample_rate, n_octave_bands=6)
print(estimate.volume, estimate.length_m, estimate.width_m, estimate.height_m)
print(estimate.drr_db, estimate.confidence, estimate.band_absorption)

rir = sonare.synthesize_rir(7.0, 5.0, 3.0, absorption=0.2, sample_rate=sample_rate)
print(rir.sample_rate, len(rir.rir), rir.diagnostics)  # warnings only

# Re-synthesize the estimated room: positions are the only thing to add.
geometry = sonare.room_geometry_from_estimate(estimate, source=(1, 1, 1.2), listener=(2, 1.5, 1.5))
rir = sonare.synthesize_rir(**geometry, sample_rate=sample_rate, material_preset="none")

morphed = sonare.room_morph(room_recording, sample_rate, 12.0, 9.0, 4.0, wet=0.6)
ROOM · IMPULSE RESPONSEIDLE
Impulse response — how a room decays

A room impulse response synthesized from shoebox dimensions, shown as its energy decay in dB. Enlarge the room or lower the absorption and the tail stretches — the RT60 (time to fall 60 dB) climbs with it. Press play to hear a clap in the room.

Room size
7 m
Absorption
0.16

Keep these cautions in mind:

  • estimate_room(...) returns an equivalent room, not guaranteed real geometry; inspect confidence.
  • synthesize_rir(...) raises SonareError (code INVALID_PARAMETER, so except ValueError does not catch it) for an unusable geometry, such as a source or listener outside the room; the message leads with the diagnostic code (acoustic.source_outside_room). A returned RirResult carries only warnings on diagnostics.
  • material_preset ("none" by default, or "concrete", "wood", "curtain", "carpet", "glass") and the estimate_room mode ("auto" by default, "blind", "impulse_response") take names only: a number is a TypeError, an unknown name a SonareValueError.
  • room_geometry_from_estimate(estimate, source=None, listener=None) returns a RoomGeometry dict of synthesize_rir keyword arguments: length_m, width_m, height_m, source and listener (the default placement scaled into the estimated room, replaced by any source/listener you pass), plus absorption (mean of the finite bands) and band_absorption when the estimate converged on them. An estimate with no measurable dimensions (NaN) raises SonareValueError.
  • room_morph(...) is a creative effect, not dereverberation.
  • room_morph_stereo(...) takes every room_morph keyword plus receiver_spacing_m in (0, 4] m (None = 0.5 m) and returns a RoomMorphStereoResult with left, right, sample_rate and diagnostics. A listener closer to a wall than half the spacing raises SonareError.

See Room Acoustics for interpretation notes and when a blind estimate is appropriate.

Effects Functions ​

FunctionReturn TypeDescription
hpss(samples, sample_rate, kernel_harmonic?, kernel_percussive?, n_fft?, hop_length?, hard_mask?)HpssResultHarmonic-Percussive Source Separation; defaults: kernel_harmonic=31, kernel_percussive=31, n_fft=2048, hop_length=512, hard_mask=False
hpss_with_residual(samples, sample_rate, kernel_harmonic?, kernel_percussive?, n_fft?, hop_length?, hard_mask?)dict[str, object]HPSS with harmonic, percussive, and residual outputs
harmonic(samples, sample_rate)list[float]Extract harmonic component
percussive(samples, sample_rate)list[float]Extract percussive component
time_stretch(samples, sample_rate, rate, n_fft?, hop_length?)list[float]Time-stretch without pitch change; defaults: n_fft=2048, hop_length=512
pitch_shift(samples, sample_rate, semitones, n_fft?, hop_length?)list[float]Pitch-shift without tempo change; defaults: n_fft=2048, hop_length=512
pitch_correct_to_midi(samples, sample_rate, current_midi?, target_midi?)list[float]Pitch-correct toward a target MIDI note
pitch_correct_to_midi_timevarying(samples, f0_hz, target_midi, sample_rate?, hop_length?, voiced?, voiced_prob?)list[float]Contour-following pitch correction: retunes every voiced frame toward target_midi along a per-frame f0_hz contour, preserving vibrato/drift instead of flattening it
pitch_correct_timevarying(samples, f0_hz, sample_rate?, hop_length?, *, mode="midi", target_midi?, scale_root?, scale_mode_mask?, reference_midi?, retune_amount?, max_correction_semitones?, retune_speed_ms?, vibrato_threshold_cents?, voiced?, voiced_prob?)list[float]The same contour-following correction toward a fixed note (mode="midi") or the nearest degree of a scale (mode="scale"); scale_mode_mask takes a mask, a Mode or a mode name, and the retune knobs trade natural for robotic correction
auto_tune(samples, sample_rate?, key="detect", *, strength?, retune_speed_ms?, vibrato_threshold_cents?, max_correction_semitones?, reference_midi?)AutoTuneResultTune a monophonic take to a detected ("detect") or named key (Key, {"root", "mode"} or (root, mode)); .samples is the tuned audio and .key the key used
note_stretch(samples, sample_rate, onset_sample?, offset_sample?, stretch_ratio?, *, onset_sec?, offset_sec?)list[float]Stretch a single note region in place
note_move(samples, sample_rate, onset_sample?, offset_sample?, target_onset_sample?, *, onset_sec?, offset_sec?, target_onset_sec?)list[float]Move a note region to a new onset without changing its duration
voice_change(samples, sample_rate, pitch_semitones?, formant_factor?, formant_mode?)list[float]Independent pitch + formant shift. formant_mode is "relative" (default: formant_factor is a warp in [0.55, 1.65] applied after the pitch shift) or "absolute" (formant_factor is the formant shift relative to the input whatever the pitch shift; 1.0 keeps the formants in place). A relative factor outside that range, and an absolute request whose warp formant_factor / 2**(pitch_semitones / 12) leaves it, raise SonareValueError naming the reachable formant_factor range
voice_change_realtime(samples, sample_rate?, preset?, channels?)np.ndarrayOne-shot render through the realtime voice preset chain
normalize(samples, sample_rate, target_db?)list[float]Normalize peak level to target dB (default: 0.0)
normalize_rms(samples, sample_rate, target_db?)list[float]Normalize RMS level to target dB (default: -20.0)
normalize_stereo(left, right, sample_rate?, target_db?, *, validate?)NormalizeStereoResultPeak-normalize a pair on one shared gain (target_db default 0.0)
normalize_rms_stereo(left, right, sample_rate?, target_db?, *, validate?)NormalizeStereoResultRMS-normalize a pair on one shared gain (target_db default -20.0)
remix(samples, intervals, sample_rate?, align_zeros?)np.ndarrayReorder/concatenate by interval slices; align_zeros default False
remix_aligned_intervals(samples, intervals, sample_rate?, align_zeros?)list[int]Resolve the cut points remix would use, without cutting; align_zeros default True
trim(samples, sample_rate, threshold_db?, frame_length?, hop_length?)list[float]Trim silence (defaults: -60.0 dB, frame_length=2048, hop_length=512)
resample(samples, src_sr, target_sr)list[float]Resample to target sample rate

Every edit position takes samples or seconds (*_sec), never both; giving both is refused. SpectralRegionOp likewise takes start_sec / end_sec.

trim(...) is the simple threshold-based edit helper. The librosa-compatible trim_silence(...) helper below uses frame RMS and top_db, and returns the trimmed audio together with its original sample range.

Normalizing a stereo pair ​

Python keeps two separate stereo normalizers — normalize_stereo for peak and normalize_rms_stereo for RMS — where the JavaScript surfaces take one function and a mode argument. Both measure the level across the pair and apply one shared gain to both channels, which is what keeps the stereo image intact: normalizing each channel on its own gain would lift the quieter side until the two peaks matched, changing the balance rather than the level. Because the gain is shared, NormalizeStereoResult.applied_gain_db is a single figure and not a pair, and an already-silent pair comes back untouched with the gain at exactly 0. Alongside left and right the result carries length, the pair's shared sample count.

Either channel empty, or the two channels unequal in length, is refused. The C entry takes one sample rate for the pair, so the two channels cannot disagree on it.

python
result = sonare.normalize_stereo(left, right, 48000, target_db=-3.0)
print(result.length, result.applied_gain_db)

Cutting a multichannel take on one frame set ​

remix(..., align_zeros=True) snaps slice boundaries to the signal's zero-crossings, which is a per-signal decision: calling remix channel by channel snaps each channel to a different frame and drifts a stereo take apart. remix_aligned_intervals(...) resolves one cut set from one channel — a flat list of clamped (start, end) pairs — so you can slice every channel with the same frames. Note the deliberate asymmetry in defaults: remix has align_zeros=False, remix_aligned_intervals has it True.

Two guards stop a slice from vanishing under snapping: a signal with no sign change at all (silence, a DC offset, any constant) is not snapped, and a slice that had content but would collapse to empty keeps its unsnapped boundaries.

python
cuts = sonare.remix_aligned_intervals(left, [0, 48000, 96000, 144000], sample_rate=48000)
left_out = sonare.remix(left, cuts, sample_rate=48000)
right_out = sonare.remix(right, cuts, sample_rate=48000)

Realtime voice changer ​

RealtimeVoiceChanger wraps the same preset-based live voice chain exposed by WASM and Node native. It keeps high-pass, gate, retune, formant, EQ, compressor, de-esser, reverb, and limiter state across blocks. Use it instead of voice_change(...) when processing microphone or stream blocks.

python
import json
import libsonare as sonare

print(sonare.realtime_voice_changer_preset_names())
print(sonare.voice_changer_abi_version())  # native POD-config ABI version
print(sonare.voice_character_preset_id(1))  # "bright-idol"
preset_json = sonare.realtime_voice_changer_preset_json("bright-idol")
print(sonare.validate_realtime_voice_changer_preset_json(preset_json)["ok"])
preset_config = sonare.realtime_voice_changer_preset_config("bright-idol")  # canonical RealtimeVoiceChangerConfig

with sonare.RealtimeVoiceChanger(48000, preset="bright-idol", max_block_size=128) as changer:
    out = changer.process_mono(input_block)
    changer.set_config(json.loads(preset_json))
    print(changer.latency_samples(), changer.config_json(), out.shape)

# Convenience one-shot render through the same realtime chain.
processed = sonare.voice_change_realtime(vocal, sample_rate=48000, preset="soft-whisper")

Preset IDs currently include neutral-monitor, bright-idol, soft-whisper, deep-narrator, robot-mascot, and dark-villain.

Built-in IDs are the strict canonical strings shown above. A custom mapping must pass the preset JSON validator and contain either a dsp object or a macros object, not both; malformed preset shapes are rejected.

Use realtime_voice_changer_preset_config(preset) when you want the resolved POD config rather than the JSON form. It returns the canonical, normalized RealtimeVoiceChangerConfig for a built-in preset by ID or index.

realtime_voice_changer_preset_pod(preset) remains as a compatibility alias.

formant_mode ("relative" by default, or "absolute") is fixed when the changer is created (preset key dsp.formant.mode); "absolute" adds one analysis frame of latency and refuses a warp outside [0.55, 1.65]. A configuration value that is non-finite or out of range, in the constructor or in a later config update, is refused by name and the changer keeps its previous configuration. For stereo, construct the changer with channels=2 and call process_planar_stereo(left, right) or process_interleaved(samples, channels). StreamingRetune(semitones=, mix=, grain_size=) runs the retune stage on its own, block by block.

librosa-Compatible Helpers ​

These mirror the corresponding librosa functions — see librosa Compatibility for the function each helper matches.

What each helper is for

  • preemphasis / deemphasis — classic one-tap IIR pre-processing that boosts (or undoes) high frequencies.
  • trim_silence / split_silence — trim leading/trailing silence or split on silent gaps.
  • frame_signal / pad_center / fix_length / fix_frames — framing and size-alignment utilities for fixed-frame DSP.
  • peak_pick / vector_normalize — peak detection on 1-D signals (e.g. onset envelopes) and vector-norm normalization.
  • pcen — dynamic range compression for mel spectrograms; features that are robust to gain and background noise.
  • tonnetz — projects chroma into a 6-D harmonic space for chord-relation and modulation analysis.
  • tempogram / plp — time-varying tempo representation from the onset envelope (autocorrelation or mode="cosine"), and the dominant local pulse on top.
  • fourier_tempogram / cyclic_tempogram / tempogram_ratio — the FFT-based tempogram, an octave-folded cyclic tempogram, and tempo-ratio features.
FunctionReturn TypeDescription
preemphasis(samples, coef?, zi?)list[float]Pre-emphasis filter (librosa.effects.preemphasis)
deemphasis(samples, coef?, zi?)list[float]Inverse pre-emphasis (librosa.effects.deemphasis)
trim_silence(samples, top_db?, frame_length?, hop_length?)tuple[list[float], int, int]librosa.effects.trim — returns (audio, start_sample, end_sample)
split_silence(samples, top_db?, frame_length?, hop_length?)list[tuple[int, int]]librosa.effects.split — non-silent intervals as sample pairs
split_silence_common(signals, top_db?, frame_length?, hop_length?)list[tuple[int, int]]The cut points several takes of one part agree are silent. signals is one sequence of sequences, so the signal count and per-signal lengths cannot disagree
split_silence_common_with_report(signals, top_db?, frame_length?, hop_length?)tuple[list[tuple[int, int]], SilenceCommonReport]The same cut points plus a report with silence_ceiling_db, max_signal_intervals and min_signal_intervals
frame_signal(samples, frame_length, hop_length)tuple[int, list[float]]librosa.util.frame; returns (n_frames, row-major frames)
pad_center(values, target_size, pad_value?)list[float]librosa.util.pad_center
fix_length(values, target_size, pad_value?)list[float]librosa.util.fix_length
fix_frames(frames, x_min?, x_max?, pad?)list[int]librosa.util.fix_frames. x_max is the inclusive upper bound; omit it (None) for no upper bound — -1 is refused
peak_pick(values, pre_max, post_max, pre_avg, post_avg, delta, wait)list[int]librosa.util.peak_pick — returns peak indices
vector_normalize(values, norm_type?, threshold?)list[float]librosa.util.normalize. norm_type: 0=inf, 1=L1, 2=L2, 3=power
pcen(values, n_bins, n_frames, sample_rate?, hop_length?, time_constant?, gain?, bias?, power?, eps?)list[float]librosa.pcen — input is row-major [n_bins x n_frames] mel
tonnetz(chromagram, n_chroma, n_frames)list[float]librosa.feature.tonnetz — returns row-major [6 x n_frames]
tempogram(onset_envelope, sample_rate?, hop_length?, win_length?, center?, norm?, mode?)tuple[int, list[float]]librosa.feature.tempogram. mode: "autocorrelation" (default) or "cosine"
fourier_tempogram(onset_envelope, sample_rate?, hop_length?, win_length?, center?, norm?)tuple[int, list[float]]FFT-based tempogram — STFT of the onset envelope
cyclic_tempogram(onset_envelope, sample_rate?, hop_length?, win_length?, bpm_min?, n_bins?)tuple[int, list[float]]Octave-folded cyclic tempogram
tempogram_ratio(tempogram_data, win_length?, sample_rate?, hop_length?, factors?)list[float]Tempo-ratio features from a tempogram
plp(onset_envelope, sample_rate?, hop_length?, tempo_min?, tempo_max?, win_length?)list[float]librosa.beat.plp — predominant local pulse

Streaming Equalizer API ​

StreamingEqualizer wraps the native block-by-block EQ engine. Use it for live preview, processor UIs, or matching a source tone to a reference without assembling a mastering chain.

python
with sonare.StreamingEqualizer(sample_rate=48000, max_block_size=512) as eq:
    eq.set_band(0, {"type": "bell", "frequencyHz": 2500, "gainDb": 2.5, "q": 1.0})
    eq.set_phase_mode("natural")
    eq.set_auto_gain(True)
    # buffers may differ in length; the reference may be at its own rate
    eq.match(source_samples, reference_samples, max_bands=8, reference_sample_rate=44100)
    out = eq.process_mono(input_block)
    snapshot = eq.spectrum()

Bands can be Python dictionaries or JSON strings. set_phase_mode(...) accepts zero / natural / linear names or numeric values. The object also exposes output gain/pan, sidechain input for dynamic bands, process_stereo(...), spectrum(), latency_samples, and last_auto_gain_db.