Skip to content

Commit 243b522

Browse files
mattgodboltclaude
andcommitted
Refactor microphone input UI and implementation
- Remove separate checkbox for enabling microphone input - Always show microphone settings with disabled option - Centralize microphone handling in Config class - Simplify channel selection with proper disabling option - Add better status messaging for microphone permissions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent a0586d0 commit 243b522

File tree

3 files changed

+76
-180
lines changed

3 files changed

+76
-180
lines changed

index.html

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -855,15 +855,11 @@ <h5 class="modal-title" id="configurationModalLabel">Emulation Configuration</h5
855855
<input class="form-check-input" type="checkbox" id="hasEconet" name="hasEconet" />
856856
<label class="form-check-label" for="hasEconet">Econet with file server</label>
857857
</div>
858-
<div class="form-check">
859-
<input class="form-check-input" type="checkbox" id="useAnalogueAudio" name="useAnalogueAudio" />
860-
<label class="form-check-label" for="useAnalogueAudio">Analogue input from microphone</label>
861-
</div>
862858
</div>
863859
</div>
864860
</div>
865861

866-
<div id="analogueAudioSettings" style="margin-top: 15px; display: none">
862+
<div id="analogueAudioSettings" style="margin-top: 15px">
867863
<div class="row align-items-center">
868864
<div class="col-sm-6">
869865
<label for="micInputChannel" class="col-form-label">Microphone channel:</label>
@@ -875,13 +871,14 @@ <h5 class="modal-title" id="configurationModalLabel">Emulation Configuration</h5
875871
data-bs-toggle="dropdown"
876872
id="micInputChannel"
877873
>
878-
<span class="mic-channel-text">Channel 1</span>
874+
<span class="mic-channel-text">Disabled</span>
879875
</button>
880876
<ul
881877
class="dropdown-menu dropdown-menu-dark mic-channel-menu"
882878
role="menu"
883879
aria-labelledby="micInputChannel"
884880
>
881+
<li><a href="#" class="dropdown-item mic-channel-option" data-channel="">Disabled</a></li>
885882
<li><a href="#" class="dropdown-item mic-channel-option" data-channel="0">Channel 0</a></li>
886883
<li><a href="#" class="dropdown-item mic-channel-option" data-channel="1">Channel 1</a></li>
887884
<li><a href="#" class="dropdown-item mic-channel-option" data-channel="2">Channel 2</a></li>

src/config.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,21 @@ export class Config {
4545
this.changed.keyLayout = keyLayout;
4646
this.setKeyLayout(keyLayout);
4747
});
48+
49+
$(".mic-channel-option").on("click", (e) => {
50+
const channelString = $(e.target).data("channel");
51+
const channel = channelString === "" ? undefined : parseInt($(e.target).data("channel"), 10);
52+
this.changed.microphoneChannel = channel;
53+
this.setMicrophoneChannel(channel);
54+
});
55+
}
56+
57+
setMicrophoneChannel(channel) {
58+
if (channel !== undefined) {
59+
$(".mic-channel-text").text(`Channel ${channel}`);
60+
} else {
61+
$(".mic-channel-text").text("Disabled");
62+
}
4863
}
4964

5065
setModel(modelName) {

src/main.js

Lines changed: 58 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ const paramTypes = {
9393
logFdcCommands: ParamTypes.BOOL,
9494
logFdcStateChanges: ParamTypes.BOOL,
9595
coProcessor: ParamTypes.BOOL,
96-
useAnalogueAudio: ParamTypes.BOOL,
9796

9897
// Numeric parameters
9998
speed: ParamTypes.INT,
@@ -242,6 +241,16 @@ const config = new Config(function (changed) {
242241
emulationConfig.keyLayout = changed.keyLayout;
243242
keyboard.setKeyLayout(changed.keyLayout);
244243
}
244+
// Restore gamepad as source for the old channels
245+
for (let oldChannel = 0; oldChannel < 3; ++oldChannel)
246+
processor.adconverter.setChannelSource(oldChannel, gamepadSource);
247+
if (changed.microphoneChannel !== undefined) {
248+
const channel = changed.microphoneChannel;
249+
console.log(`Moving microphone to channel ${channel}`);
250+
processor.adconverter.setChannelSource(channel, microphoneInput);
251+
setupMicrophone().then(() => {});
252+
}
253+
updateUrl();
245254
});
246255

247256
// Perform mapping of legacy models to the new format
@@ -253,21 +262,7 @@ config.set65c02(parsedQuery.coProcessor);
253262
config.setEconet(parsedQuery.hasEconet);
254263
config.setMusic5000(parsedQuery.hasMusic5000);
255264
config.setTeletext(parsedQuery.hasTeletextAdaptor);
256-
257-
const microphoneSetupNeeded = queryString.includes("useAnalogueAudio") || queryString.includes("microphoneChannel");
258-
let microphoneChannelFromUrl = undefined;
259-
260-
if (microphoneSetupNeeded) {
261-
console.log("Microphone: URL parameters detected for microphone");
262-
// Save this to apply later
263-
if (parsedQuery.microphoneChannel !== undefined) {
264-
console.log("Microphone: URL parameter microphoneChannel =", parsedQuery.microphoneChannel);
265-
microphoneChannelFromUrl = parsedQuery.microphoneChannel;
266-
}
267-
268-
// We need to delay initialisation until after the processor and microphoneInput are created
269-
console.log("Microphone: Will initialise after processor is created");
270-
}
265+
config.setMicrophoneChannel(parsedQuery.microphoneChannel);
271266

272267
model = config.model;
273268

@@ -526,76 +521,60 @@ microphoneInput.setErrorCallback((message) => {
526521
showError("accessing microphone", message);
527522
});
528523

529-
// Now that the microphoneInput is created, we can apply the settings from URL parameters
530-
if (microphoneSetupNeeded) {
531-
console.log("Microphone: Initialising from URL parameters");
532-
533-
// First, update the UI
534-
$("#useAnalogueAudio").prop("checked", true);
535-
$("#analogueAudioSettings").show();
524+
async function setupMicrophone() {
525+
const $micPermissionStatus = $("#micPermissionStatus");
526+
$micPermissionStatus.text("Requesting microphone access...");
527+
528+
// Try to initialise the microphone
529+
const success = await microphoneInput.initialise();
530+
if (success) {
531+
console.log("Microphone: Successfully initialised from URL parameters");
532+
// Set microphone as source for its channel
533+
console.log(`Setting microphone as source for channel ${parsedQuery.microphoneChannel}`);
534+
processor.adconverter.setChannelSource(parsedQuery.microphoneChannel, microphoneInput);
535+
$micPermissionStatus.text("Microphone connected successfully");
536+
537+
// Ensure audio context is running
538+
if (microphoneInput.audioContext) {
539+
console.log(
540+
"Microphone: Ensuring audio context is running, current state:",
541+
microphoneInput.audioContext.state,
542+
);
543+
try {
544+
await microphoneInput.audioContext.resume();
545+
console.log("Microphone: Audio context resumed, new state:", microphoneInput.audioContext.state);
546+
} catch (err) {
547+
console.error("Microphone: Error resuming audio context:", err);
548+
}
549+
}
536550

537-
// Apply channel setting if provided
538-
if (microphoneChannelFromUrl !== undefined) {
539-
console.log("Microphone: Setting channel from URL parameter to", microphoneChannelFromUrl);
540-
$(".mic-channel-text").text(`Channel ${microphoneChannelFromUrl}`);
551+
// Try starting audio context from user gesture
552+
const tryAgain = () => {
553+
if (microphoneInput.audioContext && microphoneInput.audioContext.state !== "running") {
554+
console.log("Microphone: Auto-starting audio context from click");
555+
microphoneInput.audioContext.resume();
556+
}
557+
document.removeEventListener("click", tryAgain);
558+
};
559+
document.addEventListener("click", tryAgain);
560+
} else {
561+
console.error("Microphone: Failed to initialise from URL parameters:", microphoneInput.getErrorMessage());
562+
$micPermissionStatus.text(`Error: ${microphoneInput.getErrorMessage() || "Unknown error"}`);
563+
config.setMicrophoneChannel(undefined);
564+
// Update URL to remove the parameter
565+
delete parsedQuery.microphoneChannel;
566+
updateUrl();
541567
}
568+
}
569+
570+
if (parsedQuery.microphoneChannel !== undefined) {
571+
console.log("Microphone: Initialising from URL parameters");
542572

543573
// We need to use setTimeout to make sure this runs after the page has loaded
544574
// This is needed because some browsers require user interaction for audio context
545575
setTimeout(async () => {
546576
console.log("Microphone: Delayed initialisation starting");
547-
$("#micPermissionStatus").text("Requesting microphone access...");
548-
549-
// Try to initialise the microphone
550-
const success = await microphoneInput.initialise();
551-
if (success) {
552-
console.log("Microphone: Successfully initialised from URL parameters");
553-
// Set microphone as source for its channel
554-
console.log(`Setting microphone as source for channel ${microphoneChannelFromUrl}`);
555-
processor.adconverter.setChannelSource(microphoneChannelFromUrl, microphoneInput);
556-
$("#micPermissionStatus").text("Microphone connected successfully");
557-
558-
// Ensure audio context is running
559-
if (microphoneInput.audioContext) {
560-
console.log(
561-
"Microphone: Ensuring audio context is running, current state:",
562-
microphoneInput.audioContext.state,
563-
);
564-
try {
565-
await microphoneInput.audioContext.resume();
566-
console.log("Microphone: Audio context resumed, new state:", microphoneInput.audioContext.state);
567-
} catch (err) {
568-
console.error("Microphone: Error resuming audio context:", err);
569-
}
570-
}
571-
572-
// Try starting audio context from user gesture
573-
const tryAgain = () => {
574-
if (microphoneInput.audioContext && microphoneInput.audioContext.state !== "running") {
575-
console.log("Microphone: Auto-starting audio context from click");
576-
microphoneInput.audioContext.resume();
577-
}
578-
document.removeEventListener("click", tryAgain);
579-
};
580-
document.addEventListener("click", tryAgain);
581-
582-
// Make sure URL parameter is reflected in the UI
583-
parsedQuery.useAnalogueAudio = true;
584-
if (microphoneChannelFromUrl !== undefined) {
585-
parsedQuery.microphoneChannel = microphoneChannelFromUrl;
586-
}
587-
updateUrl();
588-
} else {
589-
console.error("Microphone: Failed to initialise from URL parameters:", microphoneInput.getErrorMessage());
590-
$("#micPermissionStatus").text(`Error: ${microphoneInput.getErrorMessage() || "Unknown error"}`);
591-
// Uncheck the checkbox since initialisation failed
592-
$("#useAnalogueAudio").prop("checked", false);
593-
$("#analogueAudioSettings").hide();
594-
// Update URL to remove the parameter
595-
parsedQuery.useAnalogueAudio = false;
596-
delete parsedQuery.microphoneChannel;
597-
updateUrl();
598-
}
577+
await setupMicrophone();
599578
}, 1000);
600579
}
601580

@@ -693,101 +672,6 @@ document.onkeydown = (evt) => {
693672
document.onkeypress = (evt) => keyboard.keyPress(evt);
694673
document.onkeyup = (evt) => keyboard.keyUp(evt);
695674

696-
// Set up microphone UI handlers
697-
$("#useAnalogueAudio").on("change", async function () {
698-
const useAudio = $(this).prop("checked");
699-
console.log("Analogue audio checkbox changed:", useAudio);
700-
$("#analogueAudioSettings").toggle(useAudio);
701-
702-
if (useAudio) {
703-
console.log("Initialising microphone input");
704-
$("#micPermissionStatus").text("Requesting microphone access...");
705-
const success = await microphoneInput.initialise();
706-
707-
if (success) {
708-
console.log("Adding microphone input source to ADC");
709-
710-
// Set microphone as the source for its channel
711-
console.log(`Setting microphone as source for channel ${microphoneChannelFromUrl}`);
712-
processor.adconverter.setChannelSource(microphoneChannelFromUrl, microphoneInput);
713-
$("#micPermissionStatus").text("Microphone connected successfully");
714-
// Also resume audio context to ensure it's running
715-
if (audioHandler.audioContext) {
716-
console.log("Resuming audio context, current state:", audioHandler.audioContext.state);
717-
try {
718-
await audioHandler.audioContext.resume();
719-
console.log("Audio context resumed, new state:", audioHandler.audioContext.state);
720-
} catch (err) {
721-
console.error("Error resuming audio context:", err);
722-
}
723-
} else {
724-
console.log("No audio context found in audioHandler");
725-
}
726-
727-
// Also make sure the microphone's audio context is resumed
728-
if (microphoneInput.audioContext) {
729-
console.log("Resuming microphone audio context, current state:", microphoneInput.audioContext.state);
730-
try {
731-
await microphoneInput.audioContext.resume();
732-
console.log("Microphone audio context resumed, new state:", microphoneInput.audioContext.state);
733-
} catch (err) {
734-
console.error("Error resuming microphone audio context:", err);
735-
}
736-
}
737-
} else {
738-
const errorMsg = microphoneInput.getErrorMessage() || "Unknown error";
739-
console.error("Failed to initialise microphone:", errorMsg);
740-
$("#micPermissionStatus").text(`Error: ${errorMsg}`);
741-
$(this).prop("checked", false);
742-
$("#analogueAudioSettings").hide();
743-
}
744-
} else {
745-
console.log("Removing microphone input source from ADC");
746-
// Restore gamepad as the source for the microphone channel
747-
console.log(`Restoring gamepad as source for channel ${microphoneChannelFromUrl}`);
748-
processor.adconverter.setChannelSource(microphoneChannelFromUrl, gamepadSource);
749-
750-
$("#micPermissionStatus").text("");
751-
}
752-
753-
// Update URL parameters
754-
parsedQuery.useAnalogueAudio = useAudio;
755-
if (useAudio) {
756-
parsedQuery.microphoneChannel = microphoneChannelFromUrl;
757-
console.log("Set URL parameter microphoneChannel =", microphoneChannelFromUrl);
758-
} else {
759-
delete parsedQuery.microphoneChannel;
760-
console.log("Removed URL parameter microphoneChannel");
761-
}
762-
updateUrl();
763-
console.log("URL updated with new parameters");
764-
});
765-
766-
// Handle microphone channel selection
767-
$(".mic-channel-option").on("click", function () {
768-
const channel = parseInt($(this).data("channel"), 10);
769-
console.log(`Microphone channel changed to ${channel}`);
770-
771-
// Set the channel in microphone input
772-
$(".mic-channel-text").text(`Channel ${channel}`);
773-
774-
// Update channel assignment in ADC if microphone is enabled
775-
if ($("#useAnalogueAudio").prop("checked")) {
776-
console.log(`Moving microphone to channel ${channel}`);
777-
778-
// Restore gamepad as source for the old channel
779-
for (let oldChannel = 0; oldChannel < 3; ++oldChannel)
780-
processor.adconverter.setChannelSource(oldChannel, gamepadSource);
781-
782-
// Set microphone as source for the new channel
783-
processor.adconverter.setChannelSource(channel, microphoneInput);
784-
785-
// Update URL parameters
786-
parsedQuery.microphoneChannel = channel;
787-
updateUrl();
788-
}
789-
});
790-
791675
function setDisc1Image(name) {
792676
delete parsedQuery.disc;
793677
parsedQuery.disc1 = name;

0 commit comments

Comments
 (0)