{"_id":"@audio/measure-lossy","name":"@audio/measure-lossy","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@audio/measure-lossy","version":"0.1.0","description":"Lossy-transcode detection — spectral-cutoff estimation + source classification (Spek / aucdtect class)","type":"module","sideEffects":false,"main":"lossy.js","exports":{".":"./lossy.js","./audio":{"types":"./audio.d.ts","default":"./audio.js"},"./package.json":"./package.json"},"dependencies":{"@audio/spectral-ltas":"^1.1.4","fourier-transform":"^2.4.1"},"devDependencies":{"@audio/decode":"^3.14.1","@audio/encode-mp3":"^1.2.2","@audio/encode-ogg":"^1.2.2","@audio/encode-opus":"^1.3.0","@audio/resample-sinc":"^1.1.2","audio-lena":"^3.0.1","tst":"^9.4.0"},"keywords":["audio","dsp","measurement","lossy","transcode","mp3","aac","spectral-cutoff","forensics"],"license":"MIT","author":{"name":"Dmitry Iv.","email":"dfcreative@gmail.com"},"engines":{"node":">=18"},"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/audiojs/measure.git","directory":"packages/measure-lossy"},"types":"index.d.ts","audio":"./audio.js","funding":"https://github.com/sponsors/audiojs","gitHead":"f2a50859fe5dabcd2f9b0199ff5cbfecca2f31ec","_id":"@audio/measure-lossy@0.1.0","bugs":{"url":"https://github.com/audiojs/measure/issues"},"homepage":"https://github.com/audiojs/measure#readme","_nodeVersion":"25.9.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-PkgI0bZD2Jah0eSLIduMLUk1/eSkcSf7hSsxWfEXmTq4EeatvQzXYfjGvNFemiRbnEOFzRGjEdNLIUHHD/ZyqA==","shasum":"a4c0bc5321c579d40a8811672d5fc504e03d0779","tarball":"https://registry.npmjs.org/@audio/measure-lossy/-/measure-lossy-0.1.0.tgz","fileCount":6,"unpackedSize":27416,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCDRiFgfFicXCIkmlWftqrEadkzh7vbXRb0eLLX37s4pgIhANvw2lxp5Np31w3835kHzYrXIF4GSZaJBjPQITZ6i4MC"}]},"_npmUser":{"name":"dy","email":"df.creative@gmail.com"},"directories":{},"maintainers":[{"name":"jamen","email":"jamenmarz@gmail.com"},{"name":"dfcreative","email":"df.creative@gmail.com"},{"name":"dy","email":"df.creative@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/measure-lossy_0.1.0_1787959422822_0.2705474926821845"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-28T23:23:42.602Z","0.1.0":"2026-08-28T23:23:42.957Z","modified":"2026-08-28T23:23:43.260Z"},"maintainers":[{"name":"jamen","email":"jamenmarz@gmail.com"},{"name":"dfcreative","email":"df.creative@gmail.com"},{"name":"dy","email":"df.creative@gmail.com"}],"description":"Lossy-transcode detection — spectral-cutoff estimation + source classification (Spek / aucdtect class)","homepage":"https://github.com/audiojs/measure#readme","keywords":["audio","dsp","measurement","lossy","transcode","mp3","aac","spectral-cutoff","forensics"],"repository":{"type":"git","url":"git+https://github.com/audiojs/measure.git","directory":"packages/measure-lossy"},"author":{"name":"Dmitry Iv.","email":"dfcreative@gmail.com"},"bugs":{"url":"https://github.com/audiojs/measure/issues"},"license":"MIT","readme":"# @audio/measure-lossy [![npm](https://img.shields.io/npm/v/@audio/measure-lossy)](https://www.npmjs.com/package/@audio/measure-lossy) [![MIT](https://img.shields.io/badge/MIT-%E0%A5%90-white)](https://github.com/krishnized/license)\n\nLossy-transcode detection — spectral-cutoff estimation + source classification (Spek / aucdtect class)\n\n```\nnpm install @audio/measure-lossy\n```\n\n```js\nimport lossy from '@audio/measure-lossy'\n```\n\nIs this \"lossless\" file really a lossless master, or an upsampled/transcoded MP3, AAC, Vorbis or Opus source relabelled as WAV/FLAC? Reads the file's long-term average spectrum (Welch, via [@audio/spectral-ltas](https://github.com/audiojs/spectral)) plus a per-frame max-hold spectrum, finds where content stops (a sustained dB drop, not a notch), measures how steep that edge is, and counts the \"swiss cheese\" spectral holes and the MP3-specific ~16 kHz scalefactor-band notch that give away a lossy source. This is the numeric form of the manual \"look at the spectrogram\" method popularised by [Spek](https://www.spek.cc/) and [aucdtect](https://github.com/lifestream1/lossless-audio-checker)/\"Fakin' the Funk\" guessers — the same heuristics (spectrogram cutoff, sfb21 notch), just measured instead of eyeballed.\n\n```js\nlet r = lossy(data, { fs: 44100 })\nr.cutoff      // Hz — estimated content bandwidth\nr.lossy       // boolean verdict\nr.confidence  // 0..1, heuristic — not a calibrated probability\nr.source      // best guess: 'mp3-128', 'opus-96', 'lossless', 'upsampled', 'unknown', …\nr.evidence    // the numbers behind the verdict (see below)\nr.spectrum    // { freqs, db } — the LTAS used, for plotting\n```\n\n`data` is a mono `Float32Array`, or `Float32Array[]` (one per channel — mixed down internally).\n\n| Option | Default | |\n|---|---|---|\n| `fs` | `44100` | sample rate, Hz |\n| `frameSize` | `4096` | Welch/STFT analysis window, samples |\n| `hop` | `frameSize / 2` | frame hop, samples |\n| `floorDb` | `-90` | bins at or below this level count as \"empty\" |\n| `minDrop` | `20` | dB drop that counts as a spectral cutoff |\n| `maxSeconds` | `120` | analyse at most this much, evenly sampled across the file |\n\n`evidence`:\n\n| Field | | |\n|---|---|---|\n| `cutoffDb` | `number` | dB drop from the reference band to the cutoff bin |\n| `sfb21` | `boolean` | MP3's ~16 kHz scalefactor-band starvation notch, independent of the overall cutoff |\n| `cutoffSharpness` | `number` | slope at the edge, dB/octave — a codec lowpass is steep (>60), a natural roll-off is gentle |\n| `holes` | `number` | persistent 4-16 kHz spectral holes per second (\"swiss cheese\" — low-bitrate MP3 signature) |\n| `upsampled` | `boolean` | cutoff sits at a clean fraction of `fs` — a sample-rate upsample, not a codec lowpass |\n| `bandwidthRatio` | `number` | `cutoff / (fs / 2)` |\n\nMethod (cited in full in `lossy.js`): LTAS + max-hold spectra over the analysed span; the highest frequency where the (smoothed) LTAS drops `minDrop` dB relative to the band 2 kHz below and stays down to Nyquist is the cutoff — max-hold arbitrates, extending the estimate toward Nyquist when transients demonstrably punch past what looked like a wall, but never resetting a genuine low cutoff back up on one loud click. Spectral holes and the MP3 sfb21 notch follow Hennequin et al. (\"Codec-independent lossy audio compression detection\", ICASSP 2017) and Yang et al. (\"Detecting MP3 compression history\", 2008); the bitrate→cutoff source table is LAME's/aoTuVb's/Opus's published defaults, with citations and measured caveats in `lossy.js`.\n\n**This method loses power above roughly 128-160 kbps** — that's a documented, known limit of spectral-cutoff detection generally (aucdtect and Spek-based \"guessers\" share it), not specific to this implementation: well-tuned psychoacoustic encoders at moderate-to-high bitrates can be spectrally near-transparent. It is also fooled the other way by **naturally band-limited material** — a cassette dub, a narrow-band mic, an aggressive de-esser, or plain speech (see the `audio-lena/raw` test in `test.js`) can read exactly like a lossy cutoff. `evidence` exposes every number precisely so a caller/UI can judge instead of trusting a single boolean; `confidence` is a heuristic score, not a calibrated probability.\n\nAlso exported as an `audio.js` stat manifest (`./audio` — `a.stat('lossy')`, no pre-fold needed since the kernel already accepts multichannel input).\n\n**Use when:** verifying a purchased/downloaded \"lossless\" file is what it claims to be; auditing a library for mislabelled transcodes; flagging upsampled masters before mastering/distribution.\n\n---\n\nPart of [@audio/measure](https://github.com/audiojs/measure) — the measure family umbrella.\n\nMIT © [audiojs](https://github.com/audiojs)\n","readmeFilename":"README.md","_rev":"1-0889e6a3fb38b43911d0a06299ed30f3"}