Python Types
Result and option types of the libsonare Python package; the functions that return them are on the Python API index and its sibling pages.
Types
Result objects are plain classes with attribute access; many also expose camelCase property aliases (e.g. bpm_confidence / bpmConfidence) for JS-parity. Shapes below show the data fields.
Row-major matrix layout
Flattened matrix fields (marked "row-major" throughout this page) store a [rows x n] matrix one row after another: the first n values are row 0, the next n are row 1, and so on. Reshape the flat list by the row count to recover a 2-D NumPy array:
import numpy as np
mat = np.asarray(flat).reshape(rows, n) # e.g. reshape(12, n_frames) for a 12-pitch-class chromagramclass PitchClass(IntEnum):
C, CS, D, DS, E, F, FS, G, GS, A, AS, B
class Mode(IntEnum):
MAJOR = 0
MINOR = 1
DORIAN = 2
PHRYGIAN = 3
LYDIAN = 4
MIXOLYDIAN = 5
LOCRIAN = 6
class KeyProfile(IntEnum):
KRUMHANSL_SCHMUCKLER = 0
TEMPERLEY = 1
SHAATH = 2
FARALDO_EDMT = 3
FARALDO_EDMA = 4
FARALDO_EDMM = 5
BELLMAN_BUDGE = 6
class Key:
root: PitchClass
mode: Mode
confidence: float # softmax share over the scored candidates, in [0, 1)
name: str # property -> "C major", "A minor"
short_name: str # property -> "C", "Am"
class KeyDetection(Key): # return type of detect_key(...)
tuning: float # fraction of a semitone the chroma was built with (given or measured)
class TimeSignature:
numerator: int
denominator: int
confidence: float
class BpmHypothesis:
value: float
confidence: float
relation: Literal["primary", "half", "double", "other"]
class Chord:
root: PitchClass
quality: str # "major", "minor", "diminished", "augmented",
# "dominant7", "major7", "minor7", "sus2", "sus4",
# "add9", "minorAdd9", "dim7", "halfDim7",
# "major9", "dominant9", "sus2Add4",
# "major6", "minor6", "minorMajor7",
# "dominant7Sus4", "dominant11", "dominant13",
# "dominant7Flat9", "dominant7Sharp9", "unknown"
start: float # segment start (seconds)
end: float # segment end (seconds)
confidence: float
bass: PitchClass | None # slash-chord bass, or None when it equals root
canonical_name: str
name: str # property -> "Cmaj7", "Am", "G/B"
root_name: str # property -> "C" (alias rootName)
bass_name: str # property (alias bassName)
duration: float # property -> end - start
roman_numeral: str | None # analyze() chords: numeral relative to its key;
# empty for N.C.; None on other chord results
class FunctionalChord(Chord): # return type of chord_functions(...) and chord_functional_analysis(...)
roman: str # "I", "V7", "vi"; "N.C." for an unknown chord
function: str # "tonic", "subdominant", "dominant", "chromatic", "none"
class ChordAnalysisResult:
chords: list[Chord] # return type of detect_chords(...)
tuning: float # fraction of a semitone the chroma was built with (given or measured)
class AutoTuneResult: # return type of auto_tune(...)
samples: list[float] # corrected audio, the length of the input
key: Key # the detected key, or the one the call named (confidence 1)
class AnalysisBeatObservations:
onset_strength: list[float] # windowed accent value, one per beat
low_frequency_energy: list[float]
chord_change: list[float]
class AnalysisResult:
bpm: float
bpm_confidence: float
key: Key
time_signature: TimeSignature
beat_times: list[float]
beat_strengths: list[float] # one raw, unbounded onset-envelope frame per beat
downbeat_indices: list[int] # positions within beat_times that are bar starts
downbeat_phase: int # which beat of the first bar the analysis starts on
beat_local_bpm: list[float] # parallel to beat_times; empty unless
# compute_tempo_curve=True
tuning: float # fraction of a semitone the chromagrams were built with
bpm_candidates: list[BpmHypothesis]
time_signature_candidates: list[TimeSignature]
beats: list[Beat] # property: per-beat objects with strength
# (Beat lives in libsonare.types, not the package root)
chords: list[Chord]
sections: list[Section]
timbre: AnalysisTimbre | None
dynamics: AnalysisDynamics | None
rhythm: AnalysisRhythm | None
melody: AnalysisMelody | None
beat_observations: AnalysisBeatObservations | None
form: str
# The focused detect_chords() / analyze_sections() / analyze_timbre() / ...
# functions remain useful for a single facet or per-call options.
class MeterEstimate:
time_signature: TimeSignature
downbeat_phase: int
searched: bool # False = the series was too short to score
grouping: list[int] # accent groups, e.g. [3, 2, 2]; sums to numerator
candidate_scores: list[float] # parallel to the numerators you requested
candidates: list[TimeSignature] # ordered by descending support
class NormalizeStereoResult:
left: list[float]
right: list[float]
length: int
applied_gain_db: float # one gain, applied to both channels
class HpssResult:
harmonic: list[float]
percussive: list[float]
length: int
sample_rate: int
class StftResult:
n_bins: int
n_frames: int
n_fft: int
hop_length: int
sample_rate: int
magnitude: list[float] # n_bins × n_frames, row-major
power: list[float] # n_bins × n_frames, row-major
class StftMagnitudeResult: # return type of mel_to_stft(...) / mel_result_to_stft(...)
n_bins: int
n_frames: int
magnitude: list[float] # n_bins × n_frames, row-major
class MelPowerResult: # return type of mfcc_to_mel(...) / mfcc_result_to_mel(...)
n_mels: int
n_frames: int
power: list[float] # n_mels × n_frames, row-major
class MelSpectrogramResult:
n_mels: int
n_frames: int
sample_rate: int
hop_length: int
power: list[float] # n_mels × n_frames, row-major
db: list[float] # n_mels × n_frames, row-major
n_fft: int # forward-transform parameters, so the result alone can be inverted
fmin: float # as applied
fmax: float # as applied: the Nyquist when the request left it at 0
htk: bool
is_db: bool # False for a forward transform; power is linear, db its dB form
class MfccResult:
n_mfcc: int
n_frames: int
coefficients: list[float] # n_mfcc × n_frames, row-major
sample_rate: int
hop_length: int
n_fft: int
n_mels: int
fmin: float
fmax: float
htk: bool
is_db: bool # always False
lifter: float # 0 means none
class ChromaResult:
n_chroma: int
n_frames: int
sample_rate: int
hop_length: int
features: list[float] # n_chroma × n_frames, row-major
mean_energy: list[float] # n_chroma values
class PitchResult:
n_frames: int
f0: list[float] # Fundamental frequency per frame (Hz)
voiced_prob: list[float] # Voicing probability per frame (0–1)
voiced_flag: list[bool] # Voiced/unvoiced decision per frame
median_f0: float
mean_f0: float
class WaveformPeaksReport:
min: NDArray[np.float32] # channel-major: channel * bucket_count + bucket
max: NDArray[np.float32] # channel-major
channels: int
bucket_count: int
samples_per_bucket: int
class StreamConfig:
sample_rate: int = 44100
n_fft: int = 2048 # samples at 44100 Hz; rescaled below that rate, never below hop_length
hop_length: int = 512
n_mels: int = 128
fmin: float = 0.0
fmax: float = 0.0
tuning_ref_hz: float = 440.0
compute_magnitude: bool = False
compute_mel: bool = True
compute_chroma: bool = True
compute_onset: bool = True
compute_spectral: bool = True
emit_every_n_frames: int = 1
magnitude_downsample: int = 1
max_pending_frames: int = 4096 # newly produced frames are dropped at the cap
max_progression_entries: int = 4096 # per-progression history cap
key_update_interval_sec: float = 5.0
bpm_update_interval_sec: float = 10.0
window: int = 0 # 0=Hann, 1=Hamming, 2=Blackman, 3=Rectangular
output_format: int = 0 # legacy; omit it or keep the Float32 value (0)
class StreamFrames:
n_frames: int
n_mels: int
n_chroma: int # 12 when chroma is present; otherwise 0
feature_flags: int # MEL=1, CHROMA=2, ONSET=4, SPECTRAL=8
timestamps: list[float]
mel: list[float] # n_frames × n_mels; empty if MEL is absent
chroma: list[float] # n_frames × n_chroma; empty if CHROMA is absent
onset_strength: list[float] # empty if ONSET is absent
rms_energy: list[float]
spectral_centroid: list[float] # empty if SPECTRAL is absent
spectral_flatness: list[float] # empty if SPECTRAL is absent
chord_root: list[int] # empty if CHROMA is absent
chord_quality: list[int] # empty if CHROMA is absent
chord_confidence: list[float] # empty if CHROMA is absent
class StreamChordChange:
root: int
quality: int
name: str # spelled as batch analysis spells a chord
start_time: float
confidence: float
class StreamBarChord:
bar_index: int
root: int
quality: int
name: str
start_time: float
confidence: float
class StreamPatternScore:
name: str
score: float
class StreamStats:
total_frames: int
total_samples: int
duration_seconds: float
pending_frames: int
dropped_output_frames: int
dropped_chord_progression_entries: int
dropped_bar_progression_entries: int
non_finite_discard_blocks: int # blocks whose NaN/Inf input was replaced; cleared by reset()
bpm: float
bpm_confidence: float
bpm_candidate_count: int
key: int # -1 while no key is estimated (e.g. silence)
key_minor: bool
key_confidence: float
key_name: str | None # "C major"; None while no key is estimated
key_short_name: str | None # "C", "Am"
chord_root: int # -1 for no chord
chord_quality: int
chord_name: str # "N.C." for no chord
chord_confidence: float
chord_start_time: float
current_bar: int
bar_duration: float
chord_progression: list[StreamChordChange]
bar_chord_progression: list[StreamBarChord]
voted_pattern: list[StreamBarChord]
pattern_length: int
detected_pattern_name: str
detected_pattern_score: float
all_pattern_scores: list[StreamPatternScore]
accumulated_seconds: float
used_frames: int
updated: boolAdditional Python result classes used by focused APIs:
| Area | Classes |
|---|---|
| Metering | ClippingRegion, StreamFramesU8, StreamFramesI16, WaveformPeaksReport |
| Mastering | MasteringResult, MasteringStereoResult, StreamingLoudnessGain (from streaming_loudness_gain*) |
| Mastering JSON documents | TypedDicts over the JSON strings the assistant, profile and analysis helpers return: MasteringAudioProfile (MasteringAudioProfileLoudness, MasteringAudioProfileSpectral, MasteringAudioProfileDynamics, MasteringAudioProfileDefects), MasteringAssistantProfile, MasteringAssistantResult, MasteringChainConfigDocument, MasteringStreamingPreviewResult, MasteringStreamingPreviewPlatform, MasteringRepairAnalysis, MatchReferenceLoudnessResult, MatchTonalBalanceResult, MatchTonalBalanceBand, MatchTonalBalanceLogBandsResult, MatchEqCurveResult, MatchEstimateReferenceDelaySamplesResult, StereoMonoCompatCheckResult, StereoMonoCompatBand, StereoMonoCompatCheckLogBandsResult. The functions still return JSON strings; parse them with json.loads and annotate the result with these types |
| Repair | RepairApplyResult, RepairStageReports (from mastering_repair_apply) |
| Acoustics | RoomEstimate (volume, length_m, width_m, height_m, drr_db, confidence, band_absorption, rt60_bands), RoomMorphResult (audio, sample_rate, diagnostics), RoomMorphStereoResult (left, right, sample_rate, diagnostics; from room_morph_stereo), RoomGeometry (a TypedDict, from room_geometry_from_estimate), MaterialPresetName, AcousticModeName |
| Analysis literals | ChordFunctionName |
| Mixing | MixTrackInput (track_id, left, right, name, sample_rate; a track at another rate is resampled onto the call's rate), MixerStereoResult, SidechainCheck (ok plus the first refusal reason; from the engine's can_set_*_sidechain) |
| Projects | AssistSidecar (return type of project.get_assist_sidecar(index) / project.assist_sidecars(); see Project Editing), NotePairValidation |
| Realtime engine jobs/telemetry | EngineBounceOptions, EngineBounceResult, EngineFreezeOptions, EngineFreezeResult, EngineCaptureStatus, EngineTelemetry, EngineTelemetryType, EngineTelemetryError, MeterTelemetryRecord, MeterTelemetryRecordWide, ScopeTelemetryRecord |
| Build introspection | Capabilities, CapabilityCatalog, CapabilityCatalogPresets, CapabilityCatalogChain, CapabilityCatalogChainStage, CapabilityCatalogChainList, CapabilityCatalogChainAlias, SynthParamInfo, MasteringPresetCatalogEntry, MasteringInsertParamInfo, MasteringInsertParamUnit, MasteringInsertParamScale, MasteringInsertParamRelation, MasteringInsertParamDependency, MasteringInsertSlot, MasteringInsertTiming, MasteringProcessorCatalogEntry (TypedDicts over the JSON documents described on Capability Catalog) |
CapabilityCatalog["chain"] (CapabilityCatalogChain) describes every flat mastering chain key: the stages, the indexed lists with their item counts, the keys no processor carries, and every alias spelling; CapabilityCatalog["presets"]["playbackRoom"] is always present. SynthParamInfo (from synth_engine_param_info and synth_patch_param_info) has name, type ("number" or "boolean"), default (float | bool; a bool for a switch), unit and the optional integer, min, max and activeWhen. activeWhen is a list of alternatives, each a list of SynthParamCondition dicts (key, scope "engine" or "patch", relation one of "eq", "ne", "gt", "ge", "lt", "le", and value): the field reaches the voice when every condition of at least one inner list holds; it is absent for an unconditional field. CapabilityCatalog["masteringPresets"] contains MasteringPresetCatalogEntry values with name, kind, targetLufs, truePeakCeilingDb, and maxLimiterGainReductionDb; restoration entries use None for the three numeric fields. MasteringProcessorCatalogEntry includes slots for conditional key groups and causal (false for repair.declick, repair.declip and repair.trimSilence). MasteringInsertParamInfo descriptors include id, rtSafe, type, min, max, default, unit (from the closed MasteringInsertParamUnit set), scale (linear or log), uiMin / uiMax (an optional display range inside [min, max]), dependsOn (sibling keys whose live value bounds this one, each as key, relation and factor, read as this <relation> factor * sibling), coupledWith (a list of the other sibling keys that move the accepted range; min / max are measured with them at their defaults), choices, and slot, while MasteringInsertTiming contains latencySamples and tailSamples for the requested insert configuration.
Exceptions
Two classes cover every failure, and the second derives from the first, so they form one hierarchy rather than two. ErrorCode is the IntEnum of the codes they carry:
class SonareError(RuntimeError):
code: int # the C-ABI error code, an ErrorCode value
code_name: str # property -> "InvalidParameter", "FileNotFound", ...
# str(e) -> "[4] Invalid parameter": the code is prefixed to the message
class ErrorCode(IntEnum):
OK = 0
FILE_NOT_FOUND = 1
INVALID_FORMAT = 2
DECODE_FAILED = 3
INVALID_PARAMETER = 4
OUT_OF_MEMORY = 5
NOT_SUPPORTED = 6
INVALID_STATE = 7
CANCELLED = 8
ENCODE_FAILED = 9
ABI_MISMATCH = 10 # binding-only: the loaded library does not match the binding
UNKNOWN = 99
class SonareValueError(SonareError, ValueError):
code: int # ErrorCode.INVALID_PARAMETER unless the raiser passes another
# str(e) -> "spectral_centroid: samples contains NaN or Inf at index 0": no prefixA failure that comes from the library or from an object's state is always a SonareError with the matching code — NOT_SUPPORTED when the loaded library lacks an entry point, INVALID_STATE for a closed object, UNKNOWN for a native result that fails a sanity check — so except RuntimeError keeps catching it and .code tells the cases apart. ABI_MISMATCH is raised on first library use when the loaded library's ABI version differs from the binding's. The error contract lines these codes up with the other bindings and the CLI exit codes.
SonareValueError is what the binding raises when it refuses an argument before the call crosses into native code, and when the core refuses an unknown or out-of-range id or value on Project, RealtimeEngine and transcribe. A non-number handed to a numeric argument is a TypeError rather than either class. Its bases decide what an existing handler sees:
| Handler | A binding refusal (SonareValueError) | A core rejection (SonareError, code 4) |
|---|---|---|
except ValueError | Caught | Not caught |
except SonareError or except RuntimeError | Caught | Caught |
except SonareValueError | Caught | Not caught |
Branching on .code == ErrorCode.INVALID_PARAMETER | Matches | Matches |
An instance is message-only. There is no attribute naming the offending parameter and none carrying the accepted range: .code is the only structured field, and it reads INVALID_PARAMETER for every argument refusal; a project document that fails to load carries the C-ABI code instead (INVALID_FORMAT for malformed JSON). The parameter name and, where one applies, the range are in the text — a buffer refusal names the entry point and the argument (master_audio_stereo: right must not be empty), a scalar refusal names the field and the interval the C type can hold (sample_rate must be an integer within [-2147483648, 2147483647]). Code that needs the name has to read it from str(e); Python API covers the wording and why matching on it is fragile.
Where the validation layer stops
The binding validates in two layers, and the shape of each says what a SonareValueError at the call site rules out.
Sample buffers are preflighted on every public entry point whose leading parameter is a sample buffer, through the shared preflight guard or the same check written into the body. Each preflighted buffer is coerced to a contiguous one-dimensional float32 array, refused when empty, and scanned for NaN and Inf unless the entry point exposes validate and the call passes validate=False (the scan is skipped; the emptiness check is not). An F0 track handed to the note functions is checked for shape only, because pYIN spells an unvoiced frame as nan. The set is derived rather than maintained: a test in the binding discovers every public callable whose leading parameter is annotated as a buffer and fails when one ships without the guard. A None element in a sample list is named (samples[7] is None, not a number) rather than reported as a NaN sample.
Scalars are narrowed at the conversion, not per function. Every integer or float that passes through the binding's ctypes readers or a config-struct field is refused when the C type would silently change it: an integer that would wrap (2**32 + 8000 handed to a c_int sample rate), a fractional value for an integer field, a double that would saturate a c_float, or a NaN or infinity. These are SonareValueError. A value that is not a number at all, such as a string or a bool, is a plain TypeError instead, so except ValueError does not catch it. The message names the field and the interval. Because the check sits at the conversion, it covers every entry point that hands the value on, and there is no per-function count to keep.
Five entry points refuse rather than answer. Refusing is a different contract from checking and continuing, and these are the ones whose input is refused outright instead of producing a defined empty result: trim_silence, split_silence and fix_frames raise on an empty buffer; tempogram_ratio raises on an empty matrix and on a factors entry that is not finite and positive; mix_stereo raises on a scene whose strips are all empty or carry a non-finite sample. Input some entry points refuse gives the reason for each.
What the layer does not do:
- Judge a value's meaning. Narrowing checks that a number is representable, not that the processor accepts it.
ratio=0.5ondynamics.compressorreaches the core and comes back asSonareErrorcode 4 with the messagecompressor ratio must be at least 1; a value outside an insert's range, such asdryWet=5oneffects.modulation.chorus, and a key the processor does not read, such asgainDbonutility.gain, are refused by name the same way. A handful of entry points do check a domain rule Python-side — the power-of-twon_fftof the classical repair functions, the odd kernel ofhpss, the FFT geometry oftime_stretchandpitch_shift— and their messages read the same way; everything else about a value's meaning is the core's to judge. - Read a
paramsdict. The parameter dictionaries handed tomastering_process, the chain and the inserts are serialized as given: an unknown key is ignored by the processor, and a non-finite value is rejected by the core (mastering parameters must be finite and representable), not by the binding. Check values against the capability catalog before the call if you want the refusal at the call site. - Preflight the exemptions. The entry points for which an empty buffer is a defined result — the element-wise conversions, the padding helpers, the tempogram family — accept one and return one; they are listed under What is not checked.
- Cover the CLI's own arguments. A command-line usage error is a plain
ValueError, since it reports a mistake in the command rather than an API argument.