import type { BandMeasurement, BandProfile } from "./measure.js"; /** * Interpretation of measured band statistics. Pure functions only — no FFmpeg, * no I/O — so every threshold here is unit-testable and reviewable. * * Two ideas carry the whole model: * * 1. A band's *spread* (p90 - p10 of its window RMS) says whether the band * carries music or noise. Music moves with the performance; a hiss or rumble * floor sits still. Level alone cannot tell those apart. * * 2. The noise floor can only be *observed* where the performance stops. Over a * continuous performance, even the lowest percentile measures the quietest * music, not the noise — so an estimate taken there is an upper bound and is * marked low-confidence, and callers must denoise conservatively. */ /** Spread at or below this is a stationary floor rather than performance. */ export declare const STATIONARY_SPREAD_DB = 6; /** Spread at or above this clearly tracks a performance. */ export declare const DYNAMIC_SPREAD_DB = 9; /** This far below the music core, a band cannot be heard. */ export declare const EMPTY_RELATIVE_DB = -60; /** Below this absolute level there is nothing to preserve or remove. */ export declare const EMPTY_ABSOLUTE_DB = -95; /** * How far a band must tower over the median band to read as a sustained tone * rather than part of a flat noise bed. Flat noise stays within ~10 dB of the * median; a held note is tens of dB above it. */ export declare const TONE_PROMINENCE_DB = 20; export type BandContent = "signal" | "noise" | "empty"; export type NoiseFloorSource = "gap" | "hiss-band" | "quietest-window" | "none"; /** * Confidence granted to a floor extrapolated from the hiss region. * Lower than a floor observed in a gap, far higher than a full-mix percentile. */ export declare const HISS_BAND_CONFIDENCE = 0.55; /** * Correction applied to the hiss-band extrapolation, in dB. * * Crossover band edges are nominal, so the effective width of each band is wider * than `highHz - lowHz` and the extrapolation reads low. Measured against * synthetic hiss at -40, -50 and -60 dBFS the shortfall was a consistent * 3.4-3.5 dB, independent of level, so it is corrected here — leaving * `noiseFloorDb` an unbiased estimate of the noise's broadband RMS. */ export declare const HISS_EXTRAPOLATION_CALIBRATION_DB = 3.5; /** * Broadband noise level extrapolated from the bands that hold only noise. * * A full-mix percentile cannot find the floor of a continuous performance: its * quietest window is still music, which overestimates the floor by 20-30 dB on * real material. Above the content, though, there is nothing *but* noise, and * broadband hiss is roughly flat — so its level there scales up to a broadband * figure by the ratio of bandwidths. * * Only bands above `fromHz` are used. Rumble is also stationary noise but is * concentrated at the bottom, and extrapolating from it would overestimate the * broadband level enormously. */ export declare function noiseFloorFromHissBands(bands: BandVerdict[], nyquistHz: number | null, fromHz?: number): number | null; export interface BandVerdict { lowHz: number; highHz: number; centerHz: number; /** Typical level of the band (p50 of window RMS, dBFS). */ levelDb: number | null; /** Level the band falls back to, dBFS — measured in a gap when one exists. */ floorDb: number | null; spreadDb: number | null; content: BandContent; /** 0–1, how stationary the band is. */ stationarity: number | null; } export interface NoiseModel { /** Best estimate of the broadband noise level, dBFS. */ noiseFloorDb: number | null; /** 0–1. Only a real gap in the performance earns a high value. */ noiseFloorConfidence: number; noiseFloorSource: NoiseFloorSource; /** Loud-to-quiet span of the mix, dB. A real signal-to-noise estimate. */ snrDb: number | null; /** p50 of the loudest band — the level the music core sits at. */ musicReferenceDb: number | null; /** Highest frequency still carrying performance, Hz. */ bandwidthHz: number | null; /** Level of stationary high-frequency noise relative to the music core, dB. */ hissRelativeDb: number | null; /** Level of stationary low-frequency noise relative to the music core, dB. */ rumbleRelativeDb: number | null; /** How stationary the overall noise floor is, 0–1. */ stationarity: number | null; bands: BandVerdict[]; } /** 1 when the band sits perfectly still, 0 once it moves like a performance. */ export declare function stationarityOf(spreadDb: number | null): number | null; /** * Decide whether a band carries performance, a stationary noise floor, or nothing. * * Spread decides first; level only separates "quiet noise" from "silence". * `fileIsDynamic` guards the level-based escape hatch: in a file that never * moves at all (pure tone, pure noise) loudness is no evidence of performance. */ export declare function classifyBandContent(band: Pick, musicReferenceDb: number | null, fileIsDynamic?: boolean, /** * Median band level. In a file that never moves, a band towering over the * median is a sustained tone (content), while flat noise sits close to it. */ medianBandDb?: number | null): BandContent; export interface NoiseModelInput { /** Whole-file band profile. */ profile: BandProfile; nyquistHz: number | null; /** * Band profile measured inside a gap in the performance — a true noise print. * When present and confident, it replaces percentile guesses. */ print?: BandProfile | null; printConfidence?: number; } /** A noise print is only trusted as a floor measurement above this confidence. */ export declare const TRUSTED_PRINT_CONFIDENCE = 0.35; /** * Build the noise model from measured band statistics. * Every field stays null when its inputs are unavailable. */ export declare function buildNoiseModel(input: NoiseModelInput): NoiseModel; /** * Highest frequency still carrying performance. Walks down from the top and * stops at the first band holding signal, so a hiss shelf above the real * bandwidth is not mistaken for content. */ export declare function estimateBandwidthHz(bands: BandVerdict[], nyquistHz: number | null): number | null; /** * Loudest stationary-noise band on one side of a frequency, relative to the * music core. Returns null when no band there reads as noise. */ export declare function stationaryNoiseRelativeDb(bands: BandVerdict[], musicReferenceDb: number | null, frequencyHz: number, side: "above" | "below"): number | null; /** * Score how audible a stationary noise region is, 0–1. * -55 dB below the music core is inaudible; -20 dB is intrusive. */ export declare function audibilityScore(relativeDb: number | null): number | null; /** * Confidence that a measured narrow-band excess is mains hum rather than music. * A musical note in that band comes and goes; hum is a fixed line sitting well * above the spectrum either side of it. */ export declare function humConfidence(excessDb: number | null): number;