// Module 5: OSC Communication Setup with Integrated Synth Pool // Save as "5_osc_communication.scd" (REPLACE PREVIOUS VERSION) ( // 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 // Touch state management ~touchState = ( isActive: false, currentTouchKey: nil ); ~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 ); // ========== 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; }; // 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 ); }); }; // 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); // Only create sounds for envelope-controlling pen types if([\pen, \monoline, \marker].includes(~currentPenType.asSymbol), { // End previous touch if it exists if(~touchState.currentTouchKey.notNil, { ~returnSynthToPool.value(~touchState.currentTouchKey); }); // 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; ["Touch BEGIN - Got synth from pool:", ~currentPenType, freq, amp].postln; }); }; // Function to handle touch movement (updates pool synth) ~handleTouchMove = { 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); // Only update if we have an active touch for envelope-controlling pens if(~touchState.isActive and: { ~touchState.currentTouchKey.notNil } and: { [\pen, \monoline, \marker].includes(~currentPenType.asSymbol) }, { // Update the existing synth parameters ~updatePoolSynth.value(~touchState.currentTouchKey, freq, amp); }); }; // Function to handle touch end (returns synth to pool) ~handleTouchEnd = { if(~touchState.currentTouchKey.notNil, { ~returnSynthToPool.value(~touchState.currentTouchKey); ~touchState.currentTouchKey = nil; ~touchState.isActive = false; ["Touch END - Returned synth to pool"].postln; }); }; // Smart trigger function ~smartTriggerSound = { var pressure = ~currentPadValues.pressure; // Detect touch begin: pressure goes from low to high if(pressure > 2 and: { ~touchState.isActive.not }, { ~handleTouchBegin.value; }); // Detect touch movement: pressure stays high and we have active touch if(pressure > 1 and: { ~touchState.isActive }, { ~handleTouchMove.value; }); // Detect touch end: pressure goes to very low or zero if(pressure <= 1 and: { ~touchState.isActive }, { ~handleTouchEnd.value; }); }; // 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 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; paramsChanged = true; }, // 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; paramsChanged = true; }, // 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; paramsChanged = true; }, // 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) ); }); if(~reverbSynth.notNil and: { ~reverbSynth.isPlaying }, { ~reverbSynth.set( \room, y.linlin(-0.5, 0.5, 0.1, 0.9) ); }); }, // 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) ); }); 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) ); }); } ); // Update all active synths if envelope parameters changed if(paramsChanged, { ~updateAllPoolSynths.value; }); }; // ========== OSC RESPONDERS ========== // ----- OSC Pad Values ----- OSCdef(\xOSC, { |msg, time, addr, port| var x = msg[1].asFloat; ~currentPadValues.x = x; ~changeEffectParams.value; if(~touchState.isActive, { ~handleTouchMove.value; }); }, '/aspectX'); OSCdef(\yOSC, { |msg, time, addr, port| var y = msg[1].asFloat; ~currentPadValues.y = y; ~changeEffectParams.value; if(~touchState.isActive, { ~handleTouchMove.value; }); }, '/aspectY'); OSCdef(\pressureOSC, { |msg, time, addr, port| var pressure = msg[1].asFloat; ~currentPadValues.pressure = pressure; ~changeEffectParams.value; ~smartTriggerSound.value; }, '/pressure'); // ----- OSC Pen Types ----- OSCdef(\penOSC, { |msg, time, addr, port| var penType = msg[1].asFloat; if (penType == 1.0) { ~currentPenType = \pen; ["Current pen type:", ~currentPenType].postln; } }, '/pen'); OSCdef(\monolineOSC, { |msg, time, addr, port| var penType = msg[1].asFloat; if (penType == 1.0) { ~currentPenType = \monoline; ["Current pen type:", ~currentPenType].postln; } }, '/monoline'); OSCdef(\markerOSC, { |msg, time, addr, port| var penType = msg[1].asFloat; if (penType == 1.0) { ~currentPenType = \marker; ["Current pen type:", ~currentPenType].postln; } }, '/marker'); 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'); 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'); 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'); 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 ----- OSCdef(\redOSC, { |msg, time, addr, port| var component = msg[1].asFloat; ~currentColor.r = component; ~synthParams.redAmt = component; ~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; ~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; ~updateAllPoolSynths.value; ["Color changed (blue):", component].postln; }, '/b'); // ========== CLEANUP ========== // Cleanup function ~cleanupOSCSystem = { // End any active touch ~handleTouchEnd.value; // Reset touch state ~touchState.isActive = false; ~touchState.currentTouchKey = nil; "OSC system cleaned up".postln; }; // Register cleanup with CmdPeriod CmdPeriod.add({ ~cleanupOSCSystem.value; ~cleanupSynthPool.value; }); // ========== INITIALIZATION ========== // Start the OSC server on port 57120 thisProcess.openUDPPort(57120); "========================================".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; )