5_osc_communication.scd 19 KB

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