Skip to content

Commit b3581f4

Browse files
committed
fix: auto-select compatible sample rate for opus/pcmu formats
opus defaults to 24000 Hz, pcmu_raw/pcmu_wav default to 8000 Hz. Explicit --sample-rate still takes precedence.
1 parent cfbeb91 commit b3581f4

4 files changed

Lines changed: 70 additions & 2 deletions

File tree

src/commands/speech/synthesize.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { detectOutputFormat, formatOutput } from '../../output/formatter';
88
import { saveAudioOutput } from '../../output/audio';
99
import { writeFileSync } from 'fs';
1010
import { readTextFromPathOrStdin } from '../../utils/fs';
11-
import { T2A_FORMATS, formatList, validateAudioFormat, validateT2AStreaming } from '../../utils/audio-formats';
11+
import { T2A_FORMATS, formatList, validateAudioFormat, validateT2AStreaming, t2aDefaultSampleRate } from '../../utils/audio-formats';
1212
import type { Config } from '../../config/schema';
1313
import type { GlobalFlags } from '../../types/flags';
1414
import type { SpeechRequest, SpeechResponse } from '../../types/api';
@@ -81,7 +81,7 @@ export default defineCommand({
8181
},
8282
audio_setting: {
8383
format: (flags.format as string) || 'mp3',
84-
sample_rate: (flags.sampleRate as number) ?? 32000,
84+
sample_rate: (flags.sampleRate as number) ?? t2aDefaultSampleRate(ext, 32000),
8585
bitrate: (flags.bitrate as number) ?? 128000,
8686
channel: (flags.channels as number) ?? 1,
8787
},

src/utils/audio-formats.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ export function validateAudioFormat(format: string, formats: readonly string[]):
2020
}
2121
}
2222

23+
const T2A_SAMPLE_RATE: Partial<Record<T2AFormat, number>> = {
24+
opus: 24000,
25+
pcmu_raw: 8000,
26+
pcmu_wav: 8000,
27+
};
28+
29+
export function t2aDefaultSampleRate(format: string, fallback: number): number {
30+
return T2A_SAMPLE_RATE[format as T2AFormat] ?? fallback;
31+
}
32+
2333
export function validateT2AStreaming(format: string, stream: boolean): void {
2434
if (stream && format === 'wav') {
2535
throw new CLIError(

test/commands/speech/synthesize.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,4 +259,43 @@ describe('speech synthesize format validation', () => {
259259
synthesizeCommand.execute(config, { ...flags, format: 'wav', stream: true }),
260260
).rejects.toThrow('wav format is not supported in streaming');
261261
});
262+
263+
it('defaults opus sample rate to 24000', async () => {
264+
const originalLog = console.log;
265+
let output = '';
266+
console.log = (msg: string) => { output += msg; };
267+
try {
268+
await synthesizeCommand.execute(config, { ...flags, format: 'opus' });
269+
const parsed = JSON.parse(output);
270+
expect(parsed.request.audio_setting.sample_rate).toBe(24000);
271+
} finally {
272+
console.log = originalLog;
273+
}
274+
});
275+
276+
it('defaults pcmu_wav sample rate to 8000', async () => {
277+
const originalLog = console.log;
278+
let output = '';
279+
console.log = (msg: string) => { output += msg; };
280+
try {
281+
await synthesizeCommand.execute(config, { ...flags, format: 'pcmu_wav' });
282+
const parsed = JSON.parse(output);
283+
expect(parsed.request.audio_setting.sample_rate).toBe(8000);
284+
} finally {
285+
console.log = originalLog;
286+
}
287+
});
288+
289+
it('respects explicit --sample-rate even for opus', async () => {
290+
const originalLog = console.log;
291+
let output = '';
292+
console.log = (msg: string) => { output += msg; };
293+
try {
294+
await synthesizeCommand.execute(config, { ...flags, format: 'opus', sampleRate: 16000 });
295+
const parsed = JSON.parse(output);
296+
expect(parsed.request.audio_setting.sample_rate).toBe(16000);
297+
} finally {
298+
console.log = originalLog;
299+
}
300+
});
262301
});

test/utils/audio-formats.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
formatList,
66
validateAudioFormat,
77
validateT2AStreaming,
8+
t2aDefaultSampleRate,
89
} from '../../src/utils/audio-formats';
910

1011
describe('audio-formats', () => {
@@ -52,4 +53,22 @@ describe('audio-formats', () => {
5253
expect(formatList(['a', 'b', 'c'])).toBe('a, b, c');
5354
});
5455
});
56+
57+
describe('t2aDefaultSampleRate', () => {
58+
it('returns 24000 for opus', () => {
59+
expect(t2aDefaultSampleRate('opus', 32000)).toBe(24000);
60+
});
61+
62+
it('returns 8000 for pcmu_raw', () => {
63+
expect(t2aDefaultSampleRate('pcmu_raw', 32000)).toBe(8000);
64+
});
65+
66+
it('returns 8000 for pcmu_wav', () => {
67+
expect(t2aDefaultSampleRate('pcmu_wav', 32000)).toBe(8000);
68+
});
69+
70+
it('returns fallback for mp3', () => {
71+
expect(t2aDefaultSampleRate('mp3', 32000)).toBe(32000);
72+
});
73+
});
5574
});

0 commit comments

Comments
 (0)