feat: matrix loop region and seek

This commit is contained in:
L'électron rare
2026-06-28 19:18:17 +02:00
parent b8098de39e
commit 529d6c5cd9
2 changed files with 65 additions and 3 deletions
+35 -3
View File
@@ -23,6 +23,8 @@
~matBase = ~matBase ? IdentityDictionary.new;
~matBeatsPerBar = ~matBeatsPerBar ? 4;
~matLastColor = ~matLastColor ? Array.fill(~matVoices.size, -1);
~lp[\matLoopStart] = ~lp[\matLoopStart] ? 0;
~lp[\matLoopEnd] = ~lp[\matLoopEnd] ? (~matBars - 1);
// -- Persistence env init (nil-guarded) --
~matDir = ~matDir ? "~/.config/av-live/matrices".standardizePath;
@@ -81,6 +83,12 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd });
})
};
// -- ~matNextBar : compute next playhead bar clipped within the loop region --
// Wraps nb back to loop start when nb overshoots le or undershoots ls.
~matNextBar = { var nb = ~lp[\matBar] + 1; var ls = ~lp[\matLoopStart] ? 0; var le = ~lp[\matLoopEnd] ? (~matBars - 1);
((nb > le) or: { nb < ls }).if({ nb = ls });
nb.clip(0, ~matBars - 1) };
// -- ~matApplyBar : set each voice's Pdef to its cell color and (re)play or stop --
// Re-sourcing a playing Pdef updates it at the next quant boundary (smooth for v1).
~matApplyBar = { |bar|
@@ -122,6 +130,19 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd });
~toscSend !? { ~toscSend.("/matrix/trig", vi, amp.clip(0, 1)) }
};
// -- ~matLoopPush : send current loop region to surfaces --
~matLoopPush = { ~toscSend !? { ~toscSend.("/matrix/loop", ~lp[\matLoopStart] ? 0, ~lp[\matLoopEnd] ? (~matBars - 1)) } };
// -- ~matSetLoop : set loop region (auto-ordered) and push to surfaces --
~matSetLoop = { |s, e| var a = s.asInteger.clip(0, ~matBars - 1); var b = e.asInteger.clip(0, ~matBars - 1);
(a <= b).if({ ~lp[\matLoopStart] = a; ~lp[\matLoopEnd] = b }, { ~lp[\matLoopStart] = b; ~lp[\matLoopEnd] = a });
~matLoopPush.() };
// -- ~matSeek : jump playhead to bar; re-applies if playing --
~matSeek = { |bar| ~lp[\matBar] = bar.asInteger.clip(0, ~matBars - 1);
~toscSend !? { ~toscSend.("/matrix/playhead", ~lp[\matBar]) };
~lp[\matPlaying].if({ ~matApplyBar.(~lp[\matBar]) }) };
// -- ~matStop : halt the playhead Routine and silence all matrix voices --
~matStop = {
~matRoutine !? { ~matRoutine.stop };
@@ -139,13 +160,13 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd });
var clock = ~lp[\clock] ? TempoClock.default;
~matRoutine !? { ~matRoutine.stop };
~lp[\matPlaying] = true;
~lp[\matBar] = 0;
~lp[\matBar] = ~lp[\matLoopStart] ? 0;
~matRoutine = Routine({
loop {
~matApplyBar.(~lp[\matBar]);
~toscSend !? { ~toscSend.("/matrix/playhead", ~lp[\matBar]) };
~matBeatsPerBar.wait;
~lp[\matBar] = (~lp[\matBar] + 1) % ~matBars
~lp[\matBar] = ~matNextBar.value;
}
}).play(clock, quant: ~matBeatsPerBar);
~matPush.()
@@ -227,7 +248,8 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd });
~matListPush = {
try {
var names = (~matNames.(~matPresetDir) ++ ~matNames.(~matDir)).as(Set).asArray.sort;
~toscSend !? { ~toscSend.valueArray(["/matrix/list"] ++ names) }
~toscSend !? { ~toscSend.valueArray(["/matrix/list"] ++ names) };
~matLoopPush.()
} { |e|
("[matrix] listPush err: " ++ e.class.name).postln
}
@@ -308,5 +330,15 @@ OSCdef(\mat_list, { |msg, time, addr|
~matListPush.()
}, '/matrix/list');
OSCdef(\mat_loop, { |msg, time, addr|
~toscTouch !? { ~toscTouch.(addr) };
~matSetLoop.(msg[1].asInteger, msg[2].asInteger)
}, '/matrix/loop');
OSCdef(\mat_seek, { |msg, time, addr|
~toscTouch !? { ~toscTouch.(addr) };
~matSeek.(msg[1].asInteger)
}, '/matrix/seek');
"[matrix] ready".postln;
)
+30
View File
@@ -141,6 +141,36 @@ pass = pass and: { ~trigLog[0] == "/matrix/trig" };
pass = pass and: { ~trigLog[1] == 5 };
pass = pass and: { ~trigLog[2] == 1.0 }; // color 2 spec[\amp]=1.0; base has no \amp
// -- Loop region --
pass = pass and: { ~matNextBar.notNil and: { ~matSetLoop.notNil and: { ~matSeek.notNil } } };
~matSetLoop.(4, 8);
pass = pass and: { ~lp[\matLoopStart] == 4 };
pass = pass and: { ~lp[\matLoopEnd] == 8 };
// Swapped args must auto-order
~matSetLoop.(10, 6);
pass = pass and: { ~lp[\matLoopStart] == 6 };
pass = pass and: { ~lp[\matLoopEnd] == 10 };
// Wrap: loop 2..4
~matSetLoop.(2, 4);
~lp[\matBar] = 4;
pass = pass and: { ~matNextBar.value == 2 }; // at le -> wraps to loop start
~lp[\matBar] = 2;
pass = pass and: { ~matNextBar.value == 3 }; // inside region -> advances
~lp[\matBar] = 0;
pass = pass and: { ~matNextBar.value == 2 }; // below ls -> snap to loop start
// Seek
~matSeek.(7);
pass = pass and: { ~lp[\matBar] == 7 };
~matSeek.(99);
pass = pass and: { ~lp[\matBar] == 31 }; // clamped to ~matBars - 1
// Reset loop to full range
~matSetLoop.(0, 31);
pass.if(
{ "TEST PASS".postln },
{ "TEST FAIL".postln }