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',