fix(tts): back to fast Piper controllers, classroom voice setting

Kokoro-82M generates several times slower than Piper — since the controller
pool moved onto it, every ATC reply arrived seconds late. Controllers return
to distinct Piper speakers with the standard US voice (ryan) as the product
default, so replies are fast again and the default sounds like before.

The classroom gets an instructor-voice setting: standard US voice by default,
'random per module' (stable instructor per module), or one of ten named
US/GB voices.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
itsrubberduck
2026-07-19 12:36:19 +02:00
parent f03355daff
commit 4f367a67aa
5 changed files with 139 additions and 28 deletions

View File

@@ -2,9 +2,13 @@ import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import {
CLASSROOM_DEFAULT_VOICE,
CLASSROOM_RANDOM_VOICE,
CLASSROOM_VOICE_OPTIONS,
CONTROLLER_VOICES,
PILOT_VOICES,
RESERVED_VOICES,
classroomVoiceFor,
controllerPersonaFor,
controllerVoiceFor,
fnv1a,
@@ -131,6 +135,33 @@ describe('voicePool — controller personas', () => {
})
})
describe('voicePool — classroom voices', () => {
it('defaults to the standard US speaker', () => {
assert.equal(CLASSROOM_DEFAULT_VOICE, 'alloy')
const option = CLASSROOM_VOICE_OPTIONS.find(o => o.id === CLASSROOM_DEFAULT_VOICE)
assert.ok(option, 'default voice must be selectable')
assert.equal(option!.accent, 'US')
})
it('offers unique selectable ids and the random sentinel is not one of them', () => {
const ids = CLASSROOM_VOICE_OPTIONS.map(o => o.id)
assert.equal(new Set(ids).size, ids.length)
assert.equal(ids.includes(CLASSROOM_RANDOM_VOICE), false)
})
it('assigns a stable random voice per module from the selectable set', () => {
const ids = CLASSROOM_VOICE_OPTIONS.map(o => o.id)
const seen = new Set<string>()
for (const module of ['fundamentals', 'readbacks', 'atc-advanced', 'full-flight', 'metar']) {
const voice = classroomVoiceFor(`classroom:${module}`)
assert.equal(classroomVoiceFor(`classroom:${module}`), voice, 'must be deterministic')
assert.ok(ids.includes(voice), `${voice} must be selectable`)
seen.add(voice)
}
assert.ok(seen.size > 1, 'different modules should not all share one voice')
})
})
describe('voicePool — fnv1a', () => {
it('matches the reference vectors', () => {
assert.equal(fnv1a(''), 0x811c9dc5)