|
@@ -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
|
|
// Clear any existing OSC definitions
|
|
@@ -13,9 +13,7 @@ OSCdef.freeAll;
|
|
|
// Touch state management
|
|
// Touch state management
|
|
|
~touchState = (
|
|
~touchState = (
|
|
|
isActive: false,
|
|
isActive: false,
|
|
|
- currentSynth: nil,
|
|
|
|
|
- lastTriggerTime: 0,
|
|
|
|
|
- minTriggerInterval: 0.1 // Minimum time between new synths (100ms)
|
|
|
|
|
|
|
+ currentTouchKey: nil
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
~synthParams = (
|
|
~synthParams = (
|
|
@@ -36,99 +34,224 @@ OSCdef.freeAll;
|
|
|
blueAmt: 0.5
|
|
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 = {
|
|
~handleTouchBegin = {
|
|
|
var x = ~currentPadValues.x;
|
|
var x = ~currentPadValues.x;
|
|
|
var y = ~currentPadValues.y;
|
|
var y = ~currentPadValues.y;
|
|
|
var pressure = ~currentPadValues.pressure;
|
|
var pressure = ~currentPadValues.pressure;
|
|
|
var freq = x.linexp(-0.5, 0.5, 100, 2000);
|
|
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
|
|
// Only create sounds for envelope-controlling pen types
|
|
|
if([\pen, \monoline, \marker].includes(~currentPenType.asSymbol), {
|
|
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.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 = {
|
|
~handleTouchMove = {
|
|
|
var x = ~currentPadValues.x;
|
|
var x = ~currentPadValues.x;
|
|
|
var y = ~currentPadValues.y;
|
|
var y = ~currentPadValues.y;
|
|
@@ -137,31 +260,27 @@ OSCdef.freeAll;
|
|
|
var amp = pressure.linlin(1, 8, 0.1, 0.5);
|
|
var amp = pressure.linlin(1, 8, 0.1, 0.5);
|
|
|
|
|
|
|
|
// Only update if we have an active touch for envelope-controlling pens
|
|
// 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)
|
|
[\pen, \monoline, \marker].includes(~currentPenType.asSymbol)
|
|
|
}, {
|
|
}, {
|
|
|
// Update the existing synth parameters
|
|
// 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 = {
|
|
~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;
|
|
~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 = {
|
|
~smartTriggerSound = {
|
|
|
- var currentTime = SystemClock.seconds;
|
|
|
|
|
var pressure = ~currentPadValues.pressure;
|
|
var pressure = ~currentPadValues.pressure;
|
|
|
|
|
|
|
|
// Detect touch begin: pressure goes from low to high
|
|
// 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 = {
|
|
~changeEffectParams = {
|
|
|
var x = ~currentPadValues.x;
|
|
var x = ~currentPadValues.x;
|
|
|
var y = ~currentPadValues.y;
|
|
var y = ~currentPadValues.y;
|
|
|
var pressure = ~currentPadValues.pressure;
|
|
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,
|
|
switch(~currentPenType.asSymbol,
|
|
|
// Pen - Controls amplitude envelope
|
|
// Pen - Controls amplitude envelope
|
|
|
\pen, {
|
|
\pen, {
|
|
@@ -195,6 +315,7 @@ OSCdef.freeAll;
|
|
|
|
|
|
|
|
~synthParams.ampAttack = ampAttack;
|
|
~synthParams.ampAttack = ampAttack;
|
|
|
~synthParams.ampRelease = ampRelease;
|
|
~synthParams.ampRelease = ampRelease;
|
|
|
|
|
+ paramsChanged = true;
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
// Monoline - Controls filter envelope
|
|
// Monoline - Controls filter envelope
|
|
@@ -204,6 +325,7 @@ OSCdef.freeAll;
|
|
|
|
|
|
|
|
~synthParams.filterAttack = filterAttack;
|
|
~synthParams.filterAttack = filterAttack;
|
|
|
~synthParams.filterRelease = filterRelease;
|
|
~synthParams.filterRelease = filterRelease;
|
|
|
|
|
+ paramsChanged = true;
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
// Marker - Controls pitch envelope
|
|
// Marker - Controls pitch envelope
|
|
@@ -213,6 +335,7 @@ OSCdef.freeAll;
|
|
|
|
|
|
|
|
~synthParams.pitchAttack = pitchAttack;
|
|
~synthParams.pitchAttack = pitchAttack;
|
|
|
~synthParams.pitchRelease = pitchRelease;
|
|
~synthParams.pitchRelease = pitchRelease;
|
|
|
|
|
+ paramsChanged = true;
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
// Pencil - Effect preset 1
|
|
// Pencil - Effect preset 1
|
|
@@ -227,8 +350,7 @@ OSCdef.freeAll;
|
|
|
|
|
|
|
|
if(~lfoSynth.notNil and: { ~lfoSynth.isPlaying }, {
|
|
if(~lfoSynth.notNil and: { ~lfoSynth.isPlaying }, {
|
|
|
~lfoSynth.set(
|
|
~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
|
|
// Apply Preset 2 effects - with safety checks
|
|
|
if(~lfoSynth.notNil and: { ~lfoSynth.isPlaying }, {
|
|
if(~lfoSynth.notNil and: { ~lfoSynth.isPlaying }, {
|
|
|
~lfoSynth.set(
|
|
~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)
|
|
\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 Pad Values -----
|
|
|
-// OSC responder for x coordinate
|
|
|
|
|
OSCdef(\xOSC, { |msg, time, addr, port|
|
|
OSCdef(\xOSC, { |msg, time, addr, port|
|
|
|
var x = msg[1].asFloat;
|
|
var x = msg[1].asFloat;
|
|
|
-
|
|
|
|
|
- // Update current pad value and change effects
|
|
|
|
|
~currentPadValues.x = x;
|
|
~currentPadValues.x = x;
|
|
|
~changeEffectParams.value;
|
|
~changeEffectParams.value;
|
|
|
-
|
|
|
|
|
- // Handle touch movement if active
|
|
|
|
|
- if(~touchState.isActive, {
|
|
|
|
|
- ~handleTouchMove.value;
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
|
|
+ if(~touchState.isActive, { ~handleTouchMove.value; });
|
|
|
}, '/aspectX');
|
|
}, '/aspectX');
|
|
|
|
|
|
|
|
-// OSC responder for y coordinate
|
|
|
|
|
OSCdef(\yOSC, { |msg, time, addr, port|
|
|
OSCdef(\yOSC, { |msg, time, addr, port|
|
|
|
var y = msg[1].asFloat;
|
|
var y = msg[1].asFloat;
|
|
|
-
|
|
|
|
|
- // Update current pad value and change effects
|
|
|
|
|
~currentPadValues.y = y;
|
|
~currentPadValues.y = y;
|
|
|
~changeEffectParams.value;
|
|
~changeEffectParams.value;
|
|
|
-
|
|
|
|
|
- // Handle touch movement if active
|
|
|
|
|
- if(~touchState.isActive, {
|
|
|
|
|
- ~handleTouchMove.value;
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
|
|
+ if(~touchState.isActive, { ~handleTouchMove.value; });
|
|
|
}, '/aspectY');
|
|
}, '/aspectY');
|
|
|
|
|
|
|
|
-// OSC responder for pressure coordinate - THIS IS THE KEY ONE
|
|
|
|
|
OSCdef(\pressureOSC, { |msg, time, addr, port|
|
|
OSCdef(\pressureOSC, { |msg, time, addr, port|
|
|
|
var pressure = msg[1].asFloat;
|
|
var pressure = msg[1].asFloat;
|
|
|
-
|
|
|
|
|
- // Update current pad value and change effects
|
|
|
|
|
~currentPadValues.pressure = pressure;
|
|
~currentPadValues.pressure = pressure;
|
|
|
~changeEffectParams.value;
|
|
~changeEffectParams.value;
|
|
|
-
|
|
|
|
|
- // Use smart trigger logic instead of always creating new synths
|
|
|
|
|
~smartTriggerSound.value;
|
|
~smartTriggerSound.value;
|
|
|
-
|
|
|
|
|
}, '/pressure');
|
|
}, '/pressure');
|
|
|
|
|
|
|
|
-// ----- OSC Pen Types ----- (unchanged)
|
|
|
|
|
|
|
+// ----- OSC Pen Types -----
|
|
|
OSCdef(\penOSC, { |msg, time, addr, port|
|
|
OSCdef(\penOSC, { |msg, time, addr, port|
|
|
|
var penType = msg[1].asFloat;
|
|
var penType = msg[1].asFloat;
|
|
|
if (penType == 1.0) {
|
|
if (penType == 1.0) {
|
|
@@ -386,42 +482,41 @@ OSCdef(\waterColorOSC, { |msg, time, addr, port|
|
|
|
}
|
|
}
|
|
|
}, '/waterColor');
|
|
}, '/waterColor');
|
|
|
|
|
|
|
|
-// ----- OSC RGB Colors ----- (unchanged)
|
|
|
|
|
|
|
+// ----- OSC RGB Colors -----
|
|
|
OSCdef(\redOSC, { |msg, time, addr, port|
|
|
OSCdef(\redOSC, { |msg, time, addr, port|
|
|
|
var component = msg[1].asFloat;
|
|
var component = msg[1].asFloat;
|
|
|
~currentColor.r = component;
|
|
~currentColor.r = component;
|
|
|
~synthParams.redAmt = component;
|
|
~synthParams.redAmt = component;
|
|
|
- ["Color changed:", ~currentColor].postln;
|
|
|
|
|
|
|
+ ~updateAllPoolSynths.value;
|
|
|
|
|
+ ["Color changed (red):", component].postln;
|
|
|
}, '/r');
|
|
}, '/r');
|
|
|
|
|
|
|
|
OSCdef(\greenOSC, { |msg, time, addr, port|
|
|
OSCdef(\greenOSC, { |msg, time, addr, port|
|
|
|
var component = msg[1].asFloat;
|
|
var component = msg[1].asFloat;
|
|
|
~currentColor.g = component;
|
|
~currentColor.g = component;
|
|
|
~synthParams.greenAmt = component;
|
|
~synthParams.greenAmt = component;
|
|
|
- ["Color changed:", ~currentColor].postln;
|
|
|
|
|
|
|
+ ~updateAllPoolSynths.value;
|
|
|
|
|
+ ["Color changed (green):", component].postln;
|
|
|
}, '/g');
|
|
}, '/g');
|
|
|
|
|
|
|
|
OSCdef(\blueOSC, { |msg, time, addr, port|
|
|
OSCdef(\blueOSC, { |msg, time, addr, port|
|
|
|
var component = msg[1].asFloat;
|
|
var component = msg[1].asFloat;
|
|
|
~currentColor.b = component;
|
|
~currentColor.b = component;
|
|
|
~synthParams.blueAmt = component;
|
|
~synthParams.blueAmt = component;
|
|
|
- ["Color changed:", ~currentColor].postln;
|
|
|
|
|
|
|
+ ~updateAllPoolSynths.value;
|
|
|
|
|
+ ["Color changed (blue):", component].postln;
|
|
|
}, '/b');
|
|
}, '/b');
|
|
|
|
|
|
|
|
|
|
+// ========== CLEANUP ==========
|
|
|
|
|
+
|
|
|
// Cleanup function
|
|
// Cleanup function
|
|
|
~cleanupOSCSystem = {
|
|
~cleanupOSCSystem = {
|
|
|
// End any active touch
|
|
// End any active touch
|
|
|
~handleTouchEnd.value;
|
|
~handleTouchEnd.value;
|
|
|
|
|
|
|
|
- // Clean up all synths
|
|
|
|
|
- ~activeSynths.keysValuesDo({ |key, synth|
|
|
|
|
|
- synth.set(\gate, 0);
|
|
|
|
|
- });
|
|
|
|
|
- ~activeSynths.clear;
|
|
|
|
|
-
|
|
|
|
|
// Reset touch state
|
|
// Reset touch state
|
|
|
~touchState.isActive = false;
|
|
~touchState.isActive = false;
|
|
|
- ~touchState.currentSynth = nil;
|
|
|
|
|
|
|
+ ~touchState.currentTouchKey = nil;
|
|
|
|
|
|
|
|
"OSC system cleaned up".postln;
|
|
"OSC system cleaned up".postln;
|
|
|
};
|
|
};
|
|
@@ -429,12 +524,23 @@ OSCdef(\blueOSC, { |msg, time, addr, port|
|
|
|
// Register cleanup with CmdPeriod
|
|
// Register cleanup with CmdPeriod
|
|
|
CmdPeriod.add({
|
|
CmdPeriod.add({
|
|
|
~cleanupOSCSystem.value;
|
|
~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);
|
|
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;
|
|
|
)
|
|
)
|