Repository navigation
Make MIDI automatic: on by default, output connects on the first MOO message - #202
Merged
Merged
Conversation
…message - preferences: midi.enabled defaults to true; STORAGE_VERSION 3 turns it on once for older saves, after which the user's choice sticks. - Client.Midi: before sending a MOO note/CC/program/system/raw message, connect an output if none is connected and the user did not disconnect on purpose: the saved output when it is in the device list, else the soundfont. One in-flight connect is shared by concurrent messages. The soundfont path does not initialize JZZ, whose webmidi engine calls navigator.requestMIDIAccess. Co-Authored-By: Claude <noreply@anthropic.com>
MidiStatus calls ensureInitialized() on mount (MidiStatus.tsx:219-223), which runs JZZ() and navigator.requestMIDIAccess. With MIDI on by default, the tab mounted with the sidebar. mountOnSelect defers that to the first time the user opens the MIDI tab, as the haptics tab already does. Co-Authored-By: Claude <noreply@anthropic.com>
CI's lazy-audio check (check-audio-cache.mjs:108) saw VirtualMidiService and MidiService load at startup once MIDI defaulted on. - App.tsx: drop the effect that loaded VirtualMidiService and bound it to the client's audio whenever MIDI was enabled. - Client.Midi loadMidiService(): load both services together and do that binding before resolving, so it precedes every output connect. - client.ts: reset intentional-disconnect flags through the Client.Midi package, which does nothing until MidiService has loaded. - Client.Midi sendAllNotesOff(): only silence an already loaded service; it runs on every disconnect and was loading MidiService too. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MOO MIDI now plays without the user setting anything up, and no MIDI code loads until MIDI is used.
What the code does
MIDI on by default (
src/stores/preferencesStore.ts)midi.enableddefaults totrue(line 155).STORAGE_VERSIONis 3 (line 131). The migration step at lines 213-216 setsmidi.enabled = trueonce for preferences saved at version 2 or earlier. After that, a savedfalseat version 3 staysfalse.Output connects when the MOO sends MIDI (
src/gmcp/Client/Midi.ts)loadMidiOutput()(line 140) awaits a connect if no output is connected and the user didn't disconnect on purpose (connectionStatus.outputConnected,intentionalDisconnectStatus.output). Concurrent callers share one in-flight promise (outputConnecting, line 96). The promise is cleared when it settles, so after a failed connect the next message tries again.connectAutomaticOutput()(line 150) connects the savedlastOutputDeviceIdwhen it appears ingetOutputDevices(), andSOUNDFONT_OUTPUTotherwise. It callsensureInitialized()first only when the saved output isn't the soundfont, because hardware ports only show up once JZZ is running.handleNoteAsync(line 302, used by Note and Song) andsendRawToOutput(line 358, used by ControlChange, ProgramChange, SystemMessage and RawMessage) get the service throughloadMidiOutput(). Before this change,MidiService.sendNotereturned early when no output was connected (src/MidiService.ts:390).No MIDI chunks at startup (CI's
scripts/check-audio-cache.mjsrequires this)src/App.tsxused to load and initializeVirtualMidiServiceon startup whenever MIDI was enabled. That effect is removed.loadMidiService()(Midi.ts:117) now loadsMidiServiceandVirtualMidiServicetogether. Before returning, it attaches the synth to the client's playback source and audio output, so the first output always connects into the client's audio graph.src/client.tsused to importMidiServiceon every connect to reset the intentional-disconnect flags. Now it callsresetIntentionalDisconnects()on theClient.Midihandler (client.ts:244,:446). That method (Midi.ts:132) does nothing until MIDI has loaded, and the flags start out false anyway.sendAllNotesOff()(Midi.ts:416) only silences a service that has already loaded.mountOnSelect: true(src/components/sidebar.tsx:220), the same as the Haptics tab.MidiStatuscallsensureInitialized()when it mounts (MidiStatus.tsx:219-223), and that runsJZZ().JZZ and Web MIDI
In JZZ 1.9.6,
_tryWebMIDIcallsnavigator.requestMIDIAccess(node_modules/jzz/javascript/JZZ.js:709, captured at 688-690).connectOutputDevice(SOUNDFONT_OUTPUT)(src/MidiService.ts:293-319) never touches JZZ. So hearing the MOO through the soundfont never requests Web MIDI access. That only happens when the saved output is a hardware port, or when the MIDI tab is first opened.Behaviour changes to know about
Tests
src/gmcp/Client/Midi.test.ts:initialize;connectOutputDevice;reset()andresetIntentionalDisconnects()before MIDI loads load nothing.src/stores/preferencesStore.test.ts: MIDI is on by default, a version 2 save is turned on, and a version 3falseis kept.src/App.test.tsx: rendering App with MIDI on doesn't loadVirtualMidiService.src/client.test.ts: opening the socket callsresetIntentionalDisconnects.src/components/sidebar.test.tsx:MidiStatusdoesn't mount until its tab is selected.