/** Analysis window in samples (~93 ms at 44.1 kHz). */ export declare const DEFAULT_WINDOW_SAMPLES = 4096; /** * Band edges chosen so that each decision the planner makes has its own band: * rumble (<60), hum region (60–250), body/mud (250–500), vocal fundamentals * (500–2k), presence (2k–5k), then a fine ladder through the region where * archival bandwidth ends and hiss lives (5k–16k+). */ export declare const DEFAULT_BAND_SPLITS: readonly [60, 120, 250, 500, 1000, 2000, 3500, 5000, 7000, 9000, 12000, 16000]; export interface WindowRmsDistribution { /** Low percentile of window RMS in dBFS — the level this band falls back to. */ p05: number | null; p10: number | null; p50: number | null; p90: number | null; p95: number | null; /** p90 - p10. Small = stationary (noise-like), large = dynamic (musical). */ spreadDb: number | null; windowCount: number; } export interface BandMeasurement extends WindowRmsDistribution { lowHz: number; /** Infinity for the top band. */ highHz: number; centerHz: number; } export interface BandProfile { broadband: WindowRmsDistribution; bands: BandMeasurement[]; windowSamples: number; /** Broadband window RMS in playback order — used to locate quiet regions. */ broadbandSeries: number[]; } export interface QuietRegion { startSeconds: number; endSeconds: number; /** Mean window RMS inside the region, dBFS. */ levelDb: number; /** * How much quieter the region is than the body of the track, normalised 0–1. * A genuine gap sits far below the music; a merely quiet passage does not. */ confidence: number; } export interface SpectralStats { flatness: number | null; flux: number | null; centroid: number | null; rolloffHz: number | null; entropy: number | null; } export interface HumLine { frequencyHz: number; /** How far the narrow band sits above its own neighbourhood, in dB. */ excessDb: number | null; } export interface StereoField { midDb: number | null; sideDb: number | null; /** sideDb - midDb. Below about -20 dB the source is effectively mono. */ sideToMidDb: number | null; /** Side-channel level above 5 kHz relative to mid above 5 kHz. */ highSideToMidDb: number | null; } export interface SignalStats { rmsDb: number | null; peakDb: number | null; crestFactor: number | null; dcOffset: number | null; flatFactor: number | null; peakCount: number | null; } /** Percentile of an ascending-sorted array using nearest-rank. */ export declare function percentileOf(sortedAscending: number[], fraction: number): number | null; /** Summarize raw window RMS values (dBFS, unsorted) into a distribution. */ export declare function summarizeWindowRms(values: number[]): WindowRmsDistribution; /** * Build the single-pass graph: broadband plus one branch per crossover band, * each reporting per-window RMS. Branch order in the graph is the order the * `Parsed_ametadata_*` instances are numbered, which is how results are matched. */ export declare function buildBandProfileGraph(splits: readonly number[], windowSamples: number): string; /** * Group `ametadata` prints by filter instance. Instances are numbered in graph * order, so ascending instance number == the order branches were declared. */ export declare function parseWindowRmsByInstance(stderr: string): number[][]; export interface TimeRange { startSeconds: number; durationSeconds: number; } /** * Locate the quietest sustained region — the track's own noise print. * * Restoration quality depends on knowing the real noise floor, and the only * place it can be observed directly is where the performance stops. Percentiles * over a continuous performance measure the quietest *music*, not the noise. */ export declare function findQuietestRegion(series: number[], options: { windowSamples: number; sampleRate: number; minSeconds?: number; /** Reject regions that move more than this within the run, dB. */ maxTiltDb?: number; }): QuietRegion | null; /** * Measure the per-band windowed RMS distribution in one FFmpeg pass. * Returns null when the graph or parse fails — callers must treat that as * "unknown", never as "clean". */ export declare function measureBandProfile(inputPath: string, options?: { splits?: readonly number[]; windowSamples?: number; range?: TimeRange; }): Promise; /** * Total audio pushed through the band split on a long recording. * * The 13-band crossover dominates measurement cost — 28 s of a 10-minute file * versus 0.6 s to decode it — while band statistics converge long before a whole * song has been measured. */ export declare const DEFAULT_SAMPLE_SECONDS = 150; /** Evenly spaced contiguous excerpts totalling `budgetSeconds`. */ export declare function planSampleRanges(durationSeconds: number | null, budgetSeconds: number, chunks?: number): TimeRange[] | null; /** * Band statistics for a long file, measured from several contiguous excerpts. * * Excerpts are measured separately and their window values pooled. Splicing them * into one pass with `aselect` is cheaper but wrong: each boundary is a * discontinuity, and those broadband clicks land hardest in the empty top band — * the very band the noise-floor extrapolation reads. * * The broadband series still comes from one cheap whole-file pass, so its window * index maps to real time and a gap can be located anywhere in the recording. */ export declare function measureSampledBandProfile(inputPath: string, options: { durationSeconds: number | null; splits?: readonly number[]; windowSamples?: number; sampleSeconds?: number; }): Promise; /** * Whole-file broadband window RMS in playback order. One `astats` chain with no * band split, so it is cheap enough to run over the entire recording. */ export declare function measureBroadbandSeries(inputPath: string, windowSamples?: number): Promise; /** * `aspectralstats` writes to frame metadata only — reading its values requires * an `ametadata` print, and the keys carry a per-channel index * (`lavfi.aspectralstats.1.flatness`). Without both, nothing is emitted. */ export declare function measureSpectralStats(inputPath: string, options?: { maxSeconds?: number; winSize?: number; }): Promise; /** Mean of every per-channel value printed for one aspectralstats measure. */ export declare function meanOfMetadataKey(stderr: string, key: string): number | null; /** * Measure candidate mains-hum lines against their own neighbourhood. * A line only counts as hum when the narrow band sits clearly above the * spectrum either side of it — guessing 50 vs 60 Hz from low-end energy * notches real bass instead. */ export declare function measureHumLines(inputPath: string, options?: { frequencies?: readonly number[]; shoulderHz?: number; /** Mains hum is stationary, so an excerpt measures it as well as the whole file. */ range?: TimeRange; }): Promise; /** One overall RMS value per `astats` instance, in graph order. */ export declare function parseOverallRmsByInstance(stderr: string): Array; /** * Mid/side levels. Hiss is largely uncorrelated between channels, so on the * near-mono transfers typical of archival material the side channel is mostly * noise and can be cleaned far harder than the mid. */ export declare function measureStereoField(inputPath: string, options?: { range?: TimeRange; }): Promise; /** Overall time-domain statistics from a single `astats` pass. */ export declare function measureSignalStats(inputPath: string, options?: { range?: TimeRange; }): Promise;