Files
HaloKeymind/scripts/generate_button_voice.py
T

67 lines
2.8 KiB
Python

#!/usr/bin/env python3
"""Encode the fixed button-confirmation WAV catalog as independent IMA clips."""
import argparse
from pathlib import Path
from generate_gps_voice import encode, read
PHRASES = (
('ready', 'Ready'),
('notificationCleared', 'Notification cleared'),
('advertQueued', 'Advert queued'),
('advertFailed', 'Advert failed'),
('soundOn', 'Sound on'),
('soundOff', 'Sound off'),
('actionFailed', 'Action failed'),
('usbSetup', 'USB setup'),
('shuttingDown', 'Shutting down'),
('restarting', 'Restarting'),
('alertsSoundVibration', 'Sound and vibration'),
('alertsSoundOnly', 'Sound only'),
('alertsVibrationOnly', 'Vibration only'),
('alertsSilent', 'Silent'),
)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--directory', type=Path, required=True)
parser.add_argument('--output', type=Path, required=True)
parser.add_argument('--voice', help='Speech voice used to synthesize the source WAVs')
args = parser.parse_args()
if args.voice is not None and (not args.voice.strip() or '\n' in args.voice or '\r' in args.voice):
parser.error('--voice must be a nonempty single-line label')
lines = [
'// Generated by scripts/generate_button_voice.py. 8 kHz IMA ADPCM.',
'// Fixed spoken confirmations; no runtime speech engine.',
'#pragma once', '#include <helpers/ui/GpsVoice.h>',
'namespace mesh { namespace audio {',
]
if args.voice is not None:
lines.insert(2, f'// Voice: {args.voice}')
total = 0
for name, phrase in PHRASES:
samples = read(args.directory / (name + '.wav'))
# Shutdown drains audio nonblockingly for at most five seconds.
if not samples or len(samples) > 32000:
raise ValueError(f'{phrase}: expected a nonempty clip under four seconds')
encoded = encode(samples)
total += len(encoded)
if name == 'alertsSoundVibration':
# Only buzzer+vibration devices need the additional mode catalog.
lines.append('#ifdef HAS_DRV2605')
lines.extend([f'// "{phrase}"', f'static const uint8_t {name}Data[] = {{'])
for offset in range(0, len(encoded), 20):
lines.append(' ' + ','.join(f'0x{b:02x}' for b in encoded[offset:offset + 20]) + ',')
lines.extend(['};', f'static constexpr VoiceClip {name}Clip = {{',
f' {name}Data, sizeof({name}Data), {len(samples)}, 8000', '};'])
print(f'{phrase}: {len(samples) / 8000:.3f}s, {len(encoded)} bytes')
lines.append('#endif // HAS_DRV2605')
lines.append('}} // namespace mesh::audio')
args.output.write_text('\n'.join(lines) + '\n', encoding='utf-8')
print(f'Button confirmations: {total} audio bytes')
if __name__ == '__main__':
main()