// Module 5: OSC Communication Setup - FIXED FOR MEMORY MANAGEMENT // Save as "5_osc_communication.scd" ( // Clear any existing OSC definitions OSCdef.freeAll; // Variables to track current pen type and color ~currentPenType = \pen; ~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, amp: 0.5, ampAttack: 0.01, ampRelease: 1, filterAttack: 0, filterRelease: 0, filterMin: 200, filterMax: 5000, pitchAttack: 0, pitchRelease: 0, pitchRatio: 2, redAmt: 0.5, greenAmt: 0.5, blueAmt: 0.5 ); // 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; var pressure = ~currentPadValues.pressure; // Log the received data ["Touch data:", x, y, pressure, ~currentPenType, ~currentColor].postln; // Handle touch based on current pen type switch(~currentPenType.asSymbol, // Pen - Controls amplitude envelope \pen, { var ampAttack = y.linexp(-0.5, 0.5, 0.001, 5); var ampRelease = x.linexp(-0.5, 0.5, 0.001, 10); ~synthParams.ampAttack = ampAttack; ~synthParams.ampRelease = ampRelease; ["Amplitude envelope:", ampAttack, ampRelease].postln; }, // Monoline - Controls filter envelope \monoline, { var filterAttack = y.linexp(-0.5, 0.5, 0.001, 5); var filterRelease = x.linexp(-0.5, 0.5, 0.001, 10); ~synthParams.filterAttack = filterAttack; ~synthParams.filterRelease = filterRelease; ["Filter envelope:", filterAttack, filterRelease].postln; }, // Marker - Controls pitch envelope \marker, { var pitchAttack = y.linexp(-0.5, 0.5, 0.001, 5); var pitchRelease = x.linexp(-0.5, 0.5, 0.001, 10); ~synthParams.pitchAttack = pitchAttack; ~synthParams.pitchRelease = pitchRelease; ["Pitch envelope:", pitchAttack, pitchRelease].postln; }, // Pencil - Effect preset 1 \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 - 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, { // Apply Preset 3 effects (TBD in documentation) ["Fountain pen preset (TBD)"].postln; }, // Water color - Effect preset 4 (placeholder) \waterColor, { // Apply Preset 4 effects (TBD in documentation) ["Water color preset (TBD)"].postln; } ); }; // ----- 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; }, '/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; }, '/aspectY'); // OSC responder for pressure coordinate OSCdef(\pressureOSC, { |msg, time, addr, port| var pressure = msg[1].asFloat; // 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 = \pen; ["Current pen type:", ~currentPenType].postln; } }, '/pen'); // OSC responder for monoline OSCdef(\monolineOSC, { |msg, time, addr, port| var penType = msg[1].asFloat; if (penType == 1.0) { ~currentPenType = \monoline; ["Current pen type:", ~currentPenType].postln; } }, '/monoline'); // OSC responder for marker OSCdef(\markerOSC, { |msg, time, addr, port| var penType = msg[1].asFloat; if (penType == 1.0) { ~currentPenType = \marker; ["Current pen type:", ~currentPenType].postln; } }, '/marker'); // OSC responder for pencil OSCdef(\pencilOSC, { |msg, time, addr, port| var penType = msg[1].asFloat; if (penType == 1.0) { ~currentPenType = \pencil; if(~initializePreset1.notNil, { ~initializePreset1.value; }); ["Current pen type:", ~currentPenType].postln; } }, '/pencil'); // OSC responder for crayon OSCdef(\crayonOSC, { |msg, time, addr, port| var penType = msg[1].asFloat; if (penType == 1.0) { ~currentPenType = \crayon; if(~initializePreset2.notNil, { ~initializePreset2.value; }); ["Current pen type:", ~currentPenType].postln; } }, '/crayon'); // OSC responder for fountainPen OSCdef(\fountainPenOSC, { |msg, time, addr, port| var penType = msg[1].asFloat; if (penType == 1.0) { ~currentPenType = \fountainPen; if(~initializePreset3.notNil, { ~initializePreset3.value; }); ["Current pen type:", ~currentPenType].postln; } }, '/fountainPen'); // OSC responder for waterColor OSCdef(\waterColorOSC, { |msg, time, addr, port| var penType = msg[1].asFloat; if (penType == 1.0) { ~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| var component = msg[1].asFloat; // Update current color ~currentColor.r = component; ~synthParams.redAmt = component; ["Color changed:", ~currentColor].postln; }, '/r'); // OSC responder for green changes OSCdef(\greenOSC, { |msg, time, addr, port| var component = msg[1].asFloat; // Update current color ~currentColor.g = component; ~synthParams.greenAmt = component; ["Color changed:", ~currentColor].postln; }, '/g'); // OSC responder for blue changes OSCdef(\blueOSC, { |msg, time, addr, port| var component = msg[1].asFloat; // Update current color ~currentColor.b = component; ~synthParams.blueAmt = component; ["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 for pad values, pen types, and colors".postln; "Ready to receive data from iDraw OSC app".postln; "To cleanup system, run: ~cleanupOSCSystem.value".postln; )