Skip to content

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:

python
import numpy as np
mat = np.asarray(flat).reshape(rows, n)   # e.g. reshape(12, n_frames) for a 12-pitch-class chromagram
python
class 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: bool

Additional Python result classes used by focused APIs:

AreaClasses
MeteringClippingRegion, StreamFramesU8, StreamFramesI16, WaveformPeaksReport
MasteringMasteringResult, MasteringStereoResult, StreamingLoudnessGain (from streaming_loudness_gain*)
Mastering JSON documentsTypedDicts 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
RepairRepairApplyResult, RepairStageReports (from mastering_repair_apply)
AcousticsRoomEstimate (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 literalsChordFunctionName
MixingMixTrackInput (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)
ProjectsAssistSidecar (return type of project.get_assist_sidecar(index) / project.assist_sidecars(); see Project Editing), NotePairValidation
Realtime engine jobs/telemetryEngineBounceOptions, EngineBounceResult, EngineFreezeOptions, EngineFreezeResult, EngineCaptureStatus, EngineTelemetry, EngineTelemetryType, EngineTelemetryError, MeterTelemetryRecord, MeterTelemetryRecordWide, ScopeTelemetryRecord
Build introspectionCapabilities, 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:

python
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 prefix

A 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:

HandlerA binding refusal (SonareValueError)A core rejection (SonareError, code 4)
except ValueErrorCaughtNot caught
except SonareError or except RuntimeErrorCaughtCaught
except SonareValueErrorCaughtNot caught
Branching on .code == ErrorCode.INVALID_PARAMETERMatchesMatches

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.5 on dynamics.compressor reaches the core and comes back as SonareError code 4 with the message compressor ratio must be at least 1; a value outside an insert's range, such as dryWet=5 on effects.modulation.chorus, and a key the processor does not read, such as gainDb on utility.gain, are refused by name the same way. A handful of entry points do check a domain rule Python-side — the power-of-two n_fft of the classical repair functions, the odd kernel of hpss, the FFT geometry of time_stretch and pitch_shift — and their messages read the same way; everything else about a value's meaning is the core's to judge.
  • Read a params dict. The parameter dictionaries handed to mastering_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.