feat(matrix): mute groups, time-warp, wash, next

This commit is contained in:
L'électron rare
2026-06-30 14:00:09 +02:00
parent d483a29d3d
commit 0bd8e46dd7
2 changed files with 61 additions and 6 deletions
+33 -6
View File
@@ -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) })
};
@@ -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;
)