5_osc_communication.scd 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. // Module 5: OSC Communication Setup - FIXED FOR PROPER TOUCH MANAGEMENT
  2. // Save as "5_osc_communication.scd" (REPLACE THE PREVIOUS VERSION)
  3. (
  4. // Clear any existing OSC definitions
  5. OSCdef.freeAll;
  6. // Variables to track current pen type and color
  7. ~currentPenType = \pen;
  8. ~currentColor = (r: 0.0, g: 0.0, b: 1.0); // Default blue
  9. ~currentPadValues = (x: 0.0, y: 0.0, pressure: 1.0); //Default pad values
  10. // Touch state management
  11. ~touchState = (
  12. isActive: false,
  13. currentSynth: nil,
  14. lastTriggerTime: 0,
  15. minTriggerInterval: 0.1 // Minimum time between new synths (100ms)
  16. );
  17. ~synthParams = (
  18. out: 0,
  19. freq: 440,
  20. amp: 0.5,
  21. ampAttack: 0.01,
  22. ampRelease: 1,
  23. filterAttack: 0,
  24. filterRelease: 0,
  25. filterMin: 200,
  26. filterMax: 5000,
  27. pitchAttack: 0,
  28. pitchRelease: 0,
  29. pitchRatio: 2,
  30. redAmt: 0.5,
  31. greenAmt: 0.5,
  32. blueAmt: 0.5
  33. );
  34. // Track active synths to prevent memory leaks
  35. ~activeSynths = IdentityDictionary.new;
  36. ~synthCounter = 0;
  37. // Function to clean up old synths
  38. ~cleanupSynths = {
  39. ~activeSynths.keysValuesDo({ |key, synth|
  40. if(synth.isPlaying.not, {
  41. ~activeSynths.removeAt(key);
  42. });
  43. });
  44. // If too many synths are active, force cleanup of oldest ones
  45. if(~activeSynths.size > 5, { // Reduced from 10 to 5
  46. var oldestKeys = ~activeSynths.keys.asArray.sort.copyRange(0, ~activeSynths.size - 3);
  47. oldestKeys.do({ |key|
  48. if(~activeSynths[key].notNil, {
  49. ~activeSynths[key].set(\gate, 0);
  50. ~activeSynths.removeAt(key);
  51. });
  52. });
  53. });
  54. };
  55. // Function to create a new synth safely (only when needed)
  56. ~createSafeSynth = { |synthType = \rgbSynth, args|
  57. var synth, synthId;
  58. // Clean up old synths first
  59. ~cleanupSynths.value;
  60. // Create new synth
  61. synthId = ~synthCounter;
  62. ~synthCounter = ~synthCounter + 1;
  63. // Add default parameters if not provided
  64. args = args ++ [
  65. \ampAttack, ~synthParams.ampAttack,
  66. \ampRelease, ~synthParams.ampRelease,
  67. \filterAttack, ~synthParams.filterAttack,
  68. \filterRelease, ~synthParams.filterRelease,
  69. \pitchAttack, ~synthParams.pitchAttack,
  70. \pitchRelease, ~synthParams.pitchRelease,
  71. \redAmt, ~synthParams.redAmt,
  72. \greenAmt, ~synthParams.greenAmt,
  73. \blueAmt, ~synthParams.blueAmt
  74. ];
  75. synth = Synth(synthType, args);
  76. ~activeSynths[synthId] = synth;
  77. // Schedule automatic cleanup
  78. SystemClock.sched(~synthParams.ampAttack + ~synthParams.ampRelease + 1, {
  79. if(~activeSynths[synthId].notNil, {
  80. ~activeSynths[synthId].set(\gate, 0);
  81. ~activeSynths.removeAt(synthId);
  82. });
  83. nil; // Don't reschedule
  84. });
  85. synth;
  86. };
  87. // Function to handle touch begin (creates new synth)
  88. ~handleTouchBegin = {
  89. var x = ~currentPadValues.x;
  90. var y = ~currentPadValues.y;
  91. var pressure = ~currentPadValues.pressure;
  92. var freq = x.linexp(-0.5, 0.5, 100, 2000);
  93. var amp = pressure.linlin(1, 8, 0.1, 0.5); // Reduced max amplitude
  94. // Only create sounds for envelope-controlling pen types
  95. if([\pen, \monoline, \marker].includes(~currentPenType.asSymbol), {
  96. // Release previous synth if it exists
  97. if(~touchState.currentSynth.notNil, {
  98. ~touchState.currentSynth.set(\gate, 0);
  99. });
  100. // Create new synth
  101. ~touchState.currentSynth = ~createSafeSynth.value(\rgbSynth, [
  102. \out, ~sourceBus ? 0,
  103. \freq, freq,
  104. \amp, amp
  105. ]);
  106. ~touchState.isActive = true;
  107. ~touchState.lastTriggerTime = SystemClock.seconds;
  108. ["Touch BEGIN - Sound triggered:", ~currentPenType, freq, amp].postln;
  109. });
  110. };
  111. // Function to handle touch movement (updates existing synth)
  112. ~handleTouchMove = {
  113. var x = ~currentPadValues.x;
  114. var y = ~currentPadValues.y;
  115. var pressure = ~currentPadValues.pressure;
  116. var freq = x.linexp(-0.5, 0.5, 100, 2000);
  117. var amp = pressure.linlin(1, 8, 0.1, 0.5);
  118. // Only update if we have an active touch for envelope-controlling pens
  119. if(~touchState.isActive and: { ~touchState.currentSynth.notNil } and: {
  120. [\pen, \monoline, \marker].includes(~currentPenType.asSymbol)
  121. }, {
  122. // Update the existing synth parameters
  123. if(~touchState.currentSynth.isPlaying, {
  124. ~touchState.currentSynth.set(\freq, freq, \amp, amp);
  125. // Don't log every movement to avoid spam
  126. });
  127. });
  128. };
  129. // Function to handle touch end (releases synth)
  130. ~handleTouchEnd = {
  131. if(~touchState.currentSynth.notNil, {
  132. ~touchState.currentSynth.set(\gate, 0);
  133. ~touchState.currentSynth = nil;
  134. ~touchState.isActive = false;
  135. ["Touch END - Sound released"].postln;
  136. });
  137. };
  138. // Smart trigger function that decides what to do based on pressure changes
  139. ~smartTriggerSound = {
  140. var currentTime = SystemClock.seconds;
  141. var pressure = ~currentPadValues.pressure;
  142. // Detect touch begin: pressure goes from low to high
  143. if(pressure > 2 and: { ~touchState.isActive.not }, {
  144. ~handleTouchBegin.value;
  145. });
  146. // Detect touch movement: pressure stays high and we have active touch
  147. if(pressure > 1 and: { ~touchState.isActive }, {
  148. ~handleTouchMove.value;
  149. });
  150. // Detect touch end: pressure goes to very low or zero
  151. if(pressure <= 1 and: { ~touchState.isActive }, {
  152. ~handleTouchEnd.value;
  153. });
  154. };
  155. // Function to update effect parameters safely
  156. ~changeEffectParams = {
  157. var x = ~currentPadValues.x;
  158. var y = ~currentPadValues.y;
  159. var pressure = ~currentPadValues.pressure;
  160. // Update synthesis parameters but DON'T log every change to avoid spam
  161. switch(~currentPenType.asSymbol,
  162. // Pen - Controls amplitude envelope
  163. \pen, {
  164. var ampAttack = y.linexp(-0.5, 0.5, 0.001, 5);
  165. var ampRelease = x.linexp(-0.5, 0.5, 0.001, 10);
  166. ~synthParams.ampAttack = ampAttack;
  167. ~synthParams.ampRelease = ampRelease;
  168. },
  169. // Monoline - Controls filter envelope
  170. \monoline, {
  171. var filterAttack = y.linexp(-0.5, 0.5, 0.001, 5);
  172. var filterRelease = x.linexp(-0.5, 0.5, 0.001, 10);
  173. ~synthParams.filterAttack = filterAttack;
  174. ~synthParams.filterRelease = filterRelease;
  175. },
  176. // Marker - Controls pitch envelope
  177. \marker, {
  178. var pitchAttack = y.linexp(-0.5, 0.5, 0.001, 5);
  179. var pitchRelease = x.linexp(-0.5, 0.5, 0.001, 10);
  180. ~synthParams.pitchAttack = pitchAttack;
  181. ~synthParams.pitchRelease = pitchRelease;
  182. },
  183. // Pencil - Effect preset 1
  184. \pencil, {
  185. // Apply Preset 1 effects - with safety checks
  186. if(~filterSynth.notNil and: { ~filterSynth.isPlaying }, {
  187. ~filterSynth.set(
  188. \cutoff, x.linexp(-0.5, 0.5, 20, 18000),
  189. \res, y.linlin(-0.5, 0.5, 0, 1)
  190. );
  191. });
  192. if(~lfoSynth.notNil and: { ~lfoSynth.isPlaying }, {
  193. ~lfoSynth.set(
  194. \freq, x.linlin(-0.5, 0.5, 0, 15),
  195. \intensity, pressure.linlin(1, 8, 0, 1)
  196. );
  197. });
  198. if(~reverbSynth.notNil and: { ~reverbSynth.isPlaying }, {
  199. ~reverbSynth.set(
  200. \room, y.linlin(-0.5, 0.5, 0.1, 0.9)
  201. );
  202. });
  203. },
  204. // Crayon - Effect preset 2
  205. \crayon, {
  206. // Apply Preset 2 effects - with safety checks
  207. if(~lfoSynth.notNil and: { ~lfoSynth.isPlaying }, {
  208. ~lfoSynth.set(
  209. \freq, x.linlin(-0.5, 0.5, 15, 1),
  210. \intensity, x.linlin(-0.5, 0.5, 0, 1)
  211. );
  212. });
  213. if(~delaySynth.notNil and: { ~delaySynth.isPlaying }, {
  214. ~delaySynth.set(
  215. \delaytime, x.linlin(-0.5, 0.5, 0.01, 1.0)
  216. );
  217. });
  218. if(~filterSynth.notNil and: { ~filterSynth.isPlaying }, {
  219. ~filterSynth.set(
  220. \cutoff, y.linexp(-0.5, 0.5, 20, 18000),
  221. \res, pressure.linlin(1, 5, 0, 1)
  222. );
  223. });
  224. if(~reverbSynth.notNil and: { ~reverbSynth.isPlaying }, {
  225. ~reverbSynth.set(
  226. \mix, y.linlin(-0.5, 0.5, 0, 1)
  227. );
  228. });
  229. },
  230. // Fountain pen - Effect preset 3 (placeholder)
  231. \fountainPen, {
  232. // Apply Preset 3 effects (TBD in documentation)
  233. },
  234. // Water color - Effect preset 4 (placeholder)
  235. \waterColor, {
  236. // Apply Preset 4 effects (TBD in documentation)
  237. }
  238. );
  239. };
  240. // ----- OSC Pad Values -----
  241. // OSC responder for x coordinate
  242. OSCdef(\xOSC, { |msg, time, addr, port|
  243. var x = msg[1].asFloat;
  244. // Update current pad value and change effects
  245. ~currentPadValues.x = x;
  246. ~changeEffectParams.value;
  247. // Handle touch movement if active
  248. if(~touchState.isActive, {
  249. ~handleTouchMove.value;
  250. });
  251. }, '/aspectX');
  252. // OSC responder for y coordinate
  253. OSCdef(\yOSC, { |msg, time, addr, port|
  254. var y = msg[1].asFloat;
  255. // Update current pad value and change effects
  256. ~currentPadValues.y = y;
  257. ~changeEffectParams.value;
  258. // Handle touch movement if active
  259. if(~touchState.isActive, {
  260. ~handleTouchMove.value;
  261. });
  262. }, '/aspectY');
  263. // OSC responder for pressure coordinate - THIS IS THE KEY ONE
  264. OSCdef(\pressureOSC, { |msg, time, addr, port|
  265. var pressure = msg[1].asFloat;
  266. // Update current pad value and change effects
  267. ~currentPadValues.pressure = pressure;
  268. ~changeEffectParams.value;
  269. // Use smart trigger logic instead of always creating new synths
  270. ~smartTriggerSound.value;
  271. }, '/pressure');
  272. // ----- OSC Pen Types ----- (unchanged)
  273. OSCdef(\penOSC, { |msg, time, addr, port|
  274. var penType = msg[1].asFloat;
  275. if (penType == 1.0) {
  276. ~currentPenType = \pen;
  277. ["Current pen type:", ~currentPenType].postln;
  278. }
  279. }, '/pen');
  280. OSCdef(\monolineOSC, { |msg, time, addr, port|
  281. var penType = msg[1].asFloat;
  282. if (penType == 1.0) {
  283. ~currentPenType = \monoline;
  284. ["Current pen type:", ~currentPenType].postln;
  285. }
  286. }, '/monoline');
  287. OSCdef(\markerOSC, { |msg, time, addr, port|
  288. var penType = msg[1].asFloat;
  289. if (penType == 1.0) {
  290. ~currentPenType = \marker;
  291. ["Current pen type:", ~currentPenType].postln;
  292. }
  293. }, '/marker');
  294. OSCdef(\pencilOSC, { |msg, time, addr, port|
  295. var penType = msg[1].asFloat;
  296. if (penType == 1.0) {
  297. ~currentPenType = \pencil;
  298. if(~initializePreset1.notNil, { ~initializePreset1.value; });
  299. ["Current pen type:", ~currentPenType].postln;
  300. }
  301. }, '/pencil');
  302. OSCdef(\crayonOSC, { |msg, time, addr, port|
  303. var penType = msg[1].asFloat;
  304. if (penType == 1.0) {
  305. ~currentPenType = \crayon;
  306. if(~initializePreset2.notNil, { ~initializePreset2.value; });
  307. ["Current pen type:", ~currentPenType].postln;
  308. }
  309. }, '/crayon');
  310. OSCdef(\fountainPenOSC, { |msg, time, addr, port|
  311. var penType = msg[1].asFloat;
  312. if (penType == 1.0) {
  313. ~currentPenType = \fountainPen;
  314. if(~initializePreset3.notNil, { ~initializePreset3.value; });
  315. ["Current pen type:", ~currentPenType].postln;
  316. }
  317. }, '/fountainPen');
  318. OSCdef(\waterColorOSC, { |msg, time, addr, port|
  319. var penType = msg[1].asFloat;
  320. if (penType == 1.0) {
  321. ~currentPenType = \waterColor;
  322. if(~initializePreset4.notNil, { ~initializePreset4.value; });
  323. ["Current pen type:", ~currentPenType].postln;
  324. }
  325. }, '/waterColor');
  326. // ----- OSC RGB Colors ----- (unchanged)
  327. OSCdef(\redOSC, { |msg, time, addr, port|
  328. var component = msg[1].asFloat;
  329. ~currentColor.r = component;
  330. ~synthParams.redAmt = component;
  331. ["Color changed:", ~currentColor].postln;
  332. }, '/r');
  333. OSCdef(\greenOSC, { |msg, time, addr, port|
  334. var component = msg[1].asFloat;
  335. ~currentColor.g = component;
  336. ~synthParams.greenAmt = component;
  337. ["Color changed:", ~currentColor].postln;
  338. }, '/g');
  339. OSCdef(\blueOSC, { |msg, time, addr, port|
  340. var component = msg[1].asFloat;
  341. ~currentColor.b = component;
  342. ~synthParams.blueAmt = component;
  343. ["Color changed:", ~currentColor].postln;
  344. }, '/b');
  345. // Cleanup function
  346. ~cleanupOSCSystem = {
  347. // End any active touch
  348. ~handleTouchEnd.value;
  349. // Clean up all synths
  350. ~activeSynths.keysValuesDo({ |key, synth|
  351. synth.set(\gate, 0);
  352. });
  353. ~activeSynths.clear;
  354. // Reset touch state
  355. ~touchState.isActive = false;
  356. ~touchState.currentSynth = nil;
  357. "OSC system cleaned up".postln;
  358. };
  359. // Register cleanup with CmdPeriod
  360. CmdPeriod.add({
  361. ~cleanupOSCSystem.value;
  362. });
  363. // Start the OSC server on port 57120 (default SuperCollider port)
  364. thisProcess.openUDPPort(57120);
  365. "OSC server ready on port 57120 - FIXED FOR TOUCH MANAGEMENT".postln;
  366. "Now only creates synths on touch BEGIN, updates on MOVE, releases on END".postln;
  367. "Ready to receive data from iDraw OSC app".postln;
  368. "To cleanup system, run: ~cleanupOSCSystem.value".postln;
  369. )