/** * Quantisation and rate control — turning a granule's spectrum into integers * that fit a bit budget. * * This is the encoder's decision-making. Everything before it is a fixed * transform and everything after it is bookkeeping; here is where an encoder * chooses what to throw away, and it is the only place quality is decided. * * ## The quantiser * * Layer III quantises with a power law rather than linearly: * * ix = floor( (|xr| / 2^step)^(3/4) + 0.4054 ) * * which the decoder inverts as `ix^(4/3) * 2^step`. The exponent matches the * ear's response — loudness grows roughly as the cube root of intensity — so * equal quantiser steps are closer to equal perceptual steps than uniform * spacing would be. The 0.4054 offset is the rounding point that minimises * error in that warped domain; plain rounding at 0.5 biases the result low. * * `step` comes from two places: `global_gain`, one value for the whole granule * in quarter-power-of-two increments, and a per-band `scalefactor` on top of it. * That split is the whole design — the granule's overall level is coded once, * and only the *shape* of the noise across the spectrum costs extra bits. * * ## The two loops * * - **Inner loop (rate).** Find the smallest `global_gain` whose Huffman cost * fits the granule's bit budget. Coarser gain means smaller integers means * fewer bits, so the relationship is monotone and a binary search settles it * in eight trials. * - **Outer loop (distortion).** Measure the resulting error per scalefactor * band. Where it exceeds what that band is allowed, raise the band's * scalefactor and run the inner loop again. Amplifying costs bits, so the * inner loop responds by coarsening everything else — the two loops push * against each other and the process is stopped by iteration count, by * scalefactor range, or by success. * * ## Where the thresholds come from, and what they change * * `psychoacoustic.ts` supplies the per-band noise allowance. Given it, * {@link allocateScalefactors} sets the noise *shape* directly and the loops * refine from there; without it (`thresholds: null`) the quantiser runs flat, * spreading noise evenly across the spectrum. * * Shaping raises total squared error on purpose — it moves noise under the * signal, where it cannot be heard — so SNR gets *worse* and noise-to-mask gets * better. On dense material at 128 kbps, noise-to-mask improves by around 4 dB * and the proportion of audible bands falls from roughly half to a fifth, while * SNR drops about 8 dB. Any measurement that scores total error will therefore * rate a working model as neutral or harmful, which is exactly what happened to * the first attempt at this; `test/mp3.test.ts` gates on noise-to-mask instead. */ import type { BitWriter } from '../io/bits.js'; /** `part2_3_length` is a 12-bit field. */ export declare const MAX_PART2_3_LENGTH = 4095; /** One granule and channel, coded and ready to write. */ export interface GranuleCoding { /** 576 quantised lines, signed. */ values: Int32Array; globalGain: number; /** Transmitted scalefactors, bands 0–20. Bands 21–22 are always zero. */ scalefactors: Int32Array; scalefacCompress: number; /** The multiplier the decoder applies, 0.5 or 1. */ scalefacScale: number; preflag: boolean; bigValues: number; tableSelect: [number, number, number]; region0Count: number; region1Count: number; count1TableSelect: number; /** * End of the count1 region. Not transmitted — the decoder infers it by running * out of declared bits — but the writer needs it to know when to stop. */ count1End: number; scalefactorBits: number; huffmanBits: number; /** Scalefactor bits plus Huffman bits — what the side information declares. */ part2_3Length: number; /** 0 long, 1 start, 2 three short windows, 3 stop. */ blockType: number; /** Per-window attenuation, short blocks only. */ subblockGain: Int32Array; } /** How the 576 lines divide into the three coding regions. */ interface Partition { /** End of the pair-coded region; always even. */ bigValuesEnd: number; /** End of the count1 region, where the implicit zeros begin; always even. */ rzeroStart: number; } /** * Splits the spectrum into big_values, count1 and rzero. * * Walking down from the top: trailing zeros cost nothing at all, and above the * point where everything has quantised to -1, 0 or +1 the four-at-a-time count1 * tables are cheaper than pairs. Both scans step in the granularity the format * requires — pairs for big_values, quadruples for count1 — so the boundaries are * always legal. */ declare function partition(ix: Int32Array): Partition; /** * Picks where the three big_values regions divide. * * Each region gets its own Huffman table because low frequencies have very * different statistics from high ones. Splitting the coded range into rough * thirds by scalefactor band captures most of that: the exhaustive search over * all 16 × 8 legal splits buys another percent or two of bitrate and costs far * more than it returns at this stage, so it is left for the VBR work. */ declare function chooseRegions(bigValuesEnd: number, bands: Int16Array): [number, number]; /** The Huffman layout and cost for one already-quantised granule. */ interface HuffmanCoding { bits: number; bigValues: number; tableSelect: [number, number, number]; region0Count: number; region1Count: number; count1TableSelect: number; count1End: number; } /** * Where the three big_values regions begin, as sample positions. * * Shared by the cost estimator and the writer so the two cannot disagree — the * decoder picks a table purely from position, so a one-band difference between * what we costed and what we wrote would desynchronise the whole granule. * * Clamping to `bigValuesEnd` changes nothing the decoder sees: a boundary above * the coded range only means the region above it is empty either way. */ declare function regionStarts(region0Count: number, region1Count: number, bigValuesEnd: number, bands: Int16Array, blockType?: number): [number, number]; /** * Chooses regions, tables and the count1 variant, and returns the total cost. * * Runs once per step-size trial, so it is the encoder's hot path. */ declare function codeGranule(ix: Int32Array, bands: Int16Array, blockType?: number): HuffmanCoding; /** * Writes one granule's Huffman data. * * The table for each pair follows from its position, exactly as the decoder * derives it. Nothing above `count1End` is written — the decoder treats the rest * as zero once the declared bits run out. * * @returns Bits written, which must equal the coding's `huffmanBits`. */ export declare function writeGranuleData(writer: BitWriter, coding: GranuleCoding, sampleRate: number): number; /** * Picks the cheapest `scalefac_compress` that can carry these scalefactors. * * The field selects a pair of bit widths — one for bands 0–10, one for 11–20 — * from a fixed table, so not every combination of widths is available and the * cheapest legal pair has to be searched for. * * @returns The index and its cost in bits, or `null` if the values do not fit. */ declare function chooseScalefacCompress(scalefactors: Int32Array, short?: boolean): { index: number; bits: number; } | null; /** Scratch buffers reused across granules, so the encoder allocates nothing per frame. */ interface Workspace { xr34: Float32Array; ix: Int32Array; bandScale: Float32Array; bandGain: Float32Array; scalefactors: Int32Array; bestValues: Int32Array; bestScalefactors: Int32Array; distortion: Float32Array; subblockGain: Int32Array; bestSubblockGain: Int32Array; } /** Allocates the scratch buffers {@link quantizeGranule} needs. */ export declare function createWorkspace(): Workspace; export type { Workspace }; export interface QuantizeRequest { /** 576 spectral lines from the analysis filterbank. */ spectrum: Float32Array; sampleRate: number; /** Bits available for scalefactors and Huffman data together. */ budgetBits: number; /** How many times the outer loop may amplify bands. Only used with `thresholds`. */ maxOuterIterations?: number; /** * Per-band noise allowance, as mean squared error per line. Indexed by * scalefactor band — 22 for a long granule, 13 for a short one, where all * three windows of a band share an entry. * * Defaults to `null` — no shaping, noise spread evenly. `PsychoacousticModel` * produces these; the encoder supplies them unless asked not to. */ thresholds?: Float32Array | null; /** * 0 for a long granule, 2 for three short windows. Types 1 and 3 are the * transition windows and are quantised exactly like long blocks — only the * MDCT window differs, and that has already been applied by the analysis. */ blockType?: number; /** * Spend as few bits as the thresholds allow, instead of as many as the budget * allows. Requires `thresholds`; `budgetBits` becomes a ceiling rather than a * target. * * This is the whole difference between constant and variable bitrate. Constant * bitrate asks "how good can this granule be in 417 bytes?" and always answers * with 417 bytes, whether the granule is a cymbal crash or a held note. * Variable bitrate asks "how few bytes make this granule transparent?" and * lets the size follow the material. */ minimiseBits?: boolean; } /** * Codes one granule and channel into a bit budget. * * Always succeeds: if the budget cannot be met even at the coarsest step size — * which in practice means the budget is a handful of bits — the result is * silence rather than an exception, because dropping a granule is preferable to * emitting a frame no decoder can parse. */ export declare function quantizeGranule(request: QuantizeRequest, workspace: Workspace): GranuleCoding; export { partition, chooseRegions, chooseScalefacCompress, codeGranule, regionStarts }; export type { HuffmanCoding, Partition };