Files
2026-06-28 17:00:13 +02:00

172 lines
7.9 KiB
Markdown

// =====================================================================
// data_only/scene_pose_play.scd -- le corps joue de la musique.
// Action -> mode, main D -> lead (geste), main G -> accord (arpege),
// vitesse du corps -> tempo. Active via ~doScene.(\play).
// =====================================================================
(
// -- 1. Table des modes -------------------------------------------------
~bpModes = (
ambient: (label: 1, name: "ambient",
scale: [0,2,3,5,7,8,10], root: 45, tempoLo: 56, tempoHi: 80,
kick: false, hatMul: 0.5),
pulse: (label: 0, name: "pulse",
scale: [0,2,4,7,9], root: 57, tempoLo: 88, tempoHi: 120,
kick: true, hatMul: 1.0),
drive: (label: 2, name: "drive",
scale: [0,2,3,5,7,9,10], root: 55, tempoLo: 118, tempoHi: 150,
kick: true, hatMul: 1.6),
);
~bpModeForLabel = { |labelIdx|
switch(labelIdx,
0, \pulse, 1, \ambient, 2, \drive, \pulse); // defaut pulse
};
// -- 2. Quantification sur l'echelle du mode ---------------------------
// pos01 (0..1) -> degre sur l'echelle, etale sur `octaves` octaves depuis
// la root du mode. Renvoie une midinote.
~bpScale = { |modeKey, pos01, octaves=2|
var m = ~bpModes[modeKey];
var scale = m[\scale];
var nDeg = scale.size * octaves;
var idx = (pos01.clip(0, 1) * (nDeg - 1)).round.asInteger;
var oct = idx div: scale.size;
var deg = idx % scale.size;
m[\root] + (oct * 12) + scale[deg];
};
// -- 3. Mapping pose -> musique -- fonctions pures ----------------------
~bpMap = ();
~bpMap[\tempo] = { |modeKey, speed|
var m = ~bpModes[modeKey];
(speed ? 0).linlin(0.0, 0.6, m[\tempoLo], m[\tempoHi]).clip(m[\tempoLo], m[\tempoHi]);
};
~bpMap[\leadNote] = { |modeKey, hf|
~bpScale.(modeKey, (hf[\ry] ? 0), 2);
};
~bpMap[\leadTrig] = { |hf, lastTrigTime, now|
((hf[\rspeed] ? 0) > 0.12) and: { (now - lastTrigTime) > 0.09 };
};
// 4 triades diatoniques I/IV/V/vi construites sur l'echelle du mode.
~bpMap[\chordDegrees] = { |modeKey, hf|
var m = ~bpModes[modeKey];
var sc = m[\scale];
var roots = [0, 3, 4, 5]; // index de degre des 4 accords
var ci = ((hf[\ly] ? 0).clip(0,1) * 3).round.asInteger; // main G hauteur -> accord
var base = roots[ci];
var octShift = ((hf[\lopen] ? 0) > 0.5).if({ 12 }, { 0 });
[0, 2, 4].collect { |step|
var d = base + step;
m[\root] + ((d div: sc.size) * 12) + sc[d % sc.size] + octShift;
};
};
~bpMap[\density] = { |modeKey, speed|
var m = ~bpModes[modeKey];
(((speed ? 0).linlin(0.0, 0.6, 1, 4) * m[\hatMul]).round.asInteger).clip(1, 4);
};
// -- 5) Helpers corps unique -------------------------------------------
~bpFirstBody = {
var kin = ~poseKin.notNil.if({ ~poseKin.values.detect { |v| v.notNil } });
var st = ~poseState.notNil.if({ ~poseState.values.detect { |v| v.notNil } });
(kin: kin, state: st);
};
// -- 6) Le moteur -------------------------------------------------------
~bpCtlDur = 0.05; // periode du tick de controle, en BEATS (la routine tourne sur le TempoClock)
~bpEngine = (
running: false, clock: nil, mode: \pulse, verbose: false,
pad: nil, chord: [57, 60, 64], lastLead: 0.0, arpStep: 0,
modeCand: \pulse, modeStable: 0, tempoSm: 100.0, routine: nil,
start: {
if(~bpEngine[\running]) { "[bp] deja en cours".postln } {
~bpEngine[\running] = true;
~bpEngine[\clock] = TempoClock.default; // horloge unique (Lot C)
~setTempo.(100); // tempo de base body-play
~bpEngine[\pad] = Synth(\bp_pad, [\freq, ~bpModes[\pulse][\root].midicps, \amp, 0.0]);
~bpEngine[\routine] = Routine({
var beatCount = 0;
inf.do {
var b = ~bpFirstBody.value;
var hasBody = b[\kin].notNil;
var label = (b[\state] ? ()).at(\labelIdx);
var speed = (b[\kin] ? ()).at(\speed) ? 0;
var modeKey = ~bpModeForLabel.(label);
var m, now, hf, tgtTempo;
// -- mode stability guard + cross-fade --
if(modeKey == ~bpEngine[\modeCand]) {
~bpEngine[\modeStable] = ~bpEngine[\modeStable] + 1;
} { ~bpEngine[\modeCand] = modeKey; ~bpEngine[\modeStable] = 0; };
if((~bpEngine[\modeStable] == 5) and: { modeKey != ~bpEngine[\mode] }) {
~bpEngine[\mode] = modeKey;
~bpEngine[\pad].set(\gate, 0); // fade out ancien pad
~bpEngine[\pad] = Synth(\bp_pad, [
\freq, ~bpModes[modeKey][\root].midicps, \amp, 0.18]);
if(~bpEngine[\verbose]) { ("[bp] mode -> " ++ modeKey).postln };
};
m = ~bpModes[~bpEngine[\mode]];
// -- tempo lisse (~2s) puis clock --
tgtTempo = ~bpMap[\tempo].(~bpEngine[\mode], speed);
~bpEngine[\tempoSm] = ~bpEngine[\tempoSm]
+ ((tgtTempo - ~bpEngine[\tempoSm]) * 0.03);
~bpEngine[\clock].tempo = ~bpEngine[\tempoSm] / 60;
// -- pad: silence si pas de corps --
~bpEngine[\pad].set(\amp, hasBody.if({ 0.18 }, { 0.06 }));
// -- lead (main D au geste) --
hf = ~handFeat ? ();
now = thisThread.seconds;
if(hasBody and: { ~bpMap[\leadTrig].(hf, ~bpEngine[\lastLead], now) }) {
~bpEngine[\lastLead] = now;
Synth(\bp_lead, [
\freq, ~bpMap[\leadNote].(~bpEngine[\mode], hf).midicps,
\amp, (hf[\rspeed] ? 0).linlin(0.12, 0.6, 0.15, 0.4),
\cutoff, (hf[\ropen] ? 0).linexp(0, 1, 600, 5000),
\pan, (hf[\rx] ? 0.5).linlin(0, 1, -0.8, 0.8)]);
};
// -- accord courant (main G) --
if(hasBody) { ~bpEngine[\chord] = ~bpMap[\chordDegrees].(~bpEngine[\mode], hf) };
// -- sequence rythmique sur le beat (toutes les ~1 noire) --
if((beatCount % (0.25 / ~bpCtlDur).round.max(1)) == 0) {
var dens = ~bpMap[\density].(~bpEngine[\mode], speed);
if(hasBody) {
// arpege : note suivante de l'accord
~bpEngine[\arpStep] = (~bpEngine[\arpStep] + 1) % ~bpEngine[\chord].size;
Synth(\bp_arp, [\freq, ~bpEngine[\chord][~bpEngine[\arpStep]].midicps]);
// hats selon densite
dens.do { |i| Synth(\bp_hat, [\amp, 0.18, \pan, 0.3.rand2]) };
// kick + bass selon le mode
if(m[\kick] and: { (beatCount % (1.0 / ~bpCtlDur).round.max(1)) == 0 }) {
Synth(\bp_kick);
Synth(\bp_bass, [\freq, (~bpEngine[\chord][0] - 12).midicps]);
};
};
};
beatCount = beatCount + 1;
~bpCtlDur.wait;
};
}).play(~bpEngine[\clock]);
"[bp] STARTED".postln;
};
},
stop: {
~bpEngine[\routine] !? { |r| r.stop };
~bpEngine[\pad] !? { |p| p.set(\gate, 0) };
// NE PAS stopper l'horloge : alias TempoClock.default (Lot C),
// Routine deja stoppee. La stopper figerait tout le systeme.
~bpEngine[\clock] = nil;
~bpEngine[\running] = false;
"[bp] STOPPED".postln;
},
);
// -- 7) Enregistrement scene -------------------------------------------
~doScenePlay = { ~bpEngine[\start].(); ~doRegisterPriv.(\play, { ~bpEngine[\stop].() }) };
"[data-only/scene_pose_play] modes + scale ready".postln;
)