// Module 9: MIDI Controller - ROBUST VERSION // Save as "9_midi_controller.scd" (REPLACE YOUR EXISTING VERSION) ( // MIDI note handling using synth pool - ROBUST VERSION var midiIn, synths; ~midiNoteKeys = IdentityDictionary.new; // Track which pool keys are used for MIDI notes // Connect to MIDI MIDIClient.init; MIDIIn.connectAll; // Note On: get synth from pool instead of creating new one MIDIdef.noteOn(\noteOn, { |vel, num, chan, src| var freq = num.midicps; var amp = (vel/127) * 0.4; // Reduced max amplitude var noteKey = "midi_" ++ num ++ "_" ++ chan; if (vel > 0) { // Check if note is already playing and release it first if(~midiNoteKeys[num].notNil, { // Safely return synth to pool try { if(~returnSynthToPool.notNil, { ~returnSynthToPool.value(~midiNoteKeys[num]); }); } { |error| // Silent error handling }; ~midiNoteKeys.removeAt(num); }); // Check if synth pool functions exist and pool is initialized if(~startPoolSynth.notNil and: { ~poolInitialized == true }, { try { var synth = ~startPoolSynth.value(noteKey, freq, amp); if(synth.notNil, { // Track this note ~midiNoteKeys[num] = noteKey; // Uncomment for debugging: ["MIDI Note ON: % - Got synth from pool".format(num)].postln; }, { ["MIDI Note ON: % - No free synths available!".format(num)].postln; }); } { |error| "MIDI Error getting synth from pool: %".format(error).postln; }; }, { if(~poolInitialized != true, { "MIDI Error: Synth pool not initialized! Run ~initializeSynthPool.value first".postln; }, { "MIDI Error: Synth pool functions not available!".postln; }); }); } { // Treat as noteOff if it is a noteOn with velocity 0 if(~midiNoteKeys[num].notNil, { try { if(~returnSynthToPool.notNil, { ~returnSynthToPool.value(~midiNoteKeys[num]); }); } { |error| // Silent error handling }; ~midiNoteKeys.removeAt(num); // Uncomment for debugging: ["MIDI Note OFF: % (vel 0)".format(num)].postln; }); } }); // Note Off: return synth to pool instead of setting gate MIDIdef.noteOff(\noteOff, { |vel, num, chan, src| if(~midiNoteKeys[num].notNil, { try { if(~returnSynthToPool.notNil, { ~returnSynthToPool.value(~midiNoteKeys[num]); }); } { |error| // Silent error handling }; ~midiNoteKeys.removeAt(num); // Uncomment for debugging: ["MIDI Note OFF: %".format(num)].postln; }); }); // All Notes Off (MIDI Panic) - return all MIDI synths to pool MIDIdef.cc(\allNotesOff, { |val, num, chan, src| if(num == 123, { // All Notes Off CC try { if(~returnSynthToPool.notNil, { ~midiNoteKeys.keysValuesDo({ |noteNum, noteKey| ~returnSynthToPool.value(noteKey); }); }); } { |error| "Error during All Notes Off: %".format(error).postln; }; ~midiNoteKeys.clear; "MIDI: All notes off - All synths returned to pool".postln; }); }); // Cleanup all MIDI synths function ~cleanupAllMIDI = { try { if(~returnSynthToPool.notNil, { ~midiNoteKeys.keysValuesDo({ |noteNum, noteKey| ~returnSynthToPool.value(noteKey); }); }); } { |error| "Error during MIDI cleanup: %".format(error).postln; }; ~midiNoteKeys.clear; "All MIDI synths returned to pool".postln; }; // Get MIDI status ~getMIDIStatus = { "=== MIDI Status ===".postln; if(~midiNoteKeys.notNil, { "Active MIDI notes: %".format(~midiNoteKeys.size).postln; if(~midiNoteKeys.size > 0, { "Playing notes: %".format(~midiNoteKeys.keys.asArray.sort).postln; }); }, { "MIDI note tracking not initialized".postln; }); if(~poolInitialized.notNil, { "Synth pool initialized: %".format(~poolInitialized).postln; }, { "Synth pool status: Unknown".postln; }); "==================".postln; }; // Register cleanup with CmdPeriod CmdPeriod.add({ ~cleanupAllMIDI.value; }); "MIDI functions loaded with ROBUST synth pool integration.".postln; "Notes will use pre-allocated synths from the pool.".postln; "No more memory leaks or stuck notes!".postln; "Available functions:".postln; " ~cleanupAllMIDI.value - Return all MIDI synths to pool".postln; " ~getMIDIStatus.value - Show active MIDI notes".postln; )