Explorar o código

update and added pool system to osc

Farnoosh Rad hai 6 meses
pai
achega
927c9671be
Modificáronse 2 ficheiros con 312 adicións e 261 borrados
  1. 253 147
      SC/5_osc_communication.scd
  2. 59 114
      SC/9_midi_controller.scd

+ 253 - 147
SC/5_osc_communication.scd

@@ -1,5 +1,5 @@
-// Module 5: OSC Communication Setup - FIXED FOR PROPER TOUCH MANAGEMENT
-// Save as "5_osc_communication.scd" (REPLACE THE PREVIOUS VERSION)
+// Module 5: OSC Communication Setup with Integrated Synth Pool
+// Save as "5_osc_communication.scd" (REPLACE PREVIOUS VERSION)
 
 (
 // Clear any existing OSC definitions
@@ -13,9 +13,7 @@ OSCdef.freeAll;
 // Touch state management
 ~touchState = (
     isActive: false,
-    currentSynth: nil,
-    lastTriggerTime: 0,
-    minTriggerInterval: 0.1  // Minimum time between new synths (100ms)
+    currentTouchKey: nil
 );
 
 ~synthParams = (
@@ -36,99 +34,224 @@ OSCdef.freeAll;
 	blueAmt: 0.5
 );
 
-// Track active synths to prevent memory leaks
-~activeSynths = IdentityDictionary.new;
-~synthCounter = 0;
-
-// Function to clean up old synths
-~cleanupSynths = {
-	~activeSynths.keysValuesDo({ |key, synth|
-		if(synth.isPlaying.not, {
-			~activeSynths.removeAt(key);
-		});
-	});
-	
-	// If too many synths are active, force cleanup of oldest ones
-	if(~activeSynths.size > 5, {  // Reduced from 10 to 5
-		var oldestKeys = ~activeSynths.keys.asArray.sort.copyRange(0, ~activeSynths.size - 3);
-		oldestKeys.do({ |key|
-			if(~activeSynths[key].notNil, {
-				~activeSynths[key].set(\gate, 0);
-				~activeSynths.removeAt(key);
-			});
-		});
-	});
+// ========== SYNTH POOL SYSTEM ==========
+// Synth Pool Configuration
+~poolSize = 16;  // Number of pre-allocated synths
+~synthPool = ();
+~activeSynths = IdentityDictionary.new;  // Track which synths are in use
+~freePool = Array.new(~poolSize);  // Available synths
+~synthPoolGroup = nil;
+
+// Initialize the synth pool
+~initializeSynthPool = {
+    // Clean up existing pool first
+    if(~synthPoolGroup.notNil, {
+        ~synthPoolGroup.free;
+    });
+    
+    // Clear data structures
+    ~synthPool.clear;
+    ~activeSynths.clear;
+    ~freePool.clear;
+    
+    // Create a group to hold all pool synths
+    ~synthPoolGroup = Group.new;
+    
+    "Creating synth pool with % synths...".format(~poolSize).postln;
+    
+    // Create all synths at once
+    ~poolSize.do({ |i|
+        var synth = Synth(\rgbSynth, [
+            \out, ~sourceBus ? 0,
+            \gate, 0,  // Start with gate closed (silent)
+            \amp, 0,
+            \freq, 440,
+            \ampAttack, 0.01,
+            \ampRelease, 1,
+            \filterAttack, 0,
+            \filterRelease, 0,
+            \pitchAttack, 0,
+            \pitchRelease, 0,
+            \redAmt, 0.5,
+            \greenAmt, 0.5,
+            \blueAmt, 0.5
+        ], ~synthPoolGroup);
+        
+        // Add to pool and mark as free
+        ~synthPool[i] = synth;
+        ~freePool.add(i);
+    });
+    
+    "Synth pool initialized with % synths".format(~poolSize).postln;
+    "Free synths: %".format(~freePool.size).postln;
 };
 
-// Function to create a new synth safely (only when needed)
-~createSafeSynth = { |synthType = \rgbSynth, args|
-	var synth, synthId;
-	
-	// Clean up old synths first
-	~cleanupSynths.value;
-	
-	// Create new synth
-	synthId = ~synthCounter;
-	~synthCounter = ~synthCounter + 1;
-	
-	// Add default parameters if not provided
-	args = args ++ [
-		\ampAttack, ~synthParams.ampAttack,
-		\ampRelease, ~synthParams.ampRelease,
-		\filterAttack, ~synthParams.filterAttack,
-		\filterRelease, ~synthParams.filterRelease,
-		\pitchAttack, ~synthParams.pitchAttack,
-		\pitchRelease, ~synthParams.pitchRelease,
-		\redAmt, ~synthParams.redAmt,
-		\greenAmt, ~synthParams.greenAmt,
-		\blueAmt, ~synthParams.blueAmt
-	];
-	
-	synth = Synth(synthType, args);
-	~activeSynths[synthId] = synth;
-	
-	// Schedule automatic cleanup
-	SystemClock.sched(~synthParams.ampAttack + ~synthParams.ampRelease + 1, {
-		if(~activeSynths[synthId].notNil, {
-			~activeSynths[synthId].set(\gate, 0);
-			~activeSynths.removeAt(synthId);
-		});
-		nil; // Don't reschedule
-	});
-	
-	synth;
+// Get a free synth from the pool
+~getSynthFromPool = { |key|
+    var synthIndex, synth;
+    
+    if(~freePool.size > 0, {
+        // Get the first available synth
+        synthIndex = ~freePool.removeAt(0);
+        synth = ~synthPool[synthIndex];
+        
+        // Mark it as active
+        ~activeSynths[key] = (synth: synth, index: synthIndex);
+        
+        // Update parameters with current settings
+        synth.set(
+            \ampAttack, ~synthParams.ampAttack,
+            \ampRelease, ~synthParams.ampRelease,
+            \filterAttack, ~synthParams.filterAttack,
+            \filterRelease, ~synthParams.filterRelease,
+            \pitchAttack, ~synthParams.pitchAttack,
+            \pitchRelease, ~synthParams.pitchRelease,
+            \redAmt, ~synthParams.redAmt,
+            \greenAmt, ~synthParams.greenAmt,
+            \blueAmt, ~synthParams.blueAmt
+        );
+        
+        synth;
+    }, {
+        "Warning: No free synths in pool!".postln;
+        nil;
+    });
+};
+
+// Return a synth to the pool
+~returnSynthToPool = { |key|
+    var synthData = ~activeSynths[key];
+    
+    if(synthData.notNil, {
+        var synth = synthData.synth;
+        var index = synthData.index;
+        
+        // Release the synth (set gate to 0)
+        synth.set(\gate, 0, \amp, 0);
+        
+        // Return to free pool
+        ~freePool.add(index);
+        ~activeSynths.removeAt(key);
+    });
+};
+
+// Start a synth with specific parameters
+~startPoolSynth = { |key, freq, amp, duration=nil|
+    var synth = ~getSynthFromPool.value(key);
+    
+    if(synth.notNil, {
+        // Start the synth
+        synth.set(
+            \gate, 1,
+            \freq, freq,
+            \amp, amp
+        );
+        
+        // If duration is specified, schedule automatic release
+        if(duration.notNil, {
+            SystemClock.sched(duration, {
+                ~returnSynthToPool.value(key);
+                nil;
+            });
+        });
+        
+        synth;
+    }, {
+        nil;
+    });
+};
+
+// Update an active synth
+~updatePoolSynth = { |key, freq=nil, amp=nil|
+    var synthData = ~activeSynths[key];
+    
+    if(synthData.notNil, {
+        var synth = synthData.synth;
+        
+        if(freq.notNil, { synth.set(\freq, freq); });
+        if(amp.notNil, { synth.set(\amp, amp); });
+    });
+};
+
+// Update all active synths with new envelope/color parameters
+~updateAllPoolSynths = {
+    ~activeSynths.keysValuesDo({ |key, synthData|
+        var synth = synthData.synth;
+        
+        synth.set(
+            \ampAttack, ~synthParams.ampAttack,
+            \ampRelease, ~synthParams.ampRelease,
+            \filterAttack, ~synthParams.filterAttack,
+            \filterRelease, ~synthParams.filterRelease,
+            \pitchAttack, ~synthParams.pitchAttack,
+            \pitchRelease, ~synthParams.pitchRelease,
+            \redAmt, ~synthParams.redAmt,
+            \greenAmt, ~synthParams.greenAmt,
+            \blueAmt, ~synthParams.blueAmt
+        );
+    });
 };
 
-// Function to handle touch begin (creates new synth)
+// Get pool status
+~getPoolStatus = {
+    "=== Synth Pool Status ===".postln;
+    "Total synths: %".format(~poolSize).postln;
+    "Active synths: %".format(~activeSynths.size).postln;
+    "Free synths: %".format(~freePool.size).postln;
+    "========================".postln;
+};
+
+// Clean up the entire pool
+~cleanupSynthPool = {
+    // Return all active synths to pool
+    ~activeSynths.keys.do({ |key|
+        ~returnSynthToPool.value(key);
+    });
+    
+    // Free all synths
+    if(~synthPoolGroup.notNil, {
+        ~synthPoolGroup.free;
+        ~synthPoolGroup = nil;
+    });
+    
+    // Clear data structures
+    ~synthPool.clear;
+    ~activeSynths.clear;
+    ~freePool.clear;
+    
+    "Synth pool cleaned up".postln;
+};
+
+// ========== TOUCH HANDLING ==========
+
+// Function to handle touch begin (gets synth from pool)
 ~handleTouchBegin = {
     var x = ~currentPadValues.x;
     var y = ~currentPadValues.y;
     var pressure = ~currentPadValues.pressure;
     var freq = x.linexp(-0.5, 0.5, 100, 2000);
-    var amp = pressure.linlin(1, 8, 0.1, 0.5);  // Reduced max amplitude
+    var amp = pressure.linlin(1, 8, 0.1, 0.5);
 
     // Only create sounds for envelope-controlling pen types
     if([\pen, \monoline, \marker].includes(~currentPenType.asSymbol), {
-        // Release previous synth if it exists
-        if(~touchState.currentSynth.notNil, {
-            ~touchState.currentSynth.set(\gate, 0);
+        // End previous touch if it exists
+        if(~touchState.currentTouchKey.notNil, {
+            ~returnSynthToPool.value(~touchState.currentTouchKey);
         });
         
-        // Create new synth
-        ~touchState.currentSynth = ~createSafeSynth.value(\rgbSynth, [
-            \out, ~sourceBus ? 0,
-            \freq, freq,
-            \amp, amp
-        ]);
+        // Create unique key for this touch
+        ~touchState.currentTouchKey = "touch_" ++ UniqueID.next;
+        
+        // Get synth from pool and start it
+        ~startPoolSynth.value(~touchState.currentTouchKey, freq, amp);
         
         ~touchState.isActive = true;
-        ~touchState.lastTriggerTime = SystemClock.seconds;
         
-        ["Touch BEGIN - Sound triggered:", ~currentPenType, freq, amp].postln;
+        ["Touch BEGIN - Got synth from pool:", ~currentPenType, freq, amp].postln;
     });
 };
 
-// Function to handle touch movement (updates existing synth)
+// Function to handle touch movement (updates pool synth)
 ~handleTouchMove = {
     var x = ~currentPadValues.x;
     var y = ~currentPadValues.y;
@@ -137,31 +260,27 @@ OSCdef.freeAll;
     var amp = pressure.linlin(1, 8, 0.1, 0.5);
 
     // Only update if we have an active touch for envelope-controlling pens
-    if(~touchState.isActive and: { ~touchState.currentSynth.notNil } and: { 
+    if(~touchState.isActive and: { ~touchState.currentTouchKey.notNil } and: { 
         [\pen, \monoline, \marker].includes(~currentPenType.asSymbol) 
     }, {
         // Update the existing synth parameters
-        if(~touchState.currentSynth.isPlaying, {
-            ~touchState.currentSynth.set(\freq, freq, \amp, amp);
-            // Don't log every movement to avoid spam
-        });
+        ~updatePoolSynth.value(~touchState.currentTouchKey, freq, amp);
     });
 };
 
-// Function to handle touch end (releases synth)
+// Function to handle touch end (returns synth to pool)
 ~handleTouchEnd = {
-    if(~touchState.currentSynth.notNil, {
-        ~touchState.currentSynth.set(\gate, 0);
-        ~touchState.currentSynth = nil;
+    if(~touchState.currentTouchKey.notNil, {
+        ~returnSynthToPool.value(~touchState.currentTouchKey);
+        ~touchState.currentTouchKey = nil;
         ~touchState.isActive = false;
         
-        ["Touch END - Sound released"].postln;
+        ["Touch END - Returned synth to pool"].postln;
     });
 };
 
-// Smart trigger function that decides what to do based on pressure changes
+// Smart trigger function
 ~smartTriggerSound = {
-    var currentTime = SystemClock.seconds;
     var pressure = ~currentPadValues.pressure;
     
     // Detect touch begin: pressure goes from low to high
@@ -180,13 +299,14 @@ OSCdef.freeAll;
     });
 };
 
-// Function to update effect parameters safely
+// Function to update effect parameters AND update all active synths
 ~changeEffectParams = {
     var x = ~currentPadValues.x;
     var y = ~currentPadValues.y;
     var pressure = ~currentPadValues.pressure;
+    var paramsChanged = false;
 
-    // Update synthesis parameters but DON'T log every change to avoid spam
+    // Update synthesis parameters
     switch(~currentPenType.asSymbol,
         // Pen - Controls amplitude envelope
 		\pen, {
@@ -195,6 +315,7 @@ OSCdef.freeAll;
 
 			~synthParams.ampAttack = ampAttack;
 			~synthParams.ampRelease = ampRelease;
+			paramsChanged = true;
         },
 
         // Monoline - Controls filter envelope
@@ -204,6 +325,7 @@ OSCdef.freeAll;
 
 			~synthParams.filterAttack = filterAttack;
 			~synthParams.filterRelease = filterRelease;
+			paramsChanged = true;
         },
 
         // Marker - Controls pitch envelope
@@ -213,6 +335,7 @@ OSCdef.freeAll;
 
 			~synthParams.pitchAttack = pitchAttack;
 			~synthParams.pitchRelease = pitchRelease;
+			paramsChanged = true;
         },
 
         // Pencil - Effect preset 1
@@ -227,8 +350,7 @@ OSCdef.freeAll;
 
             if(~lfoSynth.notNil and: { ~lfoSynth.isPlaying }, {
                 ~lfoSynth.set(
-                    \freq, x.linlin(-0.5, 0.5, 0, 15),
-                    \intensity, pressure.linlin(1, 8, 0, 1)
+                    \freq, x.linlin(-0.5, 0.5, 0, 15)
                 );
             });
 
@@ -244,8 +366,7 @@ OSCdef.freeAll;
             // Apply Preset 2 effects - with safety checks
             if(~lfoSynth.notNil and: { ~lfoSynth.isPlaying }, {
                 ~lfoSynth.set(
-                    \freq, x.linlin(-0.5, 0.5, 15, 1),
-                    \intensity, x.linlin(-0.5, 0.5, 0, 1)
+                    \freq, x.linlin(-0.5, 0.5, 15, 1)
                 );
             });
 
@@ -267,65 +388,40 @@ OSCdef.freeAll;
                     \mix, y.linlin(-0.5, 0.5, 0, 1)
                 );
             });
-        },
-
-        // Fountain pen - Effect preset 3 (placeholder)
-		\fountainPen, {
-            // Apply Preset 3 effects (TBD in documentation)
-        },
-
-        // Water color - Effect preset 4 (placeholder)
-		\waterColor, {
-            // Apply Preset 4 effects (TBD in documentation)
         }
     );
+    
+    // Update all active synths if envelope parameters changed
+    if(paramsChanged, {
+        ~updateAllPoolSynths.value;
+    });
 };
 
+// ========== OSC RESPONDERS ==========
+
 // ----- OSC Pad Values -----
-// OSC responder for x coordinate
 OSCdef(\xOSC, { |msg, time, addr, port|
     var x = msg[1].asFloat;
-
-    // Update current pad value and change effects
     ~currentPadValues.x = x;
 	~changeEffectParams.value;
-	
-	// Handle touch movement if active
-	if(~touchState.isActive, {
-		~handleTouchMove.value;
-	});
-
+	if(~touchState.isActive, { ~handleTouchMove.value; });
 }, '/aspectX');
 
-// OSC responder for y coordinate
 OSCdef(\yOSC, { |msg, time, addr, port|
     var y = msg[1].asFloat;
-
-    // Update current pad value and change effects
     ~currentPadValues.y = y;
 	~changeEffectParams.value;
-	
-	// Handle touch movement if active
-	if(~touchState.isActive, {
-		~handleTouchMove.value;
-	});
-
+	if(~touchState.isActive, { ~handleTouchMove.value; });
 }, '/aspectY');
 
-// OSC responder for pressure coordinate - THIS IS THE KEY ONE
 OSCdef(\pressureOSC, { |msg, time, addr, port|
     var pressure = msg[1].asFloat;
-
-    // Update current pad value and change effects
     ~currentPadValues.pressure = pressure;
 	~changeEffectParams.value;
-	
-	// Use smart trigger logic instead of always creating new synths
 	~smartTriggerSound.value;
-
 }, '/pressure');
 
-// ----- OSC Pen Types ----- (unchanged)
+// ----- OSC Pen Types -----
 OSCdef(\penOSC, { |msg, time, addr, port|
     var penType = msg[1].asFloat;
 	if (penType == 1.0) {
@@ -386,42 +482,41 @@ OSCdef(\waterColorOSC, { |msg, time, addr, port|
 	}
 }, '/waterColor');
 
-// ----- OSC RGB Colors ----- (unchanged)
+// ----- OSC RGB Colors -----
 OSCdef(\redOSC, { |msg, time, addr, port|
     var component = msg[1].asFloat;
     ~currentColor.r = component;
 	~synthParams.redAmt = component;
-    ["Color changed:", ~currentColor].postln;
+	~updateAllPoolSynths.value;
+    ["Color changed (red):", component].postln;
 }, '/r');
 
 OSCdef(\greenOSC, { |msg, time, addr, port|
     var component = msg[1].asFloat;
     ~currentColor.g = component;
 	~synthParams.greenAmt = component;
-    ["Color changed:", ~currentColor].postln;
+	~updateAllPoolSynths.value;
+    ["Color changed (green):", component].postln;
 }, '/g');
 
 OSCdef(\blueOSC, { |msg, time, addr, port|
     var component = msg[1].asFloat;
     ~currentColor.b = component;
 	~synthParams.blueAmt = component;
-    ["Color changed:", ~currentColor].postln;
+	~updateAllPoolSynths.value;
+    ["Color changed (blue):", component].postln;
 }, '/b');
 
+// ========== CLEANUP ==========
+
 // Cleanup function
 ~cleanupOSCSystem = {
 	// End any active touch
 	~handleTouchEnd.value;
 	
-	// Clean up all synths
-	~activeSynths.keysValuesDo({ |key, synth|
-		synth.set(\gate, 0);
-	});
-	~activeSynths.clear;
-	
 	// Reset touch state
 	~touchState.isActive = false;
-	~touchState.currentSynth = nil;
+	~touchState.currentTouchKey = nil;
 	
 	"OSC system cleaned up".postln;
 };
@@ -429,12 +524,23 @@ OSCdef(\blueOSC, { |msg, time, addr, port|
 // Register cleanup with CmdPeriod
 CmdPeriod.add({
 	~cleanupOSCSystem.value;
+	~cleanupSynthPool.value;
 });
 
-// Start the OSC server on port 57120 (default SuperCollider port)
+// ========== INITIALIZATION ==========
+
+// Start the OSC server on port 57120
 thisProcess.openUDPPort(57120);
-"OSC server ready on port 57120 - FIXED FOR TOUCH MANAGEMENT".postln;
-"Now only creates synths on touch BEGIN, updates on MOVE, releases on END".postln;
-"Ready to receive data from iDraw OSC app".postln;
-"To cleanup system, run: ~cleanupOSCSystem.value".postln;
+
+"========================================".postln;
+"OSC Communication with Synth Pool loaded".postln;
+"========================================".postln;
+"Available functions:".postln;
+"  ~initializeSynthPool.value  - Initialize 16 synths".postln;
+"  ~getPoolStatus.value        - Show pool status".postln;
+"  ~cleanupSynthPool.value     - Clean up pool".postln;
+"  ~cleanupOSCSystem.value     - Clean up OSC".postln;
+"".postln;
+"IMPORTANT: Run ~initializeSynthPool.value after effects chain is ready!".postln;
+"========================================".postln;
 )

+ 59 - 114
SC/9_midi_controller.scd

@@ -1,153 +1,98 @@
-// Module 9: MIDI Controller - FIXED FOR MEMORY MANAGEMENT
-// Save as "9_midi_controller.scd"
+// Module 9: MIDI Controller - Modified to use Synth Pool
+// Save as "9_midi_controller.scd" (REPLACE YOUR EXISTING VERSION)
 
 (
-// MIDI note handling with proper memory management
+// MIDI note handling using synth pool
 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;
-	});
-};
+~midiNoteKeys = IdentityDictionary.new;  // Track which pool keys are used for MIDI notes
 
 // Connect to MIDI
 MIDIClient.init;
 MIDIIn.connectAll;
 
-// Note On: create synth
+// 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(~midiSynths[num].notNil, {
-			~midiSynths[num].set(\gate, 0);
-			~midiSynths.removeAt(num);
+		if(~midiNoteKeys[num].notNil, {
+			~returnSynthToPool.value(~midiNoteKeys[num]);
+			~midiNoteKeys.removeAt(num);
 		});
 		
-		// Force cleanup if too many synths
-		~forceMIDICleanup.value;
+		// Get a synth from the pool instead of creating new one
+		var synth = ~startPoolSynth.value(noteKey, freq, amp);
 		
-		// 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
+		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;
 		});
 
     } { 
 		// Treat as noteOff if it is a noteOn with velocity 0
-		if(~midiSynths[num].notNil, {
-			~midiSynths[num].set(\gate, 0);
-			~midiSynths.removeAt(num);
+		if(~midiNoteKeys[num].notNil, {
+			~returnSynthToPool.value(~midiNoteKeys[num]);
+			~midiNoteKeys.removeAt(num);
+			// Uncomment for debugging: ["MIDI Note OFF: % (vel 0)".format(num)].postln;
 		});
 	}
 });
 
-// Note Off: release synth
+// Note Off: return synth to pool instead of setting gate
 MIDIdef.noteOff(\noteOff, { |vel, num, chan, src|
-	if(~midiSynths[num].notNil, {
-		~midiSynths[num].set(\gate, 0);
-		~midiSynths.removeAt(num);
+	if(~midiNoteKeys[num].notNil, {
+		~returnSynthToPool.value(~midiNoteKeys[num]);
+		~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
+		~midiNoteKeys.keysValuesDo({ |noteNum, noteKey|
+			~returnSynthToPool.value(noteKey);
+		});
+		~midiNoteKeys.clear;
+		"MIDI: All notes off - All synths returned to pool".postln;
 	});
 });
 
 // Cleanup all MIDI synths function
-~cleanupAllMIDISynths = {
-	~midiSynths.keysValuesDo({ |noteNum, synth|
-		synth.set(\gate, 0);
+~cleanupAllMIDI = {
+	~midiNoteKeys.keysValuesDo({ |noteNum, noteKey|
+		~returnSynthToPool.value(noteKey);
 	});
-	~midiSynths.clear;
-	~stopMIDICleanupTask.value;
-	"All MIDI synths cleaned up".postln;
+	~midiNoteKeys.clear;
+	"All MIDI synths returned to pool".postln;
 };
 
-// Start the cleanup task
-~startMIDICleanupTask.value;
+// Get MIDI status
+~getMIDIStatus = {
+	"=== MIDI Status ===".postln;
+	"Active MIDI notes: %".format(~midiNoteKeys.size).postln;
+	if(~midiNoteKeys.size > 0, {
+		"Playing notes: %".format(~midiNoteKeys.keys.asArray.sort).postln;
+	});
+	"==================".postln;
+};
 
 // Register cleanup with CmdPeriod
 CmdPeriod.add({
-	~cleanupAllMIDISynths.value;
+	~cleanupAllMIDI.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;
+"MIDI functions loaded with 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;
 )