feat(sound-algo): action-head live FX scene
Add scene_pose_action.scd to map real-time action-head pose metrics to live effect parameters. Reads ~poseState and ~poseKin dicts (populated by data_feeds.scd OSC handlers) and drives: - speed -> drive amount (0..1) - accel -> filter cutoff (200 Hz..6 kHz) - symmetry -> stereo width (0..1) - label argmax -> reverb/compressor presets (debout/assise/danse) Manual load only (eval block-by-block in SC IDE). Validates P:0 B:0.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
// =====================================================================
|
||||
// scene_pose_action.scd -- pilote les FX live a partir de l'action_head.
|
||||
//
|
||||
// Necessite : data_feeds.scd deja charge (handlers OSCdef \poseAction,
|
||||
// \poseKin, \poseEnter, \poseLeave). Les dicts ~poseState et ~poseKin
|
||||
// doivent etre populates en temps reel par data_only_viz.action_head_pub.
|
||||
//
|
||||
// Mapping :
|
||||
// speed (m/s mean joint) -> drive amount 0..1
|
||||
// accel (m/s2) -> filter cutoff 200 Hz..6 kHz
|
||||
// symmetry (-1..1) -> stereo width 0..1
|
||||
// label argmax (0=debout, 1=assise, 2=danse) :
|
||||
// - debout -> reverb mid, kick on, melody pad
|
||||
// - assise -> pad ambient, kick off, lo-fi
|
||||
// - danse -> drive max, kick punchy, acid lead
|
||||
//
|
||||
// Usage live (a evaluer bloc par bloc dans le SC IDE) :
|
||||
// [0] SETUP : declare le helper ~mapPoseToFx + lance la Routine.
|
||||
// [1] START : ~scenePoseAction.start
|
||||
// [2] STOP : ~scenePoseAction.stop
|
||||
// [3] TWEAK : ajuster les seuils dans ~paConfig
|
||||
// =====================================================================
|
||||
|
||||
// [0] SETUP -----------------------------------------------------------
|
||||
(
|
||||
~paConfig = (
|
||||
speedMin: 0.0, speedMax: 0.8,
|
||||
accelMin: 0.0, accelMax: 3.0,
|
||||
cutoffMin: 200, cutoffMax: 6000,
|
||||
rate: 10, // refresh Hz
|
||||
);
|
||||
|
||||
~mapPoseToFx = {
|
||||
var avgSpeed = 0, avgAccel = 0, avgSym = 0, avgProbs = [0, 0, 0];
|
||||
var n = max(1, ~poseKin.size);
|
||||
var labelIdx;
|
||||
|
||||
~poseKin.values.do { |kin|
|
||||
avgSpeed = avgSpeed + (kin[\speed] ? 0);
|
||||
avgAccel = avgAccel + (kin[\accel] ? 0);
|
||||
avgSym = avgSym + (kin[\symmetry] ? 0);
|
||||
};
|
||||
avgSpeed = avgSpeed / n;
|
||||
avgAccel = avgAccel / n;
|
||||
avgSym = avgSym / n;
|
||||
|
||||
~poseState.values.do { |ps|
|
||||
var p = ps[\probs] ? [0, 0, 0];
|
||||
avgProbs = avgProbs.collect { |v, i| v + (p[i] ? 0) };
|
||||
};
|
||||
avgProbs = avgProbs.collect { _ / n };
|
||||
labelIdx = avgProbs.maxIndex ? 0;
|
||||
|
||||
// Drive + filter + width
|
||||
if (~fxDrive.notNil) {
|
||||
~fxDrive.(avgSpeed.linlin(~paConfig[\speedMin], ~paConfig[\speedMax], 0, 1));
|
||||
};
|
||||
if (~fxCut.notNil) {
|
||||
~fxCut.(avgAccel.linexp(~paConfig[\accelMin], ~paConfig[\accelMax],
|
||||
~paConfig[\cutoffMin], ~paConfig[\cutoffMax]));
|
||||
};
|
||||
if (~fxSt.notNil) { ~fxSt.(avgSym.linlin(-1, 1, 0, 1)) };
|
||||
|
||||
// Label-driven track switch (smooth crossfade between presets)
|
||||
switch (labelIdx,
|
||||
0, {
|
||||
if (~fxComp.notNil) { ~fxComp.(0.7) };
|
||||
if (~fxRev.notNil) { ~fxRev.(0.4) };
|
||||
},
|
||||
1, {
|
||||
if (~fxComp.notNil) { ~fxComp.(0.3) };
|
||||
if (~fxRev.notNil) { ~fxRev.(0.6) };
|
||||
},
|
||||
2, {
|
||||
if (~fxComp.notNil) { ~fxComp.(0.9) };
|
||||
if (~fxRev.notNil) { ~fxRev.(0.2) };
|
||||
if (~fxDrive.notNil){ ~fxDrive.(avgSpeed.linlin(0, 0.6, 0.3, 1.0)) };
|
||||
}
|
||||
);
|
||||
|
||||
[labelIdx, avgSpeed.round(0.01), avgAccel.round(0.01),
|
||||
avgSym.round(0.01)];
|
||||
};
|
||||
|
||||
~scenePoseAction = ~scenePoseAction ?? {
|
||||
(
|
||||
running: false,
|
||||
routine: nil,
|
||||
start: {
|
||||
if (~scenePoseAction[\running]) {
|
||||
"[scene_pose_action] already running".postln;
|
||||
} {
|
||||
~scenePoseAction[\running] = true;
|
||||
~scenePoseAction[\routine] = Routine({
|
||||
inf.do {
|
||||
var snapshot = ~mapPoseToFx.();
|
||||
if (~scenePoseAction[\verbose] ? false) {
|
||||
("[pose] label=" ++ snapshot[0]
|
||||
++ " s=" ++ snapshot[1]
|
||||
++ " a=" ++ snapshot[2]
|
||||
++ " sym=" ++ snapshot[3]).postln;
|
||||
};
|
||||
(1 / ~paConfig[\rate]).wait;
|
||||
};
|
||||
}).play;
|
||||
"[scene_pose_action] STARTED".postln;
|
||||
};
|
||||
},
|
||||
stop: {
|
||||
~scenePoseAction[\routine] !? { |r| r.stop };
|
||||
~scenePoseAction[\routine] = nil;
|
||||
~scenePoseAction[\running] = false;
|
||||
"[scene_pose_action] STOPPED".postln;
|
||||
},
|
||||
verbose: false,
|
||||
)
|
||||
};
|
||||
|
||||
"[OK] SETUP scene_pose_action".postln;
|
||||
)
|
||||
|
||||
// [1] START -----------------------------------------------------------
|
||||
// ~scenePoseAction[\verbose] = true; // optionnel : log chaque tick
|
||||
// ~scenePoseAction[\start].();
|
||||
|
||||
// [2] STOP ------------------------------------------------------------
|
||||
// ~scenePoseAction[\stop].();
|
||||
|
||||
// [3] TWEAK ------------------------------------------------------------
|
||||
// ~paConfig[\speedMax] = 1.2;
|
||||
// ~paConfig[\rate] = 20;
|
||||
Reference in New Issue
Block a user