feat(matrix): pitch modulation target

This commit is contained in:
L'électron rare
2026-06-29 18:12:21 +02:00
parent ece67bf1fb
commit e53ae8d94d
2 changed files with 58 additions and 9 deletions
+21 -9
View File
@@ -27,12 +27,12 @@
~matInstruments = ~matInstruments ? Array.fill(~matVoices.size, nil);
~matModTargets = ~matModTargets ? IdentityDictionary[
\kick->[\amp,\pan,\rev], \hats->[\amp,\pan,\rev], \clap->[\amp,\pan,\rev],
\perc->[\amp,\cutoff,\pan,\rev],
\sub->[\amp,\pan,\rev,\res], \acid->[\amp,\cutoff,\pan,\rev,\res], \arp->[\amp,\cutoff,\pan,\rev,\res],
\lead->[\amp,\cutoff,\pan,\rev,\res], \stab->[\amp,\cutoff,\pan,\rev,\res], \pad->[\amp,\cutoff,\pan,\rev,\res],
\ride->[\amp,\pan,\rev], \rim->[\amp,\pan,\rev], \tom->[\amp,\pan,\rev],
\reese->[\amp,\cutoff,\pan,\rev,\res],
\bells->[\amp,\pan,\rev,\res], \sweep->[\amp,\pan,\rev],
\perc->[\amp,\cutoff,\pan,\rev,\pitch],
\sub->[\amp,\pan,\rev,\res,\pitch], \acid->[\amp,\cutoff,\pan,\rev,\res,\pitch], \arp->[\amp,\cutoff,\pan,\rev,\res,\pitch],
\lead->[\amp,\cutoff,\pan,\rev,\res,\pitch], \stab->[\amp,\cutoff,\pan,\rev,\res,\pitch], \pad->[\amp,\cutoff,\pan,\rev,\res,\pitch],
\ride->[\amp,\pan,\rev], \rim->[\amp,\pan,\rev], \tom->[\amp,\pan,\rev,\pitch],
\reese->[\amp,\cutoff,\pan,\rev,\res,\pitch],
\bells->[\amp,\pan,\rev,\res,\pitch], \sweep->[\amp,\pan,\rev],
\melody->[\amp,\cutoff,\pan,\rev,\res], \chord->[\amp,\cutoff,\pan,\rev,\res],
\fx->[\amp,\pan,\rev], \snare->[\amp,\pan,\rev],
\crash->[\amp,\pan,\rev], \shaker->[\amp,\pan,\rev]
@@ -158,7 +158,8 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd });
var byTarget = IdentityDictionary.new;
var pairs = [];
((~matVoiceMods[name] ? []).select { |b|
b[\source].notNil and: { b[\target].notNil } and: { allowed.includes(b[\target]) }
b[\source].notNil and: { b[\target].notNil }
and: { b[\target] != \pitch } and: { allowed.includes(b[\target]) }
}).do { |b| byTarget[b[\target]] = (byTarget[b[\target]] ? []) ++ [b] };
byTarget.keysValuesDo { |tgt, list|
(tgt == \cutoff).if({
@@ -208,7 +209,7 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd });
var pitched = [\sub, \acid, \arp, \lead, \stab, \pad, \reese, \bells, \perc, \tom];
var freqRatio = 2 ** (spec[\octave] ? 0);
var freqPair = (pitched.includes(name) and: { freqRatio != 1.0 }).if({
[\freq, Pfunc({ |e| (e[\freq] ? 440) * freqRatio })]
[\freq, Pfunc({ |e| (e[\freq] ? 440) * freqRatio })] // pitch mod step-path only (no degree here)
}, { [] });
var modPairs = ~matModPairs.(name, spec, volOf);
Pchain(
@@ -229,7 +230,7 @@ File.exists(~matDir).not.if({ ("mkdir -p " ++ ~matDir.quote).systemCmd });
var pitched = [\sub, \acid, \arp, \lead, \stab, \pad, \reese, \bells, \perc, \tom];
var modPairs = ~matModPairs.(name, spec, volOf);
var freqPair = pitched.includes(name).if({
[\freq, Pfunc { |e| (~lpNote.notNil).if({ ~lpNote.(e[\degree], 0).midicps * freqRatio }, { 440 }) }]
[\freq, Pfunc { |e| (~lpNote.notNil).if({ ~lpNote.(e[\degree] + ~matPitchOffset.(name), 0).midicps * freqRatio }, { 440 }) }]
}, { [] });
Pbind(*([
\stepIdx, Pseq((0..15), inf),
@@ -950,6 +951,17 @@ OSCdef(\mat_audition, { |msg, time, addr|
// reader: clamp to 0..1, default 0 when absent
~matModSourceVal = { |key| (~matModCache[key] ? 0).clip(0, 1) };
// +/- scale-degree offset at full deflection (env-tunable; 7 ~= 1 diatonic octave)
~matPitchRange = ~matPitchRange ? 7;
// bipolar additive sum of \pitch bindings -> integer degree offset applied in the freq Pfunc
~matPitchOffset = { |name|
var off = 0;
((~matVoiceMods[name] ? []).select { |b| b[\target] == \pitch }).do { |b|
off = off + (((~matModSourceVal.(b[\source]) * 2 - 1) * b[\depth] * ~matPitchRange).round.asInteger)
};
off
};
// -- Live modvalues push (~12 Hz) --
~matModValuesPush = {
~toscSend !? {
+37
View File
@@ -766,6 +766,43 @@ File.delete(~matDir +/+ "t6_tmp.matrix");
~matVoicePoses[\acid] = [];
}.value;
// --- pitch target (cycle 1): bipolar scale-degree offset ---
~matPitchRange = 7; // make the assertion deterministic regardless of env
// +full deflection: source 1.0, depth 1.0 -> +7
~matVoiceMods[\acid] = [ (source: \rHandY, target: \pitch, depth: 1.0) ];
~matModCache[\rHandY] = 1.0;
pass = pass and: { ~matPitchOffset.(\acid) == 7 };
// -full deflection: source 0.0 -> -7
~matModCache[\rHandY] = 0.0;
pass = pass and: { ~matPitchOffset.(\acid) == -7 };
// neutral: source 0.5 -> 0
~matModCache[\rHandY] = 0.5;
pass = pass and: { ~matPitchOffset.(\acid) == 0 };
// additive: two bindings sum (1.0*+7 and 0.5-source*0 -> +7)
~matVoiceMods[\acid] = [ (source: \rHandY, target: \pitch, depth: 1.0),
(source: \bodyY, target: \pitch, depth: 1.0) ];
~matModCache[\rHandY] = 1.0; ~matModCache[\bodyY] = 0.5; // +7 + 0 = +7
pass = pass and: { ~matPitchOffset.(\acid) == 7 };
~matVoiceMods[\acid] = [];
// gating: pitch allowed on acid (melodic) but NOT on kick (percussive)
pass = pass and: { (~matModTargets[\acid] ? []).includes(\pitch) };
pass = pass and: { (~matModTargets[\kick] ? []).includes(\pitch).not };
// pitch is NOT emitted as a ~matModPairs pair (handled in the freq Pfunc)
~matVoiceMods[\acid] = [ (source: \rHandY, target: \pitch, depth: 1.0) ];
pass = pass and: { ~matModPairs.(\acid, (amp: 1.0), { 1.0 }).includes(\pitch).not };
~matVoiceMods[\acid] = [];
// freq integration: with ~lpNote stubbed, a +7 offset transposes the played note up
~origLpNote = ~lpNote;
~lpNote = { |deg, oct| 60 + deg }; // simple midi-note stub
~matVoiceMods[\acid] = [ (source: \rHandY, target: \pitch, depth: 1.0) ];
~matModCache[\rHandY] = 1.0; // +7
~matColorDefs[5][2][\steps] = Array.fill(16, nil).put(0, (degree: 0, vel: 0.8));
~pEv = ~matVariation.(\acid, 2, 5).asStream.next(());
pass = pass and: { (~pEv[\freq] - ((60 + 0 + 7).midicps * (2 ** (~matColorDefs[5][2][\octave] ? 0)))).abs < 1.0 };
~lpNote = ~origLpNote;
~matVoiceMods[\acid] = [];
~matColorDefs[5][2][\steps] = Array.fill(16, nil);
pass.if(
{ "TEST PASS".postln },
{ "TEST FAIL".postln }