feat: /testcall /testecho /testmic — local audio diagnostics
- /testcall: plays 3 tones (440/880/660Hz) to test speaker - /testecho: mic → speaker loopback with 100ms delay (/stopecho to end) - /testmic: records 3 seconds, plays back (tests both mic + speaker) No relay or peer needed — pure local browser audio. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2068,6 +2068,97 @@ async function doSend() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (text === '/call') { startCall(); return; }
|
if (text === '/call') { startCall(); return; }
|
||||||
|
if (text === '/testcall' || text === '/testtone') {
|
||||||
|
addSys('Audio test: playing 440Hz tone for 3 seconds...');
|
||||||
|
try {
|
||||||
|
const ctx = new AudioContext({ sampleRate: 48000 });
|
||||||
|
const osc = ctx.createOscillator();
|
||||||
|
osc.type = 'sine';
|
||||||
|
osc.frequency.value = 440;
|
||||||
|
osc.connect(ctx.destination);
|
||||||
|
osc.start();
|
||||||
|
setTimeout(() => {
|
||||||
|
osc.frequency.value = 880;
|
||||||
|
addSys('Audio test: 880Hz...');
|
||||||
|
}, 1000);
|
||||||
|
setTimeout(() => {
|
||||||
|
osc.frequency.value = 660;
|
||||||
|
addSys('Audio test: 660Hz...');
|
||||||
|
}, 2000);
|
||||||
|
setTimeout(() => {
|
||||||
|
osc.stop();
|
||||||
|
ctx.close();
|
||||||
|
addSys('Audio test: done. If you heard 3 tones, speaker works.');
|
||||||
|
}, 3000);
|
||||||
|
} catch(e) { addSys('Audio test failed: ' + e.message); }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (text === '/testecho') {
|
||||||
|
addSys('Echo test: speak into mic, you should hear yourself...');
|
||||||
|
addSys('Type /stopecho to stop.');
|
||||||
|
try {
|
||||||
|
const ctx = new AudioContext({ sampleRate: 48000 });
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
|
audio: { sampleRate: 48000, channelCount: 1, echoCancellation: false, noiseSuppression: false }
|
||||||
|
});
|
||||||
|
const source = ctx.createMediaStreamSource(stream);
|
||||||
|
// Direct mic → speaker loopback (with small delay to avoid feedback)
|
||||||
|
const delay = ctx.createDelay(0.15);
|
||||||
|
delay.delayTime.value = 0.1;
|
||||||
|
source.connect(delay);
|
||||||
|
delay.connect(ctx.destination);
|
||||||
|
addSys('Echo active \u2014 mic \u2192 speaker (100ms delay)');
|
||||||
|
window._echoTest = { ctx, stream, source, delay };
|
||||||
|
} catch(e) { addSys('Echo test failed: ' + e.message); }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (text === '/stopecho') {
|
||||||
|
if (window._echoTest) {
|
||||||
|
window._echoTest.stream.getTracks().forEach(t => t.stop());
|
||||||
|
window._echoTest.source.disconnect();
|
||||||
|
window._echoTest.delay.disconnect();
|
||||||
|
window._echoTest.ctx.close();
|
||||||
|
window._echoTest = null;
|
||||||
|
addSys('Echo test stopped.');
|
||||||
|
} else { addSys('No echo test running.'); }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (text === '/testmic') {
|
||||||
|
addSys('Mic test: recording 3 seconds, then playback...');
|
||||||
|
try {
|
||||||
|
const ctx = new AudioContext({ sampleRate: 48000 });
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
|
audio: { sampleRate: 48000, channelCount: 1, echoCancellation: true, noiseSuppression: true }
|
||||||
|
});
|
||||||
|
const source = ctx.createMediaStreamSource(stream);
|
||||||
|
const processor = ctx.createScriptProcessor(4096, 1, 1);
|
||||||
|
const recorded = [];
|
||||||
|
processor.onaudioprocess = (e) => {
|
||||||
|
recorded.push(new Float32Array(e.inputBuffer.getChannelData(0)));
|
||||||
|
};
|
||||||
|
source.connect(processor);
|
||||||
|
processor.connect(ctx.destination);
|
||||||
|
addSys('Recording... speak now');
|
||||||
|
setTimeout(() => {
|
||||||
|
processor.disconnect();
|
||||||
|
source.disconnect();
|
||||||
|
stream.getTracks().forEach(t => t.stop());
|
||||||
|
// Concatenate and play back
|
||||||
|
const total = recorded.reduce((s, a) => s + a.length, 0);
|
||||||
|
const buffer = ctx.createBuffer(1, total, 48000);
|
||||||
|
const channel = buffer.getChannelData(0);
|
||||||
|
let offset = 0;
|
||||||
|
for (const chunk of recorded) { channel.set(chunk, offset); offset += chunk.length; }
|
||||||
|
addSys('Playing back ' + (total / 48000).toFixed(1) + 's of audio...');
|
||||||
|
const playSource = ctx.createBufferSource();
|
||||||
|
playSource.buffer = buffer;
|
||||||
|
playSource.connect(ctx.destination);
|
||||||
|
playSource.start();
|
||||||
|
playSource.onended = () => { ctx.close(); addSys('Mic test done. If you heard yourself, mic + speaker work.'); };
|
||||||
|
}, 3000);
|
||||||
|
} catch(e) { addSys('Mic test failed: ' + e.message); }
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (text === '/hangup' || text === '/end') { hangupCall(); return; }
|
if (text === '/hangup' || text === '/end') { hangupCall(); return; }
|
||||||
if (text === '/accept') { acceptCall(); return; }
|
if (text === '/accept') { acceptCall(); return; }
|
||||||
if (text === '/reject') { rejectCall(); return; }
|
if (text === '/reject') { rejectCall(); return; }
|
||||||
|
|||||||
Reference in New Issue
Block a user