- Add continuous guided light-body-scan meditation (16 segments, 97% coverage) - Edge TTS API endpoint with Xiaoxiao neural voice at -40% rate - Pre-generated chime WAVs and guidance MP3s in public/audio/ - Mute toggle in practice page, sequenced non-overlapping audio - Fix middleware to serve /audio and /api/audio/tts without auth - All LLM output localized to Chinese; fix blank home/settings pages - DeepSeek API compat: json_object, increased max_tokens - Analytics, cron reminders, onboarding flow fixes Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
110 lines
3.9 KiB
Bash
Executable File
110 lines
3.9 KiB
Bash
Executable File
#!/bin/bash
|
|
# Generate practice audio assets.
|
|
# Prerequisites: Node.js, Python 3, edge-tts (pip install edge-tts)
|
|
set -e
|
|
|
|
AUDIO_DIR="$(cd "$(dirname "$0")/../public/audio" && pwd)"
|
|
mkdir -p "$AUDIO_DIR"
|
|
|
|
echo "=== Generating chime sounds (Node.js) ==="
|
|
node -e "
|
|
const fs = require('fs');
|
|
|
|
function writeWav(filename, buildSamples, sampleRate, durationSec) {
|
|
const numSamples = Math.floor(sampleRate * durationSec);
|
|
const samples = new Float32Array(numSamples);
|
|
buildSamples(samples, sampleRate, durationSec);
|
|
|
|
// Convert to 16-bit PCM
|
|
const buffer = Buffer.alloc(44 + numSamples * 2);
|
|
buffer.write('RIFF', 0);
|
|
buffer.writeUInt32LE(36 + numSamples * 2, 4);
|
|
buffer.write('WAVE', 8);
|
|
buffer.write('fmt ', 12);
|
|
buffer.writeUInt32LE(16, 16); // chunk size
|
|
buffer.writeUInt16LE(1, 20); // PCM
|
|
buffer.writeUInt16LE(1, 22); // mono
|
|
buffer.writeUInt32LE(sampleRate, 24);
|
|
buffer.writeUInt32LE(sampleRate * 2, 28); // byte rate
|
|
buffer.writeUInt16LE(2, 32); // block align
|
|
buffer.writeUInt16LE(16, 34); // bits per sample
|
|
buffer.write('data', 36);
|
|
buffer.writeUInt32LE(numSamples * 2, 40);
|
|
|
|
for (let i = 0; i < numSamples; i++) {
|
|
const s = Math.max(-1, Math.min(1, samples[i]));
|
|
buffer.writeInt16LE(Math.round(s * 32767), 44 + i * 2);
|
|
}
|
|
fs.writeFileSync(filename, buffer);
|
|
console.log(' Created:', filename);
|
|
}
|
|
|
|
// Bell-like chime: two sine harmonics (fundamental + octave) with quick decay
|
|
function bellTone(t, freq1, freq2, tStart, duration) {
|
|
if (t < tStart || t > tStart + duration) return 0;
|
|
const localT = (t - tStart) / duration;
|
|
const env = Math.exp(-localT * 6); // quick exponential decay
|
|
return 0.35 * env * (
|
|
Math.sin(2 * Math.PI * freq1 * t) * 0.6 +
|
|
Math.sin(2 * Math.PI * freq2 * t) * 0.4
|
|
);
|
|
}
|
|
|
|
// Start chime — welcoming ascending bell, 2s
|
|
writeWav('$AUDIO_DIR/start-chime.wav', (samples, sr, dur) => {
|
|
for (let i = 0; i < samples.length; i++) {
|
|
const t = i / sr;
|
|
let v = 0;
|
|
v += bellTone(t, 523.25, 1046.5, 0, 0.9); // C5
|
|
v += bellTone(t, 659.25, 1318.5, 0.15, 0.8); // E5
|
|
v += bellTone(t, 783.99, 1568.0, 0.3, 0.7); // G5
|
|
samples[i] = v;
|
|
}
|
|
}, 44100, 1.8);
|
|
|
|
// Soft tick — very short click, 0.08s
|
|
writeWav('$AUDIO_DIR/tick.wav', (samples, sr) => {
|
|
for (let i = 0; i < samples.length; i++) {
|
|
const t = i / sr;
|
|
const env = Math.exp(-t * 80);
|
|
samples[i] = 0.12 * env * Math.sin(2 * Math.PI * 1200 * t);
|
|
}
|
|
}, 44100, 0.08);
|
|
|
|
// End chime — resonant closing chord, 3s
|
|
writeWav('$AUDIO_DIR/end-chime.wav', (samples, sr, dur) => {
|
|
for (let i = 0; i < samples.length; i++) {
|
|
const t = i / sr;
|
|
let v = 0;
|
|
v += bellTone(t, 261.63, 523.25, 0, 1.5); // C4 (deeper)
|
|
v += bellTone(t, 329.63, 659.25, 0.12, 1.3); // E4
|
|
v += bellTone(t, 392.00, 783.99, 0.24, 1.1); // G4
|
|
v += bellTone(t, 523.25, 1046.5, 0.36, 0.9); // C5
|
|
samples[i] = v * 0.9;
|
|
}
|
|
}, 44100, 3.0);
|
|
|
|
console.log('Chimes done.');
|
|
"
|
|
|
|
echo ""
|
|
echo "=== Generating voice guidance (Edge TTS neural voices → mp3) ==="
|
|
|
|
# Chinese — Xiaoxiao (warm, mature female)
|
|
python3 -m edge_tts --voice "zh-CN-XiaoxiaoNeural" --rate="-40%" --text "继续专注,你做得很好。" --write-media "$AUDIO_DIR/mid-guidance-zh.mp3"
|
|
echo " Created: mid-guidance-zh.mp3"
|
|
|
|
python3 -m edge_tts --voice "zh-CN-XiaoxiaoNeural" --rate="-40%" --text "练习完成。请记录你的感受。" --write-media "$AUDIO_DIR/end-guidance-zh.mp3"
|
|
echo " Created: end-guidance-zh.mp3"
|
|
|
|
# English — Aria (neutral, professional female)
|
|
python3 -m edge_tts --voice "en-US-AriaNeural" --rate="-40%" --text "Stay focused. You're doing well." --write-media "$AUDIO_DIR/mid-guidance-en.mp3"
|
|
echo " Created: mid-guidance-en.mp3"
|
|
|
|
python3 -m edge_tts --voice "en-US-AriaNeural" --rate="-40%" --text "Practice complete. Take a moment to reflect." --write-media "$AUDIO_DIR/end-guidance-en.mp3"
|
|
echo " Created: end-guidance-en.mp3"
|
|
|
|
echo ""
|
|
echo "=== All audio assets generated in $AUDIO_DIR ==="
|
|
ls -lh "$AUDIO_DIR"
|