Feedback sonori (bip): avvio, stop, annullamento, timeout, completamento

This commit is contained in:
dev
2026-08-10 13:29:38 +02:00
parent c7388dc91d
commit 25395f4713
+63 -2
View File
@@ -363,11 +363,16 @@ function startRecording(ctx?: any): string {
} }
recording = { proc, file, startTime, timer }; recording = { proc, file, startTime, timer };
// se ffmpeg termina da solo (timeout 2 min), azzera lo stato // se ffmpeg termina da solo (timeout max durata), azzera lo stato e avvisa
proc.on("exit", () => { proc.on("exit", (code) => {
if (recording && recording.proc === proc) { if (recording && recording.proc === proc) {
if (recording.timer) clearInterval(recording.timer); if (recording.timer) clearInterval(recording.timer);
recording = null; recording = null;
playSound("timeout");
if (ctx) {
ctx.ui.setStatus("agy-rec", "");
ctx.ui.notify("⏰ Tempo massimo di registrazione raggiunto", "warning");
}
} }
}); });
return file; return file;
@@ -416,6 +421,55 @@ function cancelRecording(): Promise<void> {
}); });
} }
// ---------------------------------------------------------------------------
// Feedback sonori (bip) per le interazioni con la registrazione
// ---------------------------------------------------------------------------
type SoundType = "start" | "stop" | "cancel" | "timeout" | "done";
const SOUNDS: Record<SoundType, { freq: number; dur: number; repeat?: number }> = {
start: { freq: 880, dur: 0.15 }, // bip acuto breve (inizio)
stop: { freq: 660, dur: 0.25 }, // conferma
cancel: { freq: 220, dur: 0.3 }, // tono basso (annullamento)
timeout: { freq: 440, dur: 0.4, repeat: 2 }, // doppio bip di avviso
done: { freq: 990, dur: 0.2 }, // completamento (successo)
};
function playSound(type: SoundType) {
const cfg = SOUNDS[type];
if (!cfg) return;
const wav = path.join(os.tmpdir(), `agy-sound-${type}-${Date.now()}.wav`);
const { freq, dur, repeat = 1 } = cfg;
// genera il tono con ffmpeg (silenzioso) e riproduce con paplay/aplay/ffplay
try {
execFileAsync(
"ffmpeg",
["-hide_banner", "-loglevel", "error", "-y", "-f", "lavfi", "-i", `sine=frequency=${freq}:duration=${dur}`, wav],
{ timeout: 10_000 },
)
.then(() => {
for (let i = 0; i < repeat; i++) {
execFileAsync("paplay", [wav], { timeout: 5000 }).catch(() => {
// fallback su aplay/ffplay se paplay non disponibile
execFileAsync("aplay", ["-q", wav], { timeout: 5000 }).catch(() => {
execFileAsync("ffplay", ["-nodisp", "-autoexit", wav], { timeout: 5000 }).catch(() => {});
});
});
}
// pulizia
setTimeout(() => {
try {
fs.rmSync(wav, { force: true });
} catch {
/* ignora */
}
}, repeat * 1500);
})
.catch(() => {});
} catch {
/* ignora */
}
}
// Ottimizza l'audio: taglia il silenzio iniziale/finale e converte in WAV 16kHz mono // Ottimizza l'audio: taglia il silenzio iniziale/finale e converte in WAV 16kHz mono
async function optimizeAudio(input: string): Promise<string> { async function optimizeAudio(input: string): Promise<string> {
const output = input.replace(/\.wav$/, "-opt.wav"); const output = input.replace(/\.wav$/, "-opt.wav");
@@ -1336,10 +1390,12 @@ export default function agyExtension(pi: ExtensionAPI) {
async function handleRecordToggle(ctx: any) { async function handleRecordToggle(ctx: any) {
if (!recording) { if (!recording) {
startRecording(ctx); startRecording(ctx);
playSound("start");
ctx.ui.notify("🎙️ Registrazione avviata (F12 per fermare, Esc per annullare)", "info"); ctx.ui.notify("🎙️ Registrazione avviata (F12 per fermare, Esc per annullare)", "info");
return; return;
} }
playSound("stop");
ctx.ui.setStatus("agy-rec", "⏹️ Finalizzazione..."); ctx.ui.setStatus("agy-rec", "⏹️ Finalizzazione...");
const file = await stopRecording(); const file = await stopRecording();
if (!file) { if (!file) {
@@ -1356,6 +1412,7 @@ export default function agyExtension(pi: ExtensionAPI) {
if (!tr.text) { if (!tr.text) {
ctx.ui.setStatus("agy-rec", ""); ctx.ui.setStatus("agy-rec", "");
ctx.ui.notify(`Trascrizione fallita: ${tr.error ?? "vuota"}`, "error"); ctx.ui.notify(`Trascrizione fallita: ${tr.error ?? "vuota"}`, "error");
playSound("cancel");
return; return;
} }
const transcript = tr.text; const transcript = tr.text;
@@ -1383,6 +1440,7 @@ export default function agyExtension(pi: ExtensionAPI) {
pi.sendUserMessage(finalText, { deliverAs: "followUp" }); pi.sendUserMessage(finalText, { deliverAs: "followUp" });
} }
ctx.ui.notify("✅ Trascrizione inviata come prompt a pi", "info"); ctx.ui.notify("✅ Trascrizione inviata come prompt a pi", "info");
playSound("done");
// notifica vocale (config ttsNotify o AGY_TTS_NOTIFY=0 per disattivare) // notifica vocale (config ttsNotify o AGY_TTS_NOTIFY=0 per disattivare)
const ttsNotify = getConfig("ttsNotify") ?? "true"; const ttsNotify = getConfig("ttsNotify") ?? "true";
@@ -1405,6 +1463,7 @@ export default function agyExtension(pi: ExtensionAPI) {
await cancelRecording(); await cancelRecording();
ctx.ui.setStatus("agy-rec", ""); ctx.ui.setStatus("agy-rec", "");
ctx.ui.notify("❌ Registrazione annullata", "info"); ctx.ui.notify("❌ Registrazione annullata", "info");
playSound("cancel");
} }
}, },
}); });
@@ -1416,6 +1475,7 @@ export default function agyExtension(pi: ExtensionAPI) {
await cancelRecording(); await cancelRecording();
ctx.ui.setStatus("agy-rec", ""); ctx.ui.setStatus("agy-rec", "");
ctx.ui.notify("❌ Registrazione annullata", "info"); ctx.ui.notify("❌ Registrazione annullata", "info");
playSound("cancel");
} }
}, },
}); });
@@ -1448,6 +1508,7 @@ export default function agyExtension(pi: ExtensionAPI) {
await cancelRecording(); await cancelRecording();
ctx.ui.setStatus("agy-rec", ""); ctx.ui.setStatus("agy-rec", "");
ctx.ui.notify("❌ Registrazione annullata", "info"); ctx.ui.notify("❌ Registrazione annullata", "info");
playSound("cancel");
}, },
}); });