feat(sound): concert engine + hypno drift morceau

Sequential techno setlist engine with crossed-arms gesture to advance
tracks. Adds ~poseCenter OSCdef, scene_concert.scd engine, morceaux/
directory, first morceau (Hypno Drift), and wires into boot/scenes.
This commit is contained in:
L'électron rare
2026-06-27 16:36:11 +02:00
parent 5dcefb3420
commit c4e4511262
5 changed files with 196 additions and 0 deletions
+15
View File
@@ -494,6 +494,7 @@ OSCdef(\poseLeave, { |msg|
var pid = msg[1];
~poseState.removeAt(pid);
~poseKin.removeAt(pid);
~poseCenter.removeAt(pid);
}, '/pose/leave');
OSCdef(\poseHands, { |msg|
@@ -508,6 +509,20 @@ OSCdef(\poseHands, { |msg|
~handFeat[\dist] = msg[10];
}, '/pose/hands');
~poseCenter = ~poseCenter ? Dictionary.new;
OSCdef(\poseCenter, { |msg|
var pid = msg[1].asInteger;
var e = ~poseCenter[pid] ? ();
e[\cx] = msg[2]; e[\cy] = msg[3];
~poseCenter[pid] = e;
}, '/pose/center');
OSCdef(\poseShoSpan, { |msg|
var pid = msg[1].asInteger;
var e = ~poseCenter[pid] ? ();
e[\depth] = msg[2]; // shoulder span proxy: bigger = closer
~poseCenter[pid] = e;
}, '/pose/sho_span');
"[OK] action-head OSC handlers".postln;
"[data_feeds] OSCdef installes (USGS, SWPC, NETZ, RTE, BLITZ, OPENSKY, BSKY, MEMPOOL, GCN).".postln;
+8
View File
@@ -160,6 +160,14 @@ Routine({
(~base ++ "scene_pose_play.scd").load;
0.5.wait;
// -- 6d. Concert engine + morceaux -----
(~base ++ "scene_concert.scd").load;
0.3.wait;
[
"morceaux/01_hypno_drift.scd",
].do { |f| (~base ++ f).load; 0.1.wait; };
s.sync;
// -- 7) Demarre la scene par defaut -----
if(~doScene.isNil) {
"[data-only/boot] ERREUR : ~doScene non defini".warn;
@@ -0,0 +1,50 @@
// Morceau 1 -- Hypno Drift (hypnotic). Drone filtre + sub pulse + kick sparse.
// mains hautes -> ouvre le filtre ; cx -> pan ; kick lent quand corps present.
(
SynthDef(\cc_drift_drone, { |out=0, freq=55, cutoff=300, amp=0.0, pan=0, gate=1|
var sig = Mix(Saw.ar(freq * [1, 1.003, 0.5])) * 0.25;
sig = RLPF.ar(sig, cutoff.clip(100, 6000), 0.4);
sig = sig * EnvGen.kr(Env.asr(3, 1, 4), gate, doneAction: 2);
Out.ar(out, Pan2.ar(sig, pan.clip(-1, 1)) * amp.lag(1.0));
}).add;
SynthDef(\cc_drift_sub, { |out=0, freq=55, amp=0.0, gate=1|
var sig = SinOsc.ar(freq.clip(20, 200)) * LFPulse.kr(0.5, 0, 0.35);
sig = sig * EnvGen.kr(Env.asr(0.5, 1, 1), gate, doneAction: 2);
Out.ar(out, Pan2.ar(sig, 0) * amp.lag(0.5));
}).add;
SynthDef(\cc_drift_kick, { |out=0, amp=0.5|
var env = EnvGen.kr(Env.perc(0.001, 0.4), doneAction: 2);
var fenv = EnvGen.kr(Env([120, 45], [0.08], \exp));
var sig = SinOsc.ar(fenv) * env;
Out.ar(out, Pan2.ar(sig.tanh, 0) * amp);
}).add;
~concertAdd.((
name: "Hypno Drift", style: \hypnotic,
start: { |ctx|
ctx[\drone] = Synth(\cc_drift_drone, [\freq, 55, \cutoff, 300, \amp, 0.22]);
ctx[\sub] = Synth(\cc_drift_sub, [\freq, 55, \amp, 0.18]);
ctx[\cut] = 300; ctx[\pan] = 0;
},
update: { |ctx, pose|
var hf = pose[\hands];
var ctr = pose[\center];
var hi = (1 - (((hf[\ry] ? 1) + (hf[\ly] ? 1)) * 0.5)).clip(0, 1);
var tgt = hi.linexp(0, 1, 250, 4500);
var panT = ((ctr[\cx] ? 0.5) * 2 - 1).clip(-1, 1);
ctx[\cut] = ctx[\cut] + ((tgt - ctx[\cut]) * 0.05);
ctx[\pan] = ctx[\pan] + ((panT - ctx[\pan]) * 0.05);
ctx[\drone].set(\cutoff, ctx[\cut], \pan, ctx[\pan]);
},
tick: { |ctx, pose, beat|
if(((beat % (0.5 / ~ccCtlDur).round.max(1)) == 0) and: { pose[\hasBody] }) {
Synth(\cc_drift_kick, [\amp, 0.5]);
};
},
stop: { |ctx|
ctx[\drone] !? { |s| s.set(\gate, 0) };
ctx[\sub] !? { |s| s.set(\gate, 0) };
},
));
"[morceau] Hypno Drift loaded".postln;
)
+122
View File
@@ -0,0 +1,122 @@
// =====================================================================
// data_only/scene_concert.scd -- moteur de concert (setlist techno).
// Setlist sequentielle, geste bras-croises = morceau suivant, crossfade.
// Active via ~doScene.(\concert). Les morceaux s'enregistrent via
// ~concertAdd depuis morceaux/NN_*.scd (charges apres ce fichier).
// =====================================================================
(
~ccCtlDur = 0.05; // periode du tick de controle, en BEATS
~ccCrossHold = 1.5; // duree de maintien du geste bras-croises (s)
~ccCrossCool = 3.0; // cooldown apres declenchement (s)
// -- snapshot pose (premiere personne), tout nil-garde --
~ccPose = {
var kin = ~poseKin.notNil.if({ ~poseKin.values.detect({ |v| v.notNil }) });
var st = ~poseState.notNil.if({ ~poseState.values.detect({ |v| v.notNil }) });
var ctr = ~poseCenter.notNil.if({ ~poseCenter.values.detect({ |v| v.notNil }) });
(kin: (kin ? ()), state: (st ? ()), center: (ctr ? ()),
hands: (~handFeat ? ()), hasBody: kin.notNil);
};
// -- registre des morceaux --
~concert = ~concert ? ();
~concert[\morceaux] = [];
~concertAdd = { |m| ~concert[\morceaux] = ~concert[\morceaux].add(m) };
~concert[\running] = false;
~concert[\idx] = 0;
~concert[\clock] = nil;
~concert[\routine] = nil;
~concert[\ctx] = nil; // (m: module, ctx: scratch) du morceau courant
~concert[\crossT] = 0.0;
~concert[\lastFire] = -100.0;
~concert[\startMorceau] = { |i|
var m = ~concert[\morceaux][i];
var ctx = ();
m[\start].(ctx);
~concert[\ctx] = (m: m, ctx: ctx);
("[concert] " ++ (i + 1) ++ "/" ++ ~concert[\morceaux].size
++ " : " ++ m[\name]).postln;
};
~concert[\stopMorceau] = {
~concert[\ctx] !? { |c| c[\m][\stop].(c[\ctx]) };
~concert[\ctx] = nil;
};
~concert[\next] = {
if(~concert[\idx] < (~concert[\morceaux].size - 1)) {
~concert[\stopMorceau].(); // fade out (stop releve les gates)
~concert[\idx] = ~concert[\idx] + 1;
~concert[\startMorceau].(~concert[\idx]); // fade in
} {
"[concert] derniere piste (fin de set)".postln;
};
};
// geste bras-croises : main D a gauche de main G, a mi-hauteur, maintenu
~concert[\detectCross] = { |pose, now|
var hf = pose[\hands];
var crossed = pose[\hasBody] and: {
((hf[\rx] ? 0.5) < ((hf[\lx] ? 0.5) - 0.08))
and: { (hf[\ry] ? 0).inRange(0.25, 0.75) }
and: { (hf[\ly] ? 0).inRange(0.25, 0.75) }
};
if(crossed) {
if(~concert[\crossT] <= 0) { ~concert[\crossT] = now };
if(((now - ~concert[\crossT]) >= ~ccCrossHold)
and: { (now - ~concert[\lastFire]) > ~ccCrossCool }) {
~concert[\lastFire] = now;
~concert[\crossT] = 0;
~concert[\next].();
};
} { ~concert[\crossT] = 0 };
};
~concert[\start] = {
if(~concert[\running]) { "[concert] deja en cours".postln } {
if(~concert[\morceaux].size < 1) {
"[concert] aucun morceau enregistre".warn;
} {
~concert[\running] = true;
~concert[\idx] = 0;
~concert[\clock] = TempoClock(126/60).permanent_(true);
~concert[\startMorceau].(0);
~concert[\routine] = Routine({
var beat = 0;
inf.do {
var pose = ~ccPose.value;
var now = thisThread.seconds;
var c;
~concert[\detectCross].(pose, now);
c = ~concert[\ctx]; // relire (next a pu permuter)
c !? { |cc|
cc[\m][\update].(cc[\ctx], pose);
cc[\m][\tick] !? { |t| t.(cc[\ctx], pose, beat) };
};
beat = beat + 1;
~ccCtlDur.wait;
};
}).play(~concert[\clock]);
"[concert] STARTED".postln;
};
};
};
~concert[\stop] = {
~concert[\routine] !? { |r| r.stop };
~concert[\stopMorceau].();
~concert[\clock] !? { |c| c.stop };
~concert[\clock] = nil;
~concert[\running] = false;
"[concert] STOPPED".postln;
};
~doSceneConcert = {
~concert[\start].();
~doRegisterPriv.(\concert, { ~concert[\stop].() });
};
"[data-only/scene_concert] engine ready".postln;
)
+1
View File
@@ -319,6 +319,7 @@
\quiet, { ~doSceneQuiet.value },
\live_pose, { ~doSceneLivePose.value },
\play, { ~doScenePlay.value },
\concert, { ~doSceneConcert.value },
\all, {
~doSceneCavity.value;
~doSceneGeo.value;