Add ! key toggle for effected mic loopback monitor

This commit is contained in:
Jage9
2026-02-20 16:43:14 -05:00
parent 93d2a24a9c
commit 874b14afc9
4 changed files with 30 additions and 1 deletions

View File

@@ -61,6 +61,7 @@
<p><b>P:</b> Ping server</p> <p><b>P:</b> Ping server</p>
<p><b>M:</b> Mute/unmute</p> <p><b>M:</b> Mute/unmute</p>
<p><b>Shift+M:</b> Toggle stereo/mono output</p> <p><b>Shift+M:</b> Toggle stereo/mono output</p>
<p><b>! (Shift+1):</b> Toggle loopback monitor</p>
<p><b>E:</b> Cycle voice effect</p> <p><b>E:</b> Cycle voice effect</p>
<p><b>Dash or Equals:</b> Lower/raise active effect value</p> <p><b>Dash or Equals:</b> Lower/raise active effect value</p>
</div> </div>

View File

@@ -1,3 +1,3 @@
// Maintainer-controlled web client version. // Maintainer-controlled web client version.
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) // Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
window.CHGRID_WEB_VERSION = "2026.02.20 R59"; window.CHGRID_WEB_VERSION = "2026.02.20 R60";

View File

@@ -38,6 +38,8 @@ export class AudioEngine {
private outboundInputGain: GainNode | null = null; private outboundInputGain: GainNode | null = null;
private outboundDestination: MediaStreamAudioDestinationNode | null = null; private outboundDestination: MediaStreamAudioDestinationNode | null = null;
private outboundEffectRuntime: EffectRuntime | null = null; private outboundEffectRuntime: EffectRuntime | null = null;
private loopbackEnabled = false;
private loopbackRuntime: EffectRuntime | null = null;
private outputMode: OutputMode = 'stereo'; private outputMode: OutputMode = 'stereo';
private effectIndex = EFFECT_SEQUENCE.findIndex((effect) => effect.id === 'off'); private effectIndex = EFFECT_SEQUENCE.findIndex((effect) => effect.id === 'off');
private readonly effectValues: Record<EffectId, number> = { private readonly effectValues: Record<EffectId, number> = {
@@ -160,6 +162,12 @@ export class AudioEngine {
return this.outputMode; return this.outputMode;
} }
toggleLoopback(): boolean {
this.loopbackEnabled = !this.loopbackEnabled;
this.rebuildOutboundEffectGraph();
return this.loopbackEnabled;
}
async attachRemoteStream( async attachRemoteStream(
peer: SpatialPeerRuntime, peer: SpatialPeerRuntime,
stream: MediaStream, stream: MediaStream,
@@ -321,6 +329,19 @@ export class AudioEngine {
effect, effect,
this.effectValues[effect], this.effectValues[effect],
); );
this.rebuildLoopbackGraph(effect, this.effectValues[effect]);
}
private rebuildLoopbackGraph(effect: EffectId, effectValue: number): void {
if (!this.audioCtx || !this.outboundInputGain) {
return;
}
disconnectEffectRuntime(this.loopbackRuntime);
this.loopbackRuntime = null;
if (!this.loopbackEnabled) {
return;
}
this.loopbackRuntime = connectEffectChain(this.audioCtx, this.outboundInputGain, this.audioCtx.destination, effect, effectValue);
} }
private clampLevel(value: number): number { private clampLevel(value: number): number {

View File

@@ -988,6 +988,13 @@ function handleNormalModeInput(code: string, shiftKey: boolean): void {
return; return;
} }
if (code === 'Digit1' && shiftKey) {
const enabled = audio.toggleLoopback();
updateStatus(enabled ? 'Loopback on.' : 'Loopback off.');
audio.sfxUiBlip();
return;
}
if (code === 'KeyE') { if (code === 'KeyE') {
const effect = audio.cycleOutboundEffect(); const effect = audio.cycleOutboundEffect();
updateStatus(effect.label); updateStatus(effect.label);