172 lines
7.9 KiB
Markdown
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;
|
|
)
|