5_osc_communication.scd 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. // Module 5: OSC Communication Setup with Integrated Synth Pool
  2. // Save as "5_osc_communication.scd" (REPLACE 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. currentTouchKey: nil
  14. );
  15. ~synthParams = (
  16. out: 0,
  17. freq: 440,
  18. amp: 0.5,
  19. ampAttack: 0.01,
  20. ampRelease: 1,
  21. filterAttack: 0,
  22. filterRelease: 0,
  23. filterMin: 200,
  24. filterMax: 5000,
  25. pitchAttack: 0,
  26. pitchRelease: 0,
  27. pitchRatio: 2,
  28. redAmt: 0.5,
  29. greenAmt: 0.5,
  30. blueAmt: 0.5
  31. );
  32. // ========== SYNTH POOL SYSTEM ==========
  33. // Synth Pool Configuration
  34. ~poolSize = 16; // Number of pre-allocated synths
  35. ~synthPool = ();
  36. ~activeSynths = IdentityDictionary.new; // Track which synths are in use
  37. ~freePool = Array.new(~poolSize); // Available synths
  38. ~synthPoolGroup = nil;
  39. // Initialize the synth pool
  40. ~initializeSynthPool = {
  41. // Clean up existing pool first
  42. if(~synthPoolGroup.notNil, {
  43. ~synthPoolGroup.free;
  44. });
  45. // Clear data structures
  46. ~synthPool.clear;
  47. ~activeSynths.clear;
  48. ~freePool.clear;
  49. // Create a group to hold all pool synths
  50. ~synthPoolGroup = Group.new;
  51. "Creating synth pool with % synths...".format(~poolSize).postln;
  52. // Create all synths at once
  53. ~poolSize.do({ |i|
  54. var synth = Synth(\rgbSynth, [
  55. \out, ~sourceBus ? 0,
  56. \gate, 0, // Start with gate closed (silent)
  57. \amp, 0,
  58. \freq, 440,
  59. \ampAttack, 0.01,
  60. \ampRelease, 1,
  61. \filterAttack, 0,
  62. \filterRelease, 0,
  63. \pitchAttack, 0,
  64. \pitchRelease, 0,
  65. \redAmt, 0.5,
  66. \greenAmt, 0.5,
  67. \blueAmt, 0.5
  68. ], ~synthPoolGroup);
  69. // Add to pool and mark as free
  70. ~synthPool[i] = synth;
  71. ~freePool.add(i);
  72. });
  73. "Synth pool initialized with % synths".format(~poolSize).postln;
  74. "Free synths: %".format(~freePool.size).postln;
  75. };
  76. // Get a free synth from the pool
  77. ~getSynthFromPool = { |key|
  78. var synthIndex, synth;
  79. if(~freePool.size > 0, {
  80. // Get the first available synth
  81. synthIndex = ~freePool.removeAt(0);
  82. synth = ~synthPool[synthIndex];
  83. // Mark it as active
  84. ~activeSynths[key] = (synth: synth, index: synthIndex);
  85. // Update parameters with current settings
  86. synth.set(
  87. \ampAttack, ~synthParams.ampAttack,
  88. \ampRelease, ~synthParams.ampRelease,
  89. \filterAttack, ~synthParams.filterAttack,
  90. \filterRelease, ~synthParams.filterRelease,
  91. \pitchAttack, ~synthParams.pitchAttack,
  92. \pitchRelease, ~synthParams.pitchRelease,
  93. \redAmt, ~synthParams.redAmt,
  94. \greenAmt, ~synthParams.greenAmt,
  95. \blueAmt, ~synthParams.blueAmt
  96. );
  97. synth;
  98. }, {
  99. "Warning: No free synths in pool!".postln;
  100. nil;
  101. });
  102. };
  103. // Return a synth to the pool
  104. ~returnSynthToPool = { |key|
  105. var synthData = ~activeSynths[key];
  106. if(synthData.notNil, {
  107. var synth = synthData.synth;
  108. var index = synthData.index;
  109. // Release the synth (set gate to 0)
  110. synth.set(\gate, 0, \amp, 0);
  111. // Return to free pool
  112. ~freePool.add(index);
  113. ~activeSynths.removeAt(key);
  114. });
  115. };
  116. // Start a synth with specific parameters
  117. ~startPoolSynth = { |key, freq, amp, duration=nil|
  118. var synth = ~getSynthFromPool.value(key);
  119. if(synth.notNil, {
  120. // Start the synth
  121. synth.set(
  122. \gate, 1,
  123. \freq, freq,
  124. \amp, amp
  125. );
  126. // If duration is specified, schedule automatic release
  127. if(duration.notNil, {
  128. SystemClock.sched(duration, {
  129. ~returnSynthToPool.value(key);
  130. nil;
  131. });
  132. });
  133. synth;
  134. }, {
  135. nil;
  136. });
  137. };
  138. // Update an active synth
  139. ~updatePoolSynth = { |key, freq=nil, amp=nil|
  140. var synthData = ~activeSynths[key];
  141. if(synthData.notNil, {
  142. var synth = synthData.synth;
  143. if(freq.notNil, { synth.set(\freq, freq); });
  144. if(amp.notNil, { synth.set(\amp, amp); });
  145. });
  146. };
  147. // Update all active synths with new envelope/color parameters
  148. ~updateAllPoolSynths = {
  149. ~activeSynths.keysValuesDo({ |key, synthData|
  150. var synth = synthData.synth;
  151. synth.set(
  152. \ampAttack, ~synthParams.ampAttack,
  153. \ampRelease, ~synthParams.ampRelease,
  154. \filterAttack, ~synthParams.filterAttack,
  155. \filterRelease, ~synthParams.filterRelease,
  156. \pitchAttack, ~synthParams.pitchAttack,
  157. \pitchRelease, ~synthParams.pitchRelease,
  158. \redAmt, ~synthParams.redAmt,
  159. \greenAmt, ~synthParams.greenAmt,
  160. \blueAmt, ~synthParams.blueAmt
  161. );
  162. });
  163. };
  164. // Get pool status
  165. ~getPoolStatus = {
  166. "=== Synth Pool Status ===".postln;
  167. "Total synths: %".format(~poolSize).postln;
  168. "Active synths: %".format(~activeSynths.size).postln;
  169. "Free synths: %".format(~freePool.size).postln;
  170. "========================".postln;
  171. };
  172. // Clean up the entire pool
  173. ~cleanupSynthPool = {
  174. // Return all active synths to pool
  175. ~activeSynths.keys.do({ |key|
  176. ~returnSynthToPool.value(key);
  177. });
  178. // Free all synths
  179. if(~synthPoolGroup.notNil, {
  180. ~synthPoolGroup.free;
  181. ~synthPoolGroup = nil;
  182. });
  183. // Clear data structures
  184. ~synthPool.clear;
  185. ~activeSynths.clear;
  186. ~freePool.clear;
  187. "Synth pool cleaned up".postln;
  188. };
  189. // ========== TOUCH HANDLING ==========
  190. // Function to handle touch begin (gets synth from pool)
  191. ~handleTouchBegin = {
  192. var x = ~currentPadValues.x;
  193. var y = ~currentPadValues.y;
  194. var pressure = ~currentPadValues.pressure;
  195. var freq = x.linexp(-0.5, 0.5, 100, 2000);
  196. var amp = pressure.linlin(1, 8, 0.1, 0.5);
  197. // Only create sounds for envelope-controlling pen types
  198. if([\pen, \monoline, \marker].includes(~currentPenType.asSymbol), {
  199. // End previous touch if it exists
  200. if(~touchState.currentTouchKey.notNil, {
  201. ~returnSynthToPool.value(~touchState.currentTouchKey);
  202. });
  203. // Create unique key for this touch
  204. ~touchState.currentTouchKey = "touch_" ++ UniqueID.next;
  205. // Get synth from pool and start it
  206. ~startPoolSynth.value(~touchState.currentTouchKey, freq, amp);
  207. ~touchState.isActive = true;
  208. ["Touch BEGIN - Got synth from pool:", ~currentPenType, freq, amp].postln;
  209. });
  210. };
  211. // Function to handle touch movement (updates pool synth)
  212. ~handleTouchMove = {
  213. var x = ~currentPadValues.x;
  214. var y = ~currentPadValues.y;
  215. var pressure = ~currentPadValues.pressure;
  216. var freq = x.linexp(-0.5, 0.5, 100, 2000);
  217. var amp = pressure.linlin(1, 8, 0.1, 0.5);
  218. // Only update if we have an active touch for envelope-controlling pens
  219. if(~touchState.isActive and: { ~touchState.currentTouchKey.notNil } and: {
  220. [\pen, \monoline, \marker].includes(~currentPenType.asSymbol)
  221. }, {
  222. // Update the existing synth parameters
  223. ~updatePoolSynth.value(~touchState.currentTouchKey, freq, amp);
  224. });
  225. };
  226. // Function to handle touch end (returns synth to pool)
  227. ~handleTouchEnd = {
  228. if(~touchState.currentTouchKey.notNil, {
  229. ~returnSynthToPool.value(~touchState.currentTouchKey);
  230. ~touchState.currentTouchKey = nil;
  231. ~touchState.isActive = false;
  232. ["Touch END - Returned synth to pool"].postln;
  233. });
  234. };
  235. // Smart trigger function
  236. ~smartTriggerSound = {
  237. var pressure = ~currentPadValues.pressure;
  238. // Detect touch begin: pressure goes from low to high
  239. if(pressure > 2 and: { ~touchState.isActive.not }, {
  240. ~handleTouchBegin.value;
  241. });
  242. // Detect touch movement: pressure stays high and we have active touch
  243. if(pressure > 1 and: { ~touchState.isActive }, {
  244. ~handleTouchMove.value;
  245. });
  246. // Detect touch end: pressure goes to very low or zero
  247. if(pressure <= 1 and: { ~touchState.isActive }, {
  248. ~handleTouchEnd.value;
  249. });
  250. };
  251. // Function to update effect parameters AND update all active synths
  252. ~changeEffectParams = {
  253. var x = ~currentPadValues.x;
  254. var y = ~currentPadValues.y;
  255. var pressure = ~currentPadValues.pressure;
  256. var paramsChanged = false;
  257. // Update synthesis parameters
  258. switch(~currentPenType.asSymbol,
  259. // Pen - Controls amplitude envelope
  260. \pen, {
  261. var ampAttack = y.linexp(-0.5, 0.5, 0.001, 5);
  262. var ampRelease = x.linexp(-0.5, 0.5, 0.001, 10);
  263. ~synthParams.ampAttack = ampAttack;
  264. ~synthParams.ampRelease = ampRelease;
  265. paramsChanged = true;
  266. },
  267. // Monoline - Controls filter envelope
  268. \monoline, {
  269. var filterAttack = y.linexp(-0.5, 0.5, 0.001, 5);
  270. var filterRelease = x.linexp(-0.5, 0.5, 0.001, 10);
  271. ~synthParams.filterAttack = filterAttack;
  272. ~synthParams.filterRelease = filterRelease;
  273. paramsChanged = true;
  274. },
  275. // Marker - Controls pitch envelope
  276. \marker, {
  277. var pitchAttack = y.linexp(-0.5, 0.5, 0.001, 5);
  278. var pitchRelease = x.linexp(-0.5, 0.5, 0.001, 10);
  279. ~synthParams.pitchAttack = pitchAttack;
  280. ~synthParams.pitchRelease = pitchRelease;
  281. paramsChanged = true;
  282. },
  283. // Pencil - Effect preset 1
  284. \pencil, {
  285. // Apply Preset 1 effects - with safety checks
  286. if(~filterSynth.notNil and: { ~filterSynth.isPlaying }, {
  287. ~filterSynth.set(
  288. \cutoff, x.linexp(-0.5, 0.5, 20, 18000),
  289. \res, y.linlin(-0.5, 0.5, 0, 1)
  290. );
  291. });
  292. if(~lfoSynth.notNil and: { ~lfoSynth.isPlaying }, {
  293. ~lfoSynth.set(
  294. \freq, x.linlin(-0.5, 0.5, 0, 15)
  295. );
  296. });
  297. if(~reverbSynth.notNil and: { ~reverbSynth.isPlaying }, {
  298. ~reverbSynth.set(
  299. \room, y.linlin(-0.5, 0.5, 0.1, 0.9)
  300. );
  301. });
  302. },
  303. // Crayon - Effect preset 2
  304. \crayon, {
  305. // Apply Preset 2 effects - with safety checks
  306. if(~lfoSynth.notNil and: { ~lfoSynth.isPlaying }, {
  307. ~lfoSynth.set(
  308. \freq, x.linlin(-0.5, 0.5, 15, 1)
  309. );
  310. });
  311. if(~delaySynth.notNil and: { ~delaySynth.isPlaying }, {
  312. ~delaySynth.set(
  313. \delaytime, x.linlin(-0.5, 0.5, 0.01, 1.0)
  314. );
  315. });
  316. if(~filterSynth.notNil and: { ~filterSynth.isPlaying }, {
  317. ~filterSynth.set(
  318. \cutoff, y.linexp(-0.5, 0.5, 20, 18000),
  319. \res, pressure.linlin(1, 5, 0, 1)
  320. );
  321. });
  322. if(~reverbSynth.notNil and: { ~reverbSynth.isPlaying }, {
  323. ~reverbSynth.set(
  324. \mix, y.linlin(-0.5, 0.5, 0, 1)
  325. );
  326. });
  327. }
  328. );
  329. // Update all active synths if envelope parameters changed
  330. if(paramsChanged, {
  331. ~updateAllPoolSynths.value;
  332. });
  333. };
  334. // ========== OSC RESPONDERS ==========
  335. // ----- OSC Pad Values -----
  336. OSCdef(\xOSC, { |msg, time, addr, port|
  337. var x = msg[1].asFloat;
  338. ~currentPadValues.x = x;
  339. ~changeEffectParams.value;
  340. if(~touchState.isActive, { ~handleTouchMove.value; });
  341. }, '/aspectX');
  342. OSCdef(\yOSC, { |msg, time, addr, port|
  343. var y = msg[1].asFloat;
  344. ~currentPadValues.y = y;
  345. ~changeEffectParams.value;
  346. if(~touchState.isActive, { ~handleTouchMove.value; });
  347. }, '/aspectY');
  348. OSCdef(\pressureOSC, { |msg, time, addr, port|
  349. var pressure = msg[1].asFloat;
  350. ~currentPadValues.pressure = pressure;
  351. ~changeEffectParams.value;
  352. ~smartTriggerSound.value;
  353. }, '/pressure');
  354. // ----- OSC Pen Types -----
  355. OSCdef(\penOSC, { |msg, time, addr, port|
  356. var penType = msg[1].asFloat;
  357. if (penType == 1.0) {
  358. ~currentPenType = \pen;
  359. ["Current pen type:", ~currentPenType].postln;
  360. }
  361. }, '/pen');
  362. OSCdef(\monolineOSC, { |msg, time, addr, port|
  363. var penType = msg[1].asFloat;
  364. if (penType == 1.0) {
  365. ~currentPenType = \monoline;
  366. ["Current pen type:", ~currentPenType].postln;
  367. }
  368. }, '/monoline');
  369. OSCdef(\markerOSC, { |msg, time, addr, port|
  370. var penType = msg[1].asFloat;
  371. if (penType == 1.0) {
  372. ~currentPenType = \marker;
  373. ["Current pen type:", ~currentPenType].postln;
  374. }
  375. }, '/marker');
  376. OSCdef(\pencilOSC, { |msg, time, addr, port|
  377. var penType = msg[1].asFloat;
  378. if (penType == 1.0) {
  379. ~currentPenType = \pencil;
  380. if(~initializePreset1.notNil, { ~initializePreset1.value; });
  381. ["Current pen type:", ~currentPenType].postln;
  382. }
  383. }, '/pencil');
  384. OSCdef(\crayonOSC, { |msg, time, addr, port|
  385. var penType = msg[1].asFloat;
  386. if (penType == 1.0) {
  387. ~currentPenType = \crayon;
  388. if(~initializePreset2.notNil, { ~initializePreset2.value; });
  389. ["Current pen type:", ~currentPenType].postln;
  390. }
  391. }, '/crayon');
  392. OSCdef(\fountainPenOSC, { |msg, time, addr, port|
  393. var penType = msg[1].asFloat;
  394. if (penType == 1.0) {
  395. ~currentPenType = \fountainPen;
  396. if(~initializePreset3.notNil, { ~initializePreset3.value; });
  397. ["Current pen type:", ~currentPenType].postln;
  398. }
  399. }, '/fountainPen');
  400. OSCdef(\waterColorOSC, { |msg, time, addr, port|
  401. var penType = msg[1].asFloat;
  402. if (penType == 1.0) {
  403. ~currentPenType = \waterColor;
  404. if(~initializePreset4.notNil, { ~initializePreset4.value; });
  405. ["Current pen type:", ~currentPenType].postln;
  406. }
  407. }, '/waterColor');
  408. // ----- OSC RGB Colors -----
  409. OSCdef(\redOSC, { |msg, time, addr, port|
  410. var component = msg[1].asFloat;
  411. ~currentColor.r = component;
  412. ~synthParams.redAmt = component;
  413. ~updateAllPoolSynths.value;
  414. ["Color changed (red):", component].postln;
  415. }, '/r');
  416. OSCdef(\greenOSC, { |msg, time, addr, port|
  417. var component = msg[1].asFloat;
  418. ~currentColor.g = component;
  419. ~synthParams.greenAmt = component;
  420. ~updateAllPoolSynths.value;
  421. ["Color changed (green):", component].postln;
  422. }, '/g');
  423. OSCdef(\blueOSC, { |msg, time, addr, port|
  424. var component = msg[1].asFloat;
  425. ~currentColor.b = component;
  426. ~synthParams.blueAmt = component;
  427. ~updateAllPoolSynths.value;
  428. ["Color changed (blue):", component].postln;
  429. }, '/b');
  430. // ========== CLEANUP ==========
  431. // Cleanup function
  432. ~cleanupOSCSystem = {
  433. // End any active touch
  434. ~handleTouchEnd.value;
  435. // Reset touch state
  436. ~touchState.isActive = false;
  437. ~touchState.currentTouchKey = nil;
  438. "OSC system cleaned up".postln;
  439. };
  440. // Register cleanup with CmdPeriod
  441. CmdPeriod.add({
  442. ~cleanupOSCSystem.value;
  443. ~cleanupSynthPool.value;
  444. });
  445. // ========== INITIALIZATION ==========
  446. // Start the OSC server on port 57120
  447. thisProcess.openUDPPort(57120);
  448. "========================================".postln;
  449. "OSC Communication with Synth Pool loaded".postln;
  450. "========================================".postln;
  451. "Available functions:".postln;
  452. " ~initializeSynthPool.value - Initialize 16 synths".postln;
  453. " ~getPoolStatus.value - Show pool status".postln;
  454. " ~cleanupSynthPool.value - Clean up pool".postln;
  455. " ~cleanupOSCSystem.value - Clean up OSC".postln;
  456. "".postln;
  457. "IMPORTANT: Run ~initializeSynthPool.value after effects chain is ready!".postln;
  458. "========================================".postln;
  459. )