mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-20 19:41:59 +00:00
85 lines
4.1 KiB
PowerShell
85 lines
4.1 KiB
PowerShell
# Reproduce the selected Zira / compressed button recordings for supported
|
|
# Full Companions; playback gain is configured by the firmware.
|
|
# GPS direction phrases are generated separately by generate_gps_voice.ps1.
|
|
[CmdletBinding(SupportsShouldProcess = $true)]
|
|
param(
|
|
[ValidateNotNullOrEmpty()]
|
|
[string]$OutputDirectory = 'out/gps-voice/button-zira',
|
|
[ValidateNotNullOrEmpty()]
|
|
[string]$OutputHeader = 'src/helpers/ui/ButtonVoiceData.h',
|
|
[ValidateNotNullOrEmpty()]
|
|
[string]$Voice = 'Microsoft Zira Desktop',
|
|
# Reuse original, already selected clips while adding a new catalog entry.
|
|
[switch]$OnlyMissing
|
|
)
|
|
$ErrorActionPreference = 'Stop'
|
|
# Diagnostic voices must name a deliberate header target; the default stays
|
|
# the selected Zira / rate -1 / compressed / peak-0.12 profile.
|
|
if (-not $PSBoundParameters.ContainsKey('OutputHeader') -and
|
|
$Voice -ne 'Microsoft Zira Desktop') {
|
|
throw 'Voice variants require an explicit -OutputHeader (use an out/gps-voice path for tests).'
|
|
}
|
|
$buttonDirectory = [IO.Path]::GetFullPath($OutputDirectory)
|
|
$buttonHeader = [IO.Path]::GetFullPath($OutputHeader)
|
|
if ([IO.Path]::GetExtension($buttonHeader) -ine '.h') {
|
|
throw '-OutputHeader must name a .h file.'
|
|
}
|
|
if (Test-Path -LiteralPath $buttonDirectory -PathType Leaf) {
|
|
throw '-OutputDirectory names an existing file.'
|
|
}
|
|
if (Test-Path -LiteralPath $buttonHeader -PathType Container) {
|
|
throw '-OutputHeader names an existing directory.'
|
|
}
|
|
$buttonEncoder = Join-Path $PSScriptRoot 'generate_button_voice.py'
|
|
if (-not (Test-Path -LiteralPath $buttonEncoder -PathType Leaf)) {
|
|
throw "Voice encoder not found: $buttonEncoder"
|
|
}
|
|
if (-not $PSCmdlet.ShouldProcess($buttonHeader, "Synthesize and encode $Voice button confirmations")) {
|
|
return
|
|
}
|
|
|
|
$buttonPhrases = [ordered]@{
|
|
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'
|
|
}
|
|
$buttonFilter = 'highpass=f=350,lowpass=f=3400,silenceremove=start_periods=1:start_threshold=-45dB,areverse,silenceremove=start_periods=1:start_threshold=-45dB,areverse,acompressor=threshold=0.08:ratio=6:attack=5:release=70:makeup=4:knee=4:detection=rms,volume=1.4,aresample=8000,alimiter=limit=0.12:level=disabled,apad=pad_dur=0.05'
|
|
Add-Type -AssemblyName System.Speech
|
|
$buttonVoice = New-Object System.Speech.Synthesis.SpeechSynthesizer
|
|
try {
|
|
$buttonVoices = @($buttonVoice.GetInstalledVoices() | Where-Object Enabled | ForEach-Object { $_.VoiceInfo.Name })
|
|
if ($buttonVoices -notcontains $Voice) {
|
|
throw "Voice '$Voice' is unavailable. Installed voices: $($buttonVoices -join ', ')"
|
|
}
|
|
Get-Command ffmpeg, python -ErrorAction Stop | Out-Null
|
|
$buttonVoice.SelectVoice($Voice)
|
|
$buttonVoice.Rate = -1
|
|
New-Item -ItemType Directory -Force -Path $buttonDirectory | Out-Null
|
|
New-Item -ItemType Directory -Force -Path ([IO.Path]::GetDirectoryName($buttonHeader)) | Out-Null
|
|
foreach ($buttonName in $buttonPhrases.Keys) {
|
|
$buttonRaw = Join-Path $buttonDirectory "$buttonName-raw.wav"
|
|
$buttonPcm = Join-Path $buttonDirectory "$buttonName.wav"
|
|
if ($OnlyMissing -and (Test-Path -LiteralPath $buttonPcm -PathType Leaf)) {
|
|
continue
|
|
}
|
|
$buttonVoice.SetOutputToWaveFile($buttonRaw)
|
|
$buttonVoice.Speak($buttonPhrases[$buttonName])
|
|
$buttonVoice.SetOutputToNull()
|
|
& ffmpeg -hide_banner -loglevel error -y -i $buttonRaw -af $buttonFilter -ar 8000 -ac 1 -c:a pcm_s16le $buttonPcm
|
|
if ($LASTEXITCODE -ne 0) { throw "Voice conversion failed: $buttonName" }
|
|
}
|
|
} finally { $buttonVoice.Dispose() }
|
|
& python $buttonEncoder --directory $buttonDirectory --output $buttonHeader --voice $Voice
|
|
if ($LASTEXITCODE -ne 0) { throw 'Button voice encoding failed' }
|