From c4e805d0497f780209abf157933b31b37f2f3c25 Mon Sep 17 00:00:00 2001 From: Daniel Banariba Date: Mon, 24 Aug 2026 14:48:46 -0600 Subject: [PATCH] feat(ui): show sample rate and bit depth for lossless tracks The Quality column appends the bitrate for lossy formats, so `MP3 320` tells you what you are listening to. Lossless formats have no meaningful bitrate to append, so they all render as a bare `FLAC`, and a CD rip is indistinguishable from a 96/24 remaster sitting next to it in the list. Append the PCM stream characteristics instead, giving `FLAC 96/24`. Sample rate is shown in kHz without a trailing zero, so 44100 reads as `44.1` and 96000 as `96`. When the bit depth is unknown the rate is shown on its own, and when neither is known the format is rendered exactly as before. DSD is left showing the container alone: it carries a 1-bit stream at MHz rates, where `2822.4/1` would say nothing useful. Modulation is still an open question in #1438 and wants its own representation. Part of #1438. --- ui/src/common/QualityInfo.jsx | 37 +++++++++++++++++++++++--- ui/src/common/QualityInfo.test.jsx | 42 +++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/ui/src/common/QualityInfo.jsx b/ui/src/common/QualityInfo.jsx index 57a8251a4..c2f3b00d4 100644 --- a/ui/src/common/QualityInfo.jsx +++ b/ui/src/common/QualityInfo.jsx @@ -9,6 +9,24 @@ import { calculateGain } from '../utils/calculateReplayGain' const llFormats = new Set(config.losslessFormats.split(',')) const placeholder = 'N/A' +// Lossless streams have no meaningful bitrate to show, so their quality is +// described by the PCM characteristics instead: `FLAC 96/24`. +const formatSampleRate = (sampleRate) => { + const khz = sampleRate / 1000 + return Number.isInteger(khz) ? String(khz) : khz.toFixed(1) +} + +const losslessDetail = (sampleRate, bitDepth) => { + // DSD carries a 1-bit stream at MHz rates, where `2822.4/1` would say + // nothing useful. Those formats keep showing the container alone until + // modulation gets its own representation. + if (bitDepth === 1 || !(sampleRate > 0)) { + return '' + } + const rate = formatSampleRate(sampleRate) + return bitDepth > 0 ? `${rate}/${bitDepth}` : rate +} + const useStyle = makeStyles( (theme) => ({ chip: { @@ -30,14 +48,27 @@ export const QualityInfo = ({ isDirectPlay, }) => { const classes = useStyle() - let { suffix, bitRate, rgAlbumGain, rgAlbumPeak, rgTrackGain, rgTrackPeak } = - record + let { + suffix, + bitRate, + sampleRate, + bitDepth, + rgAlbumGain, + rgAlbumPeak, + rgTrackGain, + rgTrackPeak, + } = record let info = placeholder if (suffix) { suffix = suffix.toUpperCase() info = suffix - if (!llFormats.has(suffix) && bitRate > 0) { + if (llFormats.has(suffix)) { + const detail = losslessDetail(sampleRate, bitDepth) + if (detail) { + info += ' ' + detail + } + } else if (bitRate > 0) { info += ' ' + bitRate } } diff --git a/ui/src/common/QualityInfo.test.jsx b/ui/src/common/QualityInfo.test.jsx index 174ee8a85..fb9553ad1 100644 --- a/ui/src/common/QualityInfo.test.jsx +++ b/ui/src/common/QualityInfo.test.jsx @@ -5,11 +5,51 @@ import { QualityInfo } from './QualityInfo' describe('', () => { afterEach(cleanup) - it('only render suffix for lossless formats', () => { + it('only renders suffix for lossless formats without stream details', () => { const info = { suffix: 'FLAC', bitRate: 1008 } render() expect(screen.getByText('FLAC')).toBeInTheDocument() }) + it('renders sample rate and bit depth for lossless formats', () => { + const info = { + suffix: 'FLAC', + bitRate: 1008, + sampleRate: 96000, + bitDepth: 24, + } + render() + expect(screen.getByText('FLAC 96/24')).toBeInTheDocument() + }) + it('renders CD quality without a trailing zero', () => { + const info = { suffix: 'FLAC', sampleRate: 44100, bitDepth: 16 } + render() + expect(screen.getByText('FLAC 44.1/16')).toBeInTheDocument() + }) + it('keeps one decimal for rates that are not whole kHz', () => { + const info = { suffix: 'FLAC', sampleRate: 88200, bitDepth: 24 } + render() + expect(screen.getByText('FLAC 88.2/24')).toBeInTheDocument() + }) + it('renders the sample rate alone when bit depth is unknown', () => { + const info = { suffix: 'FLAC', sampleRate: 192000 } + render() + expect(screen.getByText('FLAC 192')).toBeInTheDocument() + }) + it('renders stream details for other lossless containers', () => { + const info = { suffix: 'WAV', sampleRate: 48000, bitDepth: 32 } + render() + expect(screen.getByText('WAV 48/32')).toBeInTheDocument() + }) + it('leaves DSD showing the container alone', () => { + const info = { suffix: 'DSF', sampleRate: 2822400, bitDepth: 1 } + render() + expect(screen.getByText('DSF')).toBeInTheDocument() + }) + it('ignores stream details for lossy formats', () => { + const info = { suffix: 'MP3', bitRate: 320, sampleRate: 44100 } + render() + expect(screen.getByText('MP3 320')).toBeInTheDocument() + }) it('only render suffix and bitrate for lossy formats', () => { const info = { suffix: 'MP3',