5_osc_communication.scd 19 KB

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