From 0bd8e46dd7c73fd53f56d94022d8a0d4e7f8538f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:00:09 +0200 Subject: [PATCH] feat(matrix): mute groups, time-warp, wash, next --- sound_algo/data_only/matrix.scd | 39 ++++++++++++++++--- .../matrix_presets/test_global_actions.scd | 28 +++++++++++++ 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/sound_algo/data_only/matrix.scd b/sound_algo/data_only/matrix.scd index c04b4d6..5549b4e 100644 --- a/sound_algo/data_only/matrix.scd +++ b/sound_algo/data_only/matrix.scd @@ -216,7 +216,7 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd }); Pchain( Pbind(*([ \matGlow, Pfunc { |e| ~matEmitTrig.(vi, (e[\amp] ? spec[\amp]).clip(0, 1)); 0 }, - \stretch, spec[\stretch], + \stretch, (spec[\stretch] * ~matTxStretch.value), \amp, Pfunc({ (spec[\amp] ? 1.0) * volOf.value }) ] ++ instPair ++ cdPairs ++ freqPair ++ modPairs)), base @@ -245,7 +245,7 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd }); \amp, Pfunc { |e| var s = spec[\steps][e[\stepIdx]]; ((spec[\amp] ? 1.0) * (s.isNil.if({ 0 }, { s[\vel] }))) * volOf.value }, - \stretch, spec[\stretch], + \stretch, (spec[\stretch] * ~matTxStretch.value), \dur, ~matBeatsPerBar / 16 ] ++ freqPair ++ instPair ++ cdPairs ++ modPairs)) }; @@ -274,8 +274,8 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd }); cdPairs = cdPairs ++ [\pan, spec[\pan]] }); (spec[\res].notNil and: { ~matResVoices.includes(name) }).if({ cdPairs = cdPairs ++ [\res, spec[\res]] }); - (spec[\rev].notNil).if({ - cdPairs = cdPairs ++ [\rev, spec[\rev]] }); + ((spec[\rev].notNil) or: { ~matTxRev.value > 0 }).if({ + cdPairs = cdPairs ++ [\rev, ((spec[\rev] ? 0) + ~matTxRev.value).clip(0, 1)] }); (~matStepsEmpty.(spec[\steps]).not).if({ ~matColorStepPattern.(name, vi, color, spec, volOf, instPair, cdPairs) }, { @@ -375,6 +375,11 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd }); ); ~matGlobalActions = ~matGlobalActions ? [\fill, \drop, \evolve, \breakdown]; // slots 0..3 ~matHandState = ~matHandState ? IdentityDictionary[\L -> \unknown, \R -> \unknown]; +~matDrumVoices = ~matDrumVoices ? [\kick,\hats,\clap,\ride,\rim,\sweep,\fx,\snare,\crash,\shaker,\perc,\tom]; +~matMeloVoices = ~matMeloVoices ? [\sub,\acid,\arp,\lead,\stab,\pad,\reese,\bells,\melody,\chord]; +// transient param channels read by ~matVariation (1.0/0.0 when no transient or a colour-only kind) +~matTxStretch = { (~matTransient.notNil).if({ ~matTransient[\stretch] ? 1.0 }, { 1.0 }) }; +~matTxRev = { (~matTransient.notNil).if({ ~matTransient[\rev] ? 0.0 }, { 0.0 }) }; // effective colour for a voice this bar, given any active transient (grid untouched) ~matEffColor = { |name, vi, base| @@ -384,13 +389,17 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd }); (k == \drop).if({ 0 }, { (k == \fill).if({ (base == 0).if({ 0 }, { 6 }) }, { (k == \breakdown).if({ ((t[\voices] ? []).includes(name)).if({ base }, { 0 }) }, - { base }) }) }) + { (k == \mute).if({ ((t[\voices] ? []).includes(name)).if({ 0 }, { base }) }, + { base }) }) }) }) }) }; // set a transient + re-source now (effect lands at the next bar via Pdef quant) ~matSetTransient = { |kind, voices| - ~matTransient = (kind: kind, voices: (voices ? []), bars: (~matGlobalSettings[\transientBars] ? 1)); + var stretch = (kind == \doubleTime).if({ 0.5 }, { (kind == \halfTime).if({ 2.0 }, { 1.0 }) }); + var rev = (kind == \washReverb).if({ 0.4 }, { 0.0 }); + ~matTransient = (kind: kind, voices: (voices ? []), bars: (~matGlobalSettings[\transientBars] ? 1), + stretch: stretch, rev: rev); ~matVoices.size.do { |i| ~matLastColor[i] = -1 }; (~lp[\matPlaying] == true).if({ ~matApplyBar.(~lp[\matBar] ? 0) }); }; @@ -426,6 +435,12 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd }); (act == \drop).if({ ~matSetTransient.(\drop, []) }); (act == \breakdown).if({ ~matSetTransient.(\breakdown, ~matGlobalSettings[\breakdownKeep] ? [\kick, \sub]) }); (act == \evolve).if({ ~matRelanceEvolve.() }); + (act == \muteDrums).if({ ~matSetTransient.(\mute, ~matDrumVoices) }); + (act == \muteMelo).if({ ~matSetTransient.(\mute, ~matMeloVoices) }); + (act == \doubleTime).if({ ~matSetTransient.(\doubleTime, []) }); + (act == \halfTime).if({ ~matSetTransient.(\halfTime, []) }); + (act == \washReverb).if({ ~matSetTransient.(\washReverb, []) }); + (act == \next).if({ ~matNextPreset.() }); }; // derive open/closed per hand from openness (hysteresis); fire only on an @@ -453,6 +468,17 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd }); ~toscSend !? { ~toscSend.valueArray(["/matrix/globalsettings", s[\openThresh], s[\closeThresh], s[\transientBars], keep.size] ++ keep.collect({ |v| v.asString })) } }; +~matCurrentName = ~matCurrentName ? nil; +~matAllNames = { (~matNames.(~matPresetDir) ++ ~matNames.(~matDir)).as(Set).asArray.sort }; +~matNextPreset = { + var names = ~matAllNames.value; + (names.size > 0).if({ + var i = names.indexOf(~matCurrentName) ? -1; + var nxt = names[(i + 1) % names.size]; + ~matLoad.(nxt); + ~toscSend !? { ~toscSend.("/matrix/loaded", nxt.asString) }; + }); +}; // -- ~matPush : push playhead position to all TouchOSC clients -- ~matPush = { @@ -888,6 +914,7 @@ OSCdef(\mat_steps_get, { |msg, time, addr| var safe = ~matSafeName.(name); var up = ~matDir +/+ (safe ++ ".matrix"); var pp = ~matPresetDir +/+ (safe ++ ".matrix"); + ~matCurrentName = safe.asSymbol; File.exists(up).if({ ~matLoadFile.(up) }, { ~matLoadFile.(pp) }) }; diff --git a/sound_algo/data_only/matrix_presets/test_global_actions.scd b/sound_algo/data_only/matrix_presets/test_global_actions.scd index 6dc4636..7974e2a 100644 --- a/sound_algo/data_only/matrix_presets/test_global_actions.scd +++ b/sound_algo/data_only/matrix_presets/test_global_actions.scd @@ -107,6 +107,34 @@ feat.load; (~matGlobalSettings[\breakdownKeep] == [\kick, \hats]).not.if({ pass = false; "FAIL: breakdownKeep not restored".postln }); ("rm -f '" ++ (~matDir +/+ "zz_gset_test.matrix") ++ "'").systemCmd; +// -- L: mute group silences only its voices -- +~matSetTransient.(\mute, ~matDrumVoices); +(~matEffColor.(\kick, 0, 3) == 0).not.if({ pass = false; "FAIL: muteDrums keeps kick".postln }); +(~matEffColor.(\sub, 4, 3) == 3).not.if({ pass = false; "FAIL: muteDrums silenced melo sub".postln }); +~matTransient = nil; +// -- M: param transients set stretch / rev channels (via ~matSetTransient directly; +// ~matFireGlobal was stubbed by earlier groups, and the 6 dispatch lines are +// trivial transcription verified live in Task 7) -- +~matSetTransient.(\doubleTime, []); +(~matTxStretch.value == 0.5).not.if({ pass = false; "FAIL: doubleTime stretch != 0.5".postln }); +~matTransient = nil; +~matSetTransient.(\washReverb, []); +(~matTxRev.value > 0).not.if({ pass = false; "FAIL: washReverb rev not boosted".postln }); +~matTransient = nil; +~matSetTransient.(\halfTime, []); +(~matTxStretch.value == 2.0).not.if({ pass = false; "FAIL: halfTime stretch != 2.0".postln }); +~matTransient = nil; +// -- N: next loads the following preset in sorted order -- +~loaded = nil; ~matLoadReal = ~matLoad; ~matLoad = { |n| ~loaded = n.asSymbol }; +~matAllNames = { [\aaa_x, \bbb_y, \ccc_z] }; // stub the list deterministically +~matCurrentName = \aaa_x; +~matNextPreset.(); +(~loaded == \bbb_y).not.if({ pass = false; "FAIL: next did not load bbb_y".postln }); +~matCurrentName = \ccc_z; // wrap to first +~matNextPreset.(); +(~loaded == \aaa_x).not.if({ pass = false; "FAIL: next did not wrap to aaa_x".postln }); +~matLoad = ~matLoadReal; + pass.if({ "GLOBALACT PASS".postln }, { "GLOBALACT FAIL".postln }); 0.exit; )