瀏覽代碼

updated the modules

Farnoosh Rad 6 月之前
父節點
當前提交
0580884a7c
共有 3 個文件被更改,包括 601 次插入120 次删除
  1. 164 65
      SC/5_osc_communication.scd
  2. 311 31
      SC/6b_effect_presets.scd
  3. 126 24
      SC/9_midi_controller.scd

+ 164 - 65
SC/5_osc_communication.scd

@@ -1,4 +1,4 @@
-// Module 5: OSC Communication Setup - MODIFIED FOR IDRAW OSC
+// Module 5: OSC Communication Setup - FIXED FOR MEMORY MANAGEMENT
 // Save as "5_osc_communication.scd"
 
 (
@@ -10,6 +10,10 @@ OSCdef.freeAll;
 ~currentColor = (r: 0.0, g: 0.0, b: 1.0); // Default blue
 ~currentPadValues = (x: 0.0, y: 0.0, pressure: 1.0); //Default pad values
 
+// Track active synths to prevent memory leaks
+~activeSynths = IdentityDictionary.new;
+~synthCounter = 0;
+
 ~synthParams = (
 	out: 0,
 	freq: 440,
@@ -28,7 +32,86 @@ OSCdef.freeAll;
 	blueAmt: 0.5
 );
 
-// Define OSC responder for iDraw touch data
+// 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 > 10, {
+		var oldestKeys = ~activeSynths.keys.asArray.sort.copyRange(0, ~activeSynths.size - 6);
+		oldestKeys.do({ |key|
+			if(~activeSynths[key].notNil, {
+				~activeSynths[key].set(\gate, 0);
+				~activeSynths.removeAt(key);
+			});
+		});
+	});
+};
+
+// Function to create a new synth safely
+~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;
+};
+
+// Define OSC responder for creating sounds (only for envelope-controlling pens)
+~triggerSound = {
+    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.8);
+
+    // Only create sounds for envelope-controlling pen types
+    if([\pen, \monoline, \marker].includes(~currentPenType.asSymbol), {
+        ~createSafeSynth.value(\rgbSynth, [
+            \out, ~sourceBus ? 0,
+            \freq, freq,
+            \amp, amp
+        ]);
+        
+        ["Sound triggered:", ~currentPenType, freq, amp].postln;
+    });
+};
+
+// Function to update effect parameters safely
 ~changeEffectParams = {
     var x = ~currentPadValues.x;
     var y = ~currentPadValues.y;
@@ -38,9 +121,9 @@ OSCdef.freeAll;
     ["Touch data:", x, y, pressure, ~currentPenType, ~currentColor].postln;
 
     // Handle touch based on current pen type
-    switch(~currentPenType,
+    switch(~currentPenType.asSymbol,
         // Pen - Controls amplitude envelope
-		"/pen", {
+		\pen, {
             var ampAttack = y.linexp(-0.5, 0.5, 0.001, 5);
             var ampRelease = x.linexp(-0.5, 0.5, 0.001, 10);
 
@@ -51,7 +134,7 @@ OSCdef.freeAll;
         },
 
         // Monoline - Controls filter envelope
-		"/monoline", {
+		\monoline, {
             var filterAttack = y.linexp(-0.5, 0.5, 0.001, 5);
             var filterRelease = x.linexp(-0.5, 0.5, 0.001, 10);
 
@@ -62,7 +145,7 @@ OSCdef.freeAll;
         },
 
         // Marker - Controls pitch envelope
-		"/marker", {
+		\marker, {
             var pitchAttack = y.linexp(-0.5, 0.5, 0.001, 5);
             var pitchRelease = x.linexp(-0.5, 0.5, 0.001, 10);
 
@@ -73,65 +156,78 @@ OSCdef.freeAll;
         },
 
         // Pencil - Effect preset 1
-		"/pencil", {
-            // Apply Preset 1 effects
-            ~filterSynth.set(
-                \cutoff, x.linexp(-0.5, 0.5, 20, 18000),
-                \res, y.linlin(-0.5, 0.5, 0, 1)
-            );
-
-            ~lfoSynth.set(
-                \freq, x.linlin(-0.5, 0.5, 0, 15),
-                \intensity, pressure.linlin(1, 8, 0, 1)
-            );
-
-            ~reverbSynth.set(
-                \roomsize, y.linlin(-0.5, 0.5, 0.1, 0.9)
-            );
+		\pencil, {
+            // Apply Preset 1 effects - with safety checks
+            if(~filterSynth.notNil and: { ~filterSynth.isPlaying }, {
+                ~filterSynth.set(
+                    \cutoff, x.linexp(-0.5, 0.5, 20, 18000),
+                    \res, y.linlin(-0.5, 0.5, 0, 1)
+                );
+            });
+
+            if(~lfoSynth.notNil and: { ~lfoSynth.isPlaying }, {
+                ~lfoSynth.set(
+                    \freq, x.linlin(-0.5, 0.5, 0, 15),
+                    \intensity, pressure.linlin(1, 8, 0, 1)
+                );
+            });
+
+            if(~reverbSynth.notNil and: { ~reverbSynth.isPlaying }, {
+                ~reverbSynth.set(
+                    \room, y.linlin(-0.5, 0.5, 0.1, 0.9)
+                );
+            });
 
             ["Pencil preset:", "Cutoff", x.linexp(-0.5, 0.5, 20, 18000), "LFO", x.linlin(-0.5, 0.5, 0, 15)].postln;
         },
 
         // Crayon - Effect preset 2
-		"/crayon", {
-            // Apply Preset 2 effects
-            ~lfoSynth.set(
-                \freq, x.linlin(-0.5, 0.5, 15, 1),
-                \intensity, x.linlin(-0.5, 0.5, 0, 1)
-            );
-
-            ~delaySynth.set(
-                \delaytime, x.linlin(-0.5, 0.5, 0.01, 1.0)
-            );
-
-            ~filterSynth.set(
-                \cutoff, y.linexp(-0.5, 0.5, 20, 18000),
-                \res, pressure.linlin(1, 5, 0, 1)
-            );
-
-            ~reverbSynth.set(
-                \mix, y.linlin(-0.5, 0.5, 0, 1)
-            );
+		\crayon, {
+            // 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)
+                );
+            });
+
+            if(~delaySynth.notNil and: { ~delaySynth.isPlaying }, {
+                ~delaySynth.set(
+                    \delaytime, x.linlin(-0.5, 0.5, 0.01, 1.0)
+                );
+            });
+
+            if(~filterSynth.notNil and: { ~filterSynth.isPlaying }, {
+                ~filterSynth.set(
+                    \cutoff, y.linexp(-0.5, 0.5, 20, 18000),
+                    \res, pressure.linlin(1, 5, 0, 1)
+                );
+            });
+
+            if(~reverbSynth.notNil and: { ~reverbSynth.isPlaying }, {
+                ~reverbSynth.set(
+                    \mix, y.linlin(-0.5, 0.5, 0, 1)
+                );
+            });
 
             ["Crayon preset:", "LFO", x.linlin(-0.5, 0.5, 15, 1), "Filter", y.linexp(-0.5, 0.5, 20, 18000)].postln;
         },
 
         // Fountain pen - Effect preset 3 (placeholder)
-		"/fountainPen", {
+		\fountainPen, {
             // Apply Preset 3 effects (TBD in documentation)
             ["Fountain pen preset (TBD)"].postln;
         },
 
         // Water color - Effect preset 4 (placeholder)
-		"/waterColor", {
+		\waterColor, {
             // Apply Preset 4 effects (TBD in documentation)
             ["Water color preset (TBD)"].postln;
         }
     );
 };
 
-
-// ----- OSC Pad Vaues -----
+// ----- OSC Pad Values -----
 // OSC responder for x coordinate
 OSCdef(\xOSC, { |msg, time, addr, port|
     var x = msg[1].asFloat;
@@ -159,19 +255,19 @@ OSCdef(\pressureOSC, { |msg, time, addr, port|
     // Update current pad value and change effects
     ~currentPadValues.pressure = pressure;
 	~changeEffectParams.value;
+	
+	// Trigger sound for envelope-controlling pens
+	~triggerSound.value;
 
 }, '/pressure');
 
-
-
-
 // ----- OSC Pen Types -----
 // OSC responder for pen
 OSCdef(\penOSC, { |msg, time, addr, port|
     var penType = msg[1].asFloat;
 
 	if (penType == 1.0) {
-		~currentPenType = msg[0].asString;
+		~currentPenType = \pen;
 		["Current pen type:", ~currentPenType].postln;
 	}
 
@@ -182,7 +278,7 @@ OSCdef(\monolineOSC, { |msg, time, addr, port|
     var penType = msg[1].asFloat;
 
 	if (penType == 1.0) {
-		~currentPenType = msg[0].asString;
+		~currentPenType = \monoline;
 		["Current pen type:", ~currentPenType].postln;
 	}
 
@@ -193,7 +289,7 @@ OSCdef(\markerOSC, { |msg, time, addr, port|
     var penType = msg[1].asFloat;
 
 	if (penType == 1.0) {
-		~currentPenType = msg[0].asString;
+		~currentPenType = \marker;
 		["Current pen type:", ~currentPenType].postln;
 	}
 
@@ -204,8 +300,8 @@ OSCdef(\pencilOSC, { |msg, time, addr, port|
     var penType = msg[1].asFloat;
 
 	if (penType == 1.0) {
-		~currentPenType = msg[0].asString;
-		~initializePreset1.value;
+		~currentPenType = \pencil;
+		if(~initializePreset1.notNil, { ~initializePreset1.value; });
 		["Current pen type:", ~currentPenType].postln;
 	}
 
@@ -216,8 +312,8 @@ OSCdef(\crayonOSC, { |msg, time, addr, port|
     var penType = msg[1].asFloat;
 
 	if (penType == 1.0) {
-		~currentPenType = msg[0].asString;
-		~initializePreset2.value;
+		~currentPenType = \crayon;
+		if(~initializePreset2.notNil, { ~initializePreset2.value; });
 		["Current pen type:", ~currentPenType].postln;
 	}
 
@@ -228,8 +324,8 @@ OSCdef(\fountainPenOSC, { |msg, time, addr, port|
     var penType = msg[1].asFloat;
 
 	if (penType == 1.0) {
-		~currentPenType = msg[0].asString;
-		~initializePreset3.value;
+		~currentPenType = \fountainPen;
+		if(~initializePreset3.notNil, { ~initializePreset3.value; });
 		["Current pen type:", ~currentPenType].postln;
 	}
 
@@ -240,16 +336,13 @@ OSCdef(\waterColorOSC, { |msg, time, addr, port|
     var penType = msg[1].asFloat;
 
 	if (penType == 1.0) {
-		~currentPenType = msg[0].asString;
-		~initializePreset4.value;
+		~currentPenType = \waterColor;
+		if(~initializePreset4.notNil, { ~initializePreset4.value; });
 		["Current pen type:", ~currentPenType].postln;
 	}
 
 }, '/waterColor');
 
-
-
-
 // ----- OSC RGB Colors -----
 // OSC responder for red changes
 OSCdef(\redOSC, { |msg, time, addr, port|
@@ -284,13 +377,19 @@ OSCdef(\blueOSC, { |msg, time, addr, port|
     ["Color changed:", ~currentColor].postln;
 }, '/b');
 
-
-
-
+// Cleanup function
+~cleanupOSCSystem = {
+	~activeSynths.keysValuesDo({ |key, synth|
+		synth.set(\gate, 0);
+	});
+	~activeSynths.clear;
+	"OSC system cleaned up".postln;
+};
 
 // Start the OSC server on port 57120 (default SuperCollider port)
 thisProcess.openUDPPort(57120);
 "OSC server ready on port 57120".postln;
-"Registered OSC commands: /touch, /pen, /color".postln;
+"Registered OSC commands for pad values, pen types, and colors".postln;
 "Ready to receive data from iDraw OSC app".postln;
+"To cleanup system, run: ~cleanupOSCSystem.value".postln;
 )

+ 311 - 31
SC/6b_effect_presets.scd

@@ -1,47 +1,327 @@
+// Module 6: Test Functions & Effect Chain Setup - FIXED FOR MEMORY MANAGEMENT
+// Save as "6_test_functions.scd"
+
 (
-// Initialize Effect Preset 1 (Pencil)
-~initializePreset1 = {
-    // Make sure effects chain exists
-    if(~effectsChain.isNil, { ~startEffectsChain.value; });
+// Function to start effects chain
+~startEffectsChain = {
+    // Stop existing effects first
+    ~stopEffectsChain.value;
+    
+    // Create audio buses for effects chain
+    ~sourceBus = Bus.audio(s, 2);
+    ~reverbBus = Bus.audio(s, 2);
+    ~delayBus = Bus.audio(s, 2);
+    ~filterBus = Bus.audio(s, 2);
+    
+    // Create control buses for modulation
+    ~lfoControlBus = Bus.control(s, 1);
+    
+    // Create effects synths in chain with error handling
+    try {
+        ~filterSynth = Synth(\lpf, [
+            \in, ~sourceBus, 
+            \out, ~delayBus, 
+            \cutoff, 1000, 
+            \res, 0.5
+        ]);
+        
+        ~delaySynth = Synth(\delay, [
+            \in, ~delayBus, 
+            \out, ~reverbBus, 
+            \delaytime, 0.4, 
+            \feedback, 0.3, 
+            \mix, 0.3
+        ], ~filterSynth, \addAfter);
+        
+        ~reverbSynth = Synth(\reverb, [
+            \in, ~reverbBus, 
+            \out, 0, 
+            \mix, 0.2, 
+            \room, 0.5, 
+            \damp, 0.5
+        ], ~delaySynth, \addAfter);
+        
+        // Create LFO separately
+        ~lfoSynth = Synth(\lfoEffect, [
+            \out, ~lfoControlBus, 
+            \freq, 1, 
+            \min, 100, 
+            \max, 5000,
+            \waveform, 0
+        ]);
+        
+        // Wait for server sync then map LFO
+        s.sync;
+        ~filterSynth.map(\cutoff, ~lfoControlBus);
+        
+        // Store the effects chain nodes in an array for easy access
+        ~effectsChain = [~filterSynth, ~delaySynth, ~reverbSynth, ~lfoSynth];
+        
+        "Effects chain started successfully".postln;
+        
+    } { |error|
+        "Error starting effects chain: %".format(error).postln;
+        ~stopEffectsChain.value;
+    };
+};
 
-    // Set LFO to specific settings for Preset 1
-    ~lfoSynth.set(
-        \waveform, 2,  // Decreasing sawtooth
-        \target, 0     // Target filter cutoff
-    );
+// Function to stop effects chain
+~stopEffectsChain = {
+    if(~effectsChain.notNil, {
+        ~effectsChain.do({ |synth|
+            if(synth.notNil and: { synth.isPlaying }, {
+                synth.free;
+            });
+        });
+        ~effectsChain = nil;
+    });
+    
+    // Free individual synth references
+    [~filterSynth, ~delaySynth, ~reverbSynth, ~lfoSynth].do({ |synth|
+        if(synth.notNil and: { synth.isPlaying }, {
+            synth.free;
+        });
+    });
+    
+    ~filterSynth = nil;
+    ~delaySynth = nil;
+    ~reverbSynth = nil;
+    ~lfoSynth = nil;
+    
+    // Free buses
+    [~sourceBus, ~reverbBus, ~delayBus, ~filterBus, ~lfoControlBus].do({ |bus|
+        if(bus.notNil, {
+            bus.free;
+        });
+    });
+    
+    ~sourceBus = nil;
+    ~reverbBus = nil;
+    ~delayBus = nil;
+    ~filterBus = nil;
+    ~lfoControlBus = nil;
+    
+    "Effects chain stopped and cleaned up".postln;
+};
 
-    "Initialized Preset 1 (Pencil) - LFO + Filter + Reverb".postln;
+// Function to reset the entire system
+~resetSystem = {
+    // Stop all effects
+    ~stopEffectsChain.value;
+    
+    // Clear OSC system if it exists
+    if(~cleanupOSCSystem.notNil, { ~cleanupOSCSystem.value; });
+    
+    // Clear MIDI system if it exists
+    if(~cleanupAllMIDISynths.notNil, { ~cleanupAllMIDISynths.value; });
+    
+    // Clear all OSC definitions
+    OSCdef.freeAll;
+    
+    // Reset touch synths tracking
+    ~touchSynths = ();
+    
+    "System reset complete".postln;
+    
+    // Restart effects chain
+    ~startEffectsChain.value;
 };
 
-// Initialize Effect Preset 2 (Crayon)
-~initializePreset2 = {
-    // Make sure effects chain exists
+// Test function for parameter changes - FIXED to not use problematic timing
+~testParameters = {
+    // Start effects chain if not already running
     if(~effectsChain.isNil, { ~startEffectsChain.value; });
-
-    // Set LFO to specific settings for Preset 2
-    ~lfoSynth.set(
-        \waveform, 3,  // Square wave
-        \target, 0     // Target filter cutoff
-    );
-
-    "Initialized Preset 2 (Crayon) - LFO + Filter + Reverb + Delay".postln;
+    
+    // Test different oscillator types with varying parameters
+    var testParams = [
+        [\rgbSynth, 440, 0.2, -0.5, 0.05, 0.5],
+        [\rgbSynth, 330, 0.15, 0, 0.01, 0.8],
+        [\rgbSynth, 220, 0.1, 0.5, 0.1, 1.2],
+        [\rgbSynth, 550, 0.18, -0.2, 0.02, 0.7],
+        [\rgbSynth, 660, 0.15, 0.3, 0.05, 1.0]
+    ];
+    
+    testParams.do { |params, i|
+        var synthType, freq, amp, pan, attack, release;
+        #synthType, freq, amp, pan, attack, release = params;
+        
+        // Create the synth with the specified parameters
+        Synth(synthType, [
+            \out, ~sourceBus ? 0,
+            \freq, freq,
+            \amp, amp,
+            \pan, pan,
+            \ampAttack, attack,
+            \ampRelease, release,
+            \redAmt, ~synthParams.redAmt ? 0.5,
+            \greenAmt, ~synthParams.greenAmt ? 0.5,
+            \blueAmt, ~synthParams.blueAmt ? 0.5
+        ]);
+        
+        // Change effect parameters for demonstration
+        if(~filterSynth.notNil and: { ~filterSynth.isPlaying }, {
+            ~filterSynth.set(\cutoff, 500 + (i * 1000));
+        });
+        if(~delaySynth.notNil and: { ~delaySynth.isPlaying }, {
+            ~delaySynth.set(\mix, 0.2 + (i * 0.1));
+        });
+        if(~reverbSynth.notNil and: { ~reverbSynth.isPlaying }, {
+            ~reverbSynth.set(\room, 0.3 + (i * 0.1));
+        });
+        
+        // Log the current sound
+        ["Testing synth:", synthType, freq, amp].postln;
+    };
+    
+    "Parameter test complete".postln;
 };
 
-// Initialize Effect Preset 3 (Fountain Pen)
-~initializePreset3 = {
-    // Make sure effects chain exists
+// Test function that creates sounds with scheduled timing (safer approach)
+~testSequence = {
+    var sounds = [
+        [440, 0.2, 0.1, 1.0],
+        [330, 0.15, 0.2, 0.8],
+        [550, 0.18, 0.3, 0.7],
+        [660, 0.12, 0.4, 0.9],
+        [220, 0.25, 0.5, 1.2]
+    ];
+    
+    // Start effects chain if needed
     if(~effectsChain.isNil, { ~startEffectsChain.value; });
-
-    "Initialized Preset 3 (Fountain Pen) - TBD".postln;
+    
+    sounds.do { |params, i|
+        var freq, amp, delay, release;
+        #freq, amp, delay, release = params;
+        
+        // Schedule each sound with SystemClock (safer than Routine)
+        SystemClock.sched(delay * i, {
+            Synth(\rgbSynth, [
+                \out, ~sourceBus ? 0,
+                \freq, freq,
+                \amp, amp,
+                \ampRelease, release,
+                \redAmt, ~synthParams.redAmt ? 0.5,
+                \greenAmt, ~synthParams.greenAmt ? 0.5,
+                \blueAmt, ~synthParams.blueAmt ? 0.5
+            ]);
+            
+            ["Scheduled sound:", freq, amp].postln;
+            nil; // Don't reschedule
+        });
+    };
+    
+    "Test sequence scheduled".postln;
 };
 
-// Initialize Effect Preset 4 (Water Color)
-~initializePreset4 = {
-    // Make sure effects chain exists
+// Function to simulate touch input safely
+~simulateTouch = { |count=5|
+    var touchCount = 0;
+    
+    "Simulating % touch events".format(count).postln;
+    
+    // Start the effects chain if needed
     if(~effectsChain.isNil, { ~startEffectsChain.value; });
+    
+    // Schedule touch events
+    count.do { |i|
+        SystemClock.sched(i * 0.5, {
+            var x = (-0.5 + 1.0.rand);
+            var y = (-0.5 + 1.0.rand);
+            var pressure = 1 + 7.rand;
+            
+            // Update pad values
+            ~currentPadValues.x = x;
+            ~currentPadValues.y = y;
+            ~currentPadValues.pressure = pressure;
+            
+            // Trigger the change effect params function
+            if(~changeEffectParams.notNil, {
+                ~changeEffectParams.value;
+            });
+            
+            // Trigger sound if using appropriate pen type
+            if(~triggerSound.notNil, {
+                ~triggerSound.value;
+            });
+            
+            touchCount = touchCount + 1;
+            ["Simulated touch #%: x=%, y=%, pressure=%".format(touchCount, x.round(0.01), y.round(0.01), pressure.round(0.01))].postln;
+            
+            nil; // Don't reschedule
+        });
+    };
+    
+    // Schedule completion message
+    SystemClock.sched(count * 0.5 + 1, {
+        "Touch simulation complete".postln;
+        nil;
+    });
+};
 
-    "Initialized Preset 4 (Water Color) - TBD".postln;
+// Memory monitoring function
+~checkMemory = {
+    var serverStatus = s.queryAllNodes;
+    var activeSynths = s.numSynths;
+    var activeGroups = s.numGroups;
+    
+    "=== Memory Status ===".postln;
+    "Active synths: %".format(activeSynths).postln;
+    "Active groups: %".format(activeGroups).postln;
+    
+    if(~midiSynths.notNil, {
+        "Active MIDI synths: %".format(~midiSynths.size).postln;
+    });
+    
+    if(~activeSynths.notNil, {
+        "Active OSC synths: %".format(~activeSynths.size).postln;
+    });
+    
+    // Warning if too many synths
+    if(activeSynths > 50, {
+        "WARNING: High number of active synths (%). Consider running cleanup.".format(activeSynths).postln;
+    });
+    
+    "===================".postln;
 };
 
-"Effect preset initializers loaded".postln;
+// Emergency cleanup function
+~emergencyCleanup = {
+    "EMERGENCY CLEANUP - Stopping all sounds".postln;
+    
+    // Free all synths on server
+    s.freeAll;
+    
+    // Clear all tracking dictionaries
+    if(~midiSynths.notNil, { ~midiSynths.clear; });
+    if(~activeSynths.notNil, { ~activeSynths.clear; });
+    if(~touchSynths.notNil, { ~touchSynths.clear; });
+    
+    // Clear buses
+    ~stopEffectsChain.value;
+    
+    // Reset counters
+    ~synthCounter = 0;
+    
+    "Emergency cleanup complete".postln;
+    
+    // Wait a moment then restart effects
+    SystemClock.sched(1, {
+        ~startEffectsChain.value;
+        nil;
+    });
+};
+
+// Register emergency cleanup with Cmd+Period
+CmdPeriod.add({
+    ~emergencyCleanup.value;
+});
+
+"Test functions loaded with memory management.".postln;
+"Available test functions:".postln;
+"  ~testParameters.value        - Test different synth parameters".postln;
+"  ~testSequence.value          - Test scheduled sound sequence".postln;
+"  ~simulateTouch.value(10)     - Simulate 10 touch events".postln;
+"  ~checkMemory.value           - Check current memory usage".postln;
+"  ~emergencyCleanup.value      - Emergency cleanup all sounds".postln;
+"  ~resetSystem.value           - Complete system reset".postln;
 )

+ 126 - 24
SC/9_midi_controller.scd

@@ -1,8 +1,70 @@
-// MIDI note handling
+// Module 9: MIDI Controller - FIXED FOR MEMORY MANAGEMENT
+// Save as "9_midi_controller.scd"
+
 (
+// MIDI note handling with proper memory management
 var midiIn, synths;
 
-~synths = IdentityDictionary.new;  // Store active synths keyed by note number
+~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;
@@ -12,40 +74,80 @@ MIDIIn.connectAll;
 MIDIdef.noteOn(\noteOn, { |vel, num, chan, src|
     var freq = num.midicps;
 
-	//postln("Note On: " + num + " Velocity: " + vel);
-
     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,
-			\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
+			\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
 		]);
-        ~synths[num] = synth;
+		
+        ~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
-		//postln("Note On: " + num + " Velocity: " + vel);
-	    ~synths[num].set(\gate, 0);
-		~synths.removeAt(num);
+    } { 
+		// 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|
-	//postln("Note Off: " + num + " Velocity: " + vel);
-    ~synths[num].set(\gate, 0);
-    ~synths.removeAt(num);
+	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;
+});
 
-// Run these functions directly
-"MIDI functions loaded.".postln;
+"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;
 )