// Module 9: MIDI Controller - FIXED FOR MEMORY MANAGEMENT // Save as "9_midi_controller.scd" ( // MIDI note handling with proper memory management var midiIn, synths; ~midiSynths = IdentityDictionary.new; // Store active synths keyed by note number ~midiCleanupTask = nil; // For scheduled cleanup // Function to cleanup old MIDI synths ~cleanupMIDISynths = { var toRemove = Array.new; ~midiSynths.keysValuesDo({ |noteNum, synth| if(synth.isPlaying.not, { toRemove.add(noteNum); }); }); toRemove.do({ |noteNum| ~midiSynths.removeAt(noteNum); }); // Log cleanup if synths were removed if(toRemove.size > 0, { ["Cleaned up % inactive MIDI synths".format(toRemove.size)].postln; }); }; // Function to force cleanup if too many synths are active ~forceMIDICleanup = { if(~midiSynths.size > 20, { var oldestNotes = ~midiSynths.keys.asArray.copyRange(0, ~midiSynths.size - 15); oldestNotes.do({ |noteNum| if(~midiSynths[noteNum].notNil, { ~midiSynths[noteNum].set(\gate, 0); ~midiSynths.removeAt(noteNum); }); }); ["Force cleaned up % MIDI synths".format(oldestNotes.size)].postln; }); }; // Start periodic cleanup task ~startMIDICleanupTask = { if(~midiCleanupTask.notNil, { ~midiCleanupTask.stop; }); ~midiCleanupTask = Task({ loop { 3.wait; // Wait 3 seconds between cleanups ~cleanupMIDISynths.value; ~forceMIDICleanup.value; } }).play; "MIDI cleanup task started".postln; }; // Stop cleanup task ~stopMIDICleanupTask = { if(~midiCleanupTask.notNil, { ~midiCleanupTask.stop; ~midiCleanupTask = nil; "MIDI cleanup task stopped".postln; }); }; // Connect to MIDI MIDIClient.init; MIDIIn.connectAll; // Note On: create synth MIDIdef.noteOn(\noteOn, { |vel, num, chan, src| var freq = num.midicps; if (vel > 0) { // Check if note is already playing and release it first if(~midiSynths[num].notNil, { ~midiSynths[num].set(\gate, 0); ~midiSynths.removeAt(num); }); // Force cleanup if too many synths ~forceMIDICleanup.value; // Create new synth with proper parameters var synth = Synth(\rgbSynth, [ \freq, freq, \amp, (vel/127) * 0.5, // Reduce amplitude to prevent overload \ampAttack, ~synthParams.ampAttack ? 0.01, \ampRelease, ~synthParams.ampRelease ? 1, \filterAttack, ~synthParams.filterAttack ? 0, \filterRelease, ~synthParams.filterRelease ? 0, \pitchAttack, ~synthParams.pitchAttack ? 0, \pitchRelease, ~synthParams.pitchRelease ? 0, \redAmt, ~synthParams.redAmt ? 0.5, \greenAmt, ~synthParams.greenAmt ? 0.5, \blueAmt, ~synthParams.blueAmt ? 0.5, \out, ~sourceBus ? 0 ]); ~midiSynths[num] = synth; // Schedule automatic cleanup for sustained notes SystemClock.sched(10, { // Auto-cleanup after 10 seconds maximum if(~midiSynths[num] == synth, { synth.set(\gate, 0); ~midiSynths.removeAt(num); }); nil; // Don't reschedule }); } { // Treat as noteOff if it is a noteOn with velocity 0 if(~midiSynths[num].notNil, { ~midiSynths[num].set(\gate, 0); ~midiSynths.removeAt(num); }); } }); // Note Off: release synth MIDIdef.noteOff(\noteOff, { |vel, num, chan, src| if(~midiSynths[num].notNil, { ~midiSynths[num].set(\gate, 0); ~midiSynths.removeAt(num); }); }); // Cleanup all MIDI synths function ~cleanupAllMIDISynths = { ~midiSynths.keysValuesDo({ |noteNum, synth| synth.set(\gate, 0); }); ~midiSynths.clear; ~stopMIDICleanupTask.value; "All MIDI synths cleaned up".postln; }; // Start the cleanup task ~startMIDICleanupTask.value; // Register cleanup with CmdPeriod CmdPeriod.add({ ~cleanupAllMIDISynths.value; }); "MIDI functions loaded with memory management.".postln; "Active MIDI synths will be automatically cleaned up.".postln; "To manually cleanup all MIDI synths: ~cleanupAllMIDISynths.value".postln; "Current active MIDI synths: % can be checked with ~midiSynths.size".postln; )