diff --git a/sound_algo/data_only/matrix.scd b/sound_algo/data_only/matrix.scd index 6c85b8f..95c7477 100644 --- a/sound_algo/data_only/matrix.scd +++ b/sound_algo/data_only/matrix.scd @@ -321,6 +321,52 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd }); } }; +// -- generative per-cycle evolution (SP-B) -- +~matEvolve = ~matEvolve ? false; +~matEvolveRate = ~matEvolveRate ? 0.15; // fraction of active cells recoloured per cycle +~matBaseGrid = ~matBaseGrid ? nil; // immutable clean snapshot while evolving + +// re-derive ~lp[\matrix] from ~matBaseGrid: recolour active cells (0 stays 0), +// mutated colour differs from base and is in 1..6. Bounded (always from base). +~matEvolveStep = { + ~matBaseGrid.notNil.if({ + ~matVoices.do { |name, vi| + ~matBars.do { |b| + var base = ~matBaseGrid[vi][b]; + ((base != 0) and: { 1.0.rand < ~matEvolveRate }).if({ + ~lp[\matrix][vi][b] = (1..6).reject({ |c| c == base }).choose + }, { + ~lp[\matrix][vi][b] = base + }) + } + }; + ~matVoices.size.do { |i| ~matLastColor[i] = -1 }; // force re-source next bar + ~matGridPush.() + }) +}; + +// push evolve state to surfaces +~matEvolvePush = { + ~toscSend !? { ~toscSend.("/matrix/evolve", ~matEvolve.if({ 1 }, { 0 }), ~matEvolveRate) } +}; + +// enable/disable: snapshot base on enable; restore clean grid on disable +~matSetEvolve = { |on| + on.asBoolean.if({ + ~matEvolve = true; + ~matBaseGrid = ~lp[\matrix].collect({ |row| row.copy }); + }, { + ~matEvolve = false; + ~matBaseGrid.notNil.if({ + ~lp[\matrix] = ~matBaseGrid.collect({ |row| row.copy }); + ~matVoices.size.do { |i| ~matLastColor[i] = -1 }; + ~matGridPush.() + }); + ~matBaseGrid = nil; + }); + ~matEvolvePush.() +}; + // -- ~matPush : push playhead position to all TouchOSC clients -- ~matPush = { ~toscSend !? { ~toscSend.("/matrix/playhead", ~lp[\matBar]) } @@ -370,7 +416,10 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd }); ~matApplyBar.(~lp[\matBar]); ~toscSend !? { ~toscSend.("/matrix/playhead", ~lp[\matBar]) }; ~matBeatsPerBar.wait; + ~matPrevBar = ~lp[\matBar]; ~lp[\matBar] = ~matNextBar.value; + // loop wrapped (playhead jumped backward) -> mutate one cycle + (~matEvolve and: { ~lp[\matBar] < ~matPrevBar }).if({ ~matEvolveStep.() }); } }).play(clock, quant: ~matBeatsPerBar); // -- per-step playhead: emits /matrix/stephead s (0..15) every 16th note -- diff --git a/sound_algo/data_only/test/test_matrix.scd b/sound_algo/data_only/test/test_matrix.scd index 7835501..c99b150 100644 --- a/sound_algo/data_only/test/test_matrix.scd +++ b/sound_algo/data_only/test/test_matrix.scd @@ -815,6 +815,39 @@ pass = pass and: { (~pEv[\freq] - ((60 + 0 + 7).midicps * (2 ** (~matColorDefs[5 ~matVoiceMods[\acid] = []; ~matColorDefs[5][2][\steps] = Array.fill(16, nil); +// --- generative evolve (SP-B) --- +// build a base grid with known 0 and active cells (small 2-voice fixture in-place) +~evBase = Array.fill(~matVoices.size, { Array.fill(~matBars, 0) }); +~evBase[0][0] = 3; ~evBase[0][1] = 0; ~evBase[0][2] = 5; // kick: active,0,active +~evBase[1][0] = 0; ~evBase[1][4] = 2; // hats: 0,...,active +~matBaseGrid = ~evBase.collect({ |row| row.copy }); +~lp[\matrix] = ~evBase.collect({ |row| row.copy }); +~matEvolveRate = 1.0; // mutate every active cell +~matEvolveStep.(); +// density preserved: 0-cells stay 0, active stay active (1..6) +pass = pass and: { ~lp[\matrix][0][1] == 0 }; // base 0 stays 0 +pass = pass and: { ~lp[\matrix][1][0] == 0 }; +pass = pass and: { (~lp[\matrix][0][0] >= 1) and: { ~lp[\matrix][0][0] <= 6 } }; +pass = pass and: { (~lp[\matrix][0][2] >= 1) and: { ~lp[\matrix][0][2] <= 6 } }; +pass = pass and: { (~lp[\matrix][1][4] >= 1) and: { ~lp[\matrix][1][4] <= 6 } }; +// recolour differs from base (rate 1.0 guarantees a change) +pass = pass and: { ~lp[\matrix][0][0] != 3 }; +pass = pass and: { ~lp[\matrix][0][2] != 5 }; +pass = pass and: { ~lp[\matrix][1][4] != 2 }; +// rate 0 -> identical to base +~matEvolveRate = 0.0; +~lp[\matrix] = ~evBase.collect({ |row| row.copy }); +~matEvolveStep.(); +pass = pass and: { ~lp[\matrix][0][0] == 3 and: { ~lp[\matrix][0][2] == 5 } }; +// bounded: base is untouched by mutation; setEvolve(false) restores base exactly +~matEvolveRate = 1.0; +~matSetEvolve.(true); // snapshots current ~lp[\matrix] as base +~evSnap = ~matBaseGrid.collect({ |row| row.copy }); +~matEvolveStep.(); ~matEvolveStep.(); // two cycles +pass = pass and: { ~matBaseGrid == ~evSnap }; // base unchanged by steps +~matSetEvolve.(false); // restore +pass = pass and: { ~lp[\matrix] == ~evSnap }; // working grid back to base + pass.if( { "TEST PASS".postln }, { "TEST FAIL".postln }