feat(sc): humanize + synth rotation + auto layer
User : "tu refait aussi les tracks avec toutes les possibilité
de synth disponible pour plus de variation et d'humanité dans
ces créations". Niveau choisi : subtil.
3 axes implémentés en helpers globaux qui s'appliquent à TOUS
les 345 tracks existants sans les réécrire :
1) HUMANIZE (engine.scd, opt-in via ~humanizeOn défaut true)
- Timing offset Pwhite ±3.5ms par note (1.4% sur dur 0.25)
- Velocity Pwhite ±10% par note
- Micro-detune ±3 cents sur acid/melody/harmony midinote
- Appliqué aux 7 Pdef (kickSeq/hatSeq/snareSeq/clapSeq/percSeq/
acidSeq/melodySeq/harmonySeq) via Pfunc factories ~hTime
~hVel ~hDet déclarées dans ~reload
- Désactivable : ~humanizeOn = false; ~reload.value
- Tunables : ~humanizeTiming, ~humanizeVelocity, ~humanizeDetune
2) SYNTH ROTATION (palette/synth_rotation.scd, opt-in défaut OFF)
- 8 rotators (acid/bass/lead/pad/pluck/kick/hat/snare/melody)
- Pool de 19 versions par catégorie (acid_v1..v19 etc.)
- Cycle toutes les ~rotationEvery=8 notes (configurable)
- Vérifie SynthDescLib avant de switcher pour éviter crash
- Activation : ~setRotationOn.(true)
3) AUTO LAYER (palette/auto_layer.scd, opt-in défaut OFF)
- 3 layers parallèles à melody/harmony/acid
- Mirror Pdef qui rejouent le même score avec instrument
différent (warmPad/choir/sub_boom par défaut)
- Octave offset configurable, amp réduit (0.20-0.30)
- Pan opposé, attack/release plus mou, cutoff moins ouvert
- Activation : ~setAutoLayer.(true)
Hook chargement dans live/_load.scd après scales/melodies/
harmonics. Les modules sont silencieux jusqu'à activation.
This commit is contained in:
+41
-22
@@ -186,51 +186,69 @@ s.waitForBoot {
|
||||
~acidPan = ~acidPan ? 0;
|
||||
~melodyPan = ~melodyPan ? 0;
|
||||
~harmonyPan = ~harmonyPan ? 0;
|
||||
// Humanize subtil : variations naturelles de timing + velocity +
|
||||
// micro-detune pour eviter le rendu robotique. Toggle via ~humanizeOn.
|
||||
// Niveaux subtils (cf. user choice) : timing 1.5%, vel 10%, detune 3c.
|
||||
~humanizeOn = ~humanizeOn ? true;
|
||||
~humanizeTiming = ~humanizeTiming ? 0.0035; // +/- 3.5ms sur dur 0.25s = 1.4%
|
||||
~humanizeVelocity = ~humanizeVelocity ? 0.10; // +/- 10%
|
||||
~humanizeDetune = ~humanizeDetune ? 0.03; // +/- 3 cents (0.03 semitone)
|
||||
// Pfunc renvoie un offset de timing pour chaque note, ou 0 si OFF.
|
||||
~hTime = { Pfunc { if (~humanizeOn) { rrand(~humanizeTiming.neg, ~humanizeTiming) } { 0 } } };
|
||||
~hVel = { Pfunc { if (~humanizeOn) { 1 + rrand(~humanizeVelocity.neg, ~humanizeVelocity) } { 1 } } };
|
||||
~hDet = { Pfunc { if (~humanizeOn) { rrand(~humanizeDetune.neg, ~humanizeDetune) } { 0 } } };
|
||||
Pdef(\kickSeq, Pbind(
|
||||
\instrument, \kick, \dur, 0.25,
|
||||
\amp, Pseq(~kickSteps.collect { |s| s * ~kickAmp }, inf),
|
||||
\timingOffset, ~hTime.value,
|
||||
\amp, Pseq(~kickSteps.collect { |s| s * ~kickAmp }, inf) * ~hVel.value,
|
||||
\freq, Pfunc { ~kickFreq }, \pitchAmt, Pfunc { ~kickPitchAmt },
|
||||
\decay, Pfunc { ~kickDecay }, \drive, Pfunc { ~kickDrive }, \click, Pfunc { ~kickClick },
|
||||
\scBus, ~scBus.index, \scShape, Pfunc { ~kickScShape },
|
||||
));
|
||||
Pdef(\hatSeq, Pbind(
|
||||
\instrument, \hat, \dur, 0.25,
|
||||
\amp, Pseq(~hatSteps.collect { |s| s * ~hatAmp }, inf),
|
||||
\timingOffset, ~hTime.value,
|
||||
\amp, Pseq(~hatSteps.collect { |s| s * ~hatAmp }, inf) * ~hVel.value,
|
||||
));
|
||||
Pdef(\snareSeq, Pbind(
|
||||
\instrument, \snare, \dur, 0.25,
|
||||
\amp, Pseq(~snareSteps.collect { |s| s * ~snareAmp }, inf),
|
||||
\timingOffset, ~hTime.value,
|
||||
\amp, Pseq(~snareSteps.collect { |s| s * ~snareAmp }, inf) * ~hVel.value,
|
||||
\freq, Pfunc { ~snareFreq }, \noiseAmt, Pfunc { ~snareNoiseAmt }, \decay, Pfunc { ~snareDecay },
|
||||
));
|
||||
Pdef(\clapSeq, Pbind(
|
||||
\instrument, \clap, \dur, 0.25,
|
||||
\amp, Pseq(~clapSteps.collect { |s| s * ~clapAmp }, inf),
|
||||
\timingOffset, ~hTime.value,
|
||||
\amp, Pseq(~clapSteps.collect { |s| s * ~clapAmp }, inf) * ~hVel.value,
|
||||
));
|
||||
Pdef(\percSeq, Pbind(
|
||||
\instrument, ~percInst, \dur, 0.25,
|
||||
\amp, Pseq(~percSteps.collect { |s| s * ~percAmp }, inf),
|
||||
\timingOffset, ~hTime.value,
|
||||
\amp, Pseq(~percSteps.collect { |s| s * ~percAmp }, inf) * ~hVel.value,
|
||||
\freq, Pfunc { if (~percInst == \tom) { ~tomFreq } { ~cowbellFreq } },
|
||||
));
|
||||
Pdef(\acidSeq, PmonoArtic(\acid,
|
||||
\dur, 0.25,
|
||||
\legato, 0.85,
|
||||
\midinote, Pseq(~acidNotes, inf),
|
||||
\accent, Pseq(~acidAccents, inf),
|
||||
\slideTime, Pseq(~acidSlides.collect { |s| if(s == 1) {0.12} {0.005} }, inf),
|
||||
\cutoff, Pfunc { ~acidCutoff },
|
||||
\cutoffEnv, Pfunc { ~acidCutoffEnv },
|
||||
\rq, Pfunc { ~acidRq },
|
||||
\drive, Pfunc { ~acidDrive },
|
||||
\amp, Pfunc { ~acidAmp },
|
||||
\pan, Pfunc { ~acidPan ? 0 },
|
||||
\scBus, ~scBus.index, // lit le sidechain
|
||||
\scAmt, Pfunc { ~acidScAmt },
|
||||
\dur, 0.25,
|
||||
\timingOffset,~hTime.value,
|
||||
\legato, 0.85,
|
||||
\midinote, Pseq(~acidNotes, inf) + ~hDet.value,
|
||||
\accent, Pseq(~acidAccents, inf),
|
||||
\slideTime, Pseq(~acidSlides.collect { |s| if(s == 1) {0.12} {0.005} }, inf),
|
||||
\cutoff, Pfunc { ~acidCutoff },
|
||||
\cutoffEnv, Pfunc { ~acidCutoffEnv },
|
||||
\rq, Pfunc { ~acidRq },
|
||||
\drive, Pfunc { ~acidDrive },
|
||||
\amp, Pfunc { ~acidAmp } * ~hVel.value,
|
||||
\pan, Pfunc { ~acidPan ? 0 },
|
||||
\scBus, ~scBus.index, // lit le sidechain
|
||||
\scAmt, Pfunc { ~acidScAmt },
|
||||
));
|
||||
Pdef(\melodySeq, Pbind(
|
||||
\instrument, ~melodyInst, \dur, 0.25,
|
||||
\timingOffset, ~hTime.value,
|
||||
\legato, 0.9,
|
||||
\type, Pseq(~melodyNotes.collect { |n| if(n > 0) { \note } { \rest } }, inf),
|
||||
\midinote, Pseq(~melodyNotes.collect { |n| if(n > 0) { n } { 60 } }, inf),
|
||||
\midinote, Pseq(~melodyNotes.collect { |n| if(n > 0) { n } { 60 } }, inf) + ~hDet.value,
|
||||
\cutoff, Pfunc { ~melodyCutoff },
|
||||
\rq, Pfunc { ~melodyRq },
|
||||
\attack, Pfunc { ~melodyAttack },
|
||||
@@ -242,7 +260,7 @@ s.waitForBoot {
|
||||
\wobbleDepth, Pfunc { ~melodyWobbleDepth },
|
||||
\wobbleShape, Pfunc { ~melodyWobbleShape },
|
||||
\subAmt, Pfunc { ~melodySubAmt },
|
||||
\amp, Pfunc { ~melodyAmp * ~melodyBoost },
|
||||
\amp, Pfunc { ~melodyAmp * ~melodyBoost } * ~hVel.value,
|
||||
\pan, Pfunc { ~melodyPan ? 0 },
|
||||
\scBus, ~scBus.index,
|
||||
\scAmt, Pfunc { ~leadScAmt },
|
||||
@@ -251,9 +269,10 @@ s.waitForBoot {
|
||||
// (32th, 16th, 8th) pour creer du contrepoint / phasing
|
||||
Pdef(\harmonySeq, Pbind(
|
||||
\instrument, ~harmonyInst, \dur, Pfunc { ~harmonyDur },
|
||||
\timingOffset, ~hTime.value,
|
||||
\legato, 0.9,
|
||||
\type, Pseq(~harmonyNotes.collect { |n| if(n > 0) { \note } { \rest } }, inf),
|
||||
\midinote, Pseq(~harmonyNotes.collect { |n| if(n > 0) { n } { 60 } }, inf),
|
||||
\midinote, Pseq(~harmonyNotes.collect { |n| if(n > 0) { n } { 60 } }, inf) + ~hDet.value,
|
||||
\cutoff, Pfunc { ~harmonyCutoff },
|
||||
\rq, Pfunc { ~harmonyRq },
|
||||
\attack, Pfunc { ~harmonyAttack },
|
||||
@@ -263,7 +282,7 @@ s.waitForBoot {
|
||||
\drive, Pfunc { ~harmonyDrive },
|
||||
\detune, Pfunc { ~harmonyDetune },
|
||||
\sawAmt, Pfunc { ~harmonySawAmt },
|
||||
\amp, Pfunc { ~harmonyAmp * ~melodyBoost },
|
||||
\amp, Pfunc { ~harmonyAmp * ~melodyBoost } * ~hVel.value,
|
||||
\pan, Pfunc { ~harmonyPan ? 0 },
|
||||
\scBus, ~scBus.index,
|
||||
\scAmt, Pfunc { ~leadScAmt },
|
||||
|
||||
@@ -37,6 +37,9 @@ Routine({
|
||||
(~base ++ "palette/scales.scd").load;
|
||||
(~base ++ "palette/melodies/index.scd").load;
|
||||
(~base ++ "palette/harmonics/index.scd").load;
|
||||
// Helpers humanity : humanize est dans engine, rotation + layer ici.
|
||||
(~base ++ "palette/synth_rotation.scd").load;
|
||||
(~base ++ "palette/auto_layer.scd").load;
|
||||
|
||||
// --- 2. setup.scd : definit ~check / ~setupAll / ~teardown -----
|
||||
"[live/_load] setup.scd ...".postln;
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// =====================================================================
|
||||
// auto_layer.scd -- Layering automatique de timbres
|
||||
//
|
||||
// Pour chaque note jouee par melody/harmony/acid, un layer secondaire
|
||||
// est joue en parallele a une amplitude reduite. Donne du corps et
|
||||
// de l'epaisseur sans toucher aux scores existants.
|
||||
//
|
||||
// USAGE :
|
||||
// ~setAutoLayer.(true) -> active toutes les couches
|
||||
// ~layerMelodyInst = \warmPad -> override le timbre du layer melody
|
||||
// ~layerMelodyAmp = 0.3 -> volume relatif (0..1)
|
||||
// ~layerMelodyOctave = -1 -> offset octave (-2..+2)
|
||||
//
|
||||
// Le layer reuse le meme score que le canal principal (\midinote etc.)
|
||||
// mais avec un instrument different. Pdef(\xxxLayer) tournent en
|
||||
// parallele de Pdef(\xxxSeq).
|
||||
//
|
||||
// =====================================================================
|
||||
|
||||
(
|
||||
~autoLayerOn = ~autoLayerOn ? false;
|
||||
~layerMelodyInst = ~layerMelodyInst ? \warmPad;
|
||||
~layerMelodyAmp = ~layerMelodyAmp ? 0.30;
|
||||
~layerMelodyOctave = ~layerMelodyOctave ? -1;
|
||||
~layerHarmonyInst = ~layerHarmonyInst ? \choir;
|
||||
~layerHarmonyAmp = ~layerHarmonyAmp ? 0.25;
|
||||
~layerAcidInst = ~layerAcidInst ? \sub_boom;
|
||||
~layerAcidAmp = ~layerAcidAmp ? 0.20;
|
||||
~layerAcidOctave = ~layerAcidOctave ? -1;
|
||||
|
||||
~setAutoLayer = { |b|
|
||||
~autoLayerOn = b;
|
||||
if (b) {
|
||||
// Cree les Pdef de layer en mirroir des Pdef principaux
|
||||
Pdef(\melodyLayer, Pbind(
|
||||
\instrument, Pfunc { ~layerMelodyInst },
|
||||
\dur, 0.25,
|
||||
\timingOffset, ~hTime.value,
|
||||
\type, Pseq(~melodyNotes.collect { |n| if(n > 0) {\note} {\rest} }, inf),
|
||||
\midinote, Pseq(~melodyNotes.collect { |n|
|
||||
if(n > 0) { n + (12 * ~layerMelodyOctave) } { 60 }
|
||||
}, inf) + ~hDet.value,
|
||||
\attack, Pfunc { ~melodyAttack * 1.5 }, // un peu plus mou
|
||||
\release, Pfunc { ~melodyRelease * 1.3 },
|
||||
\cutoff, Pfunc { ~melodyCutoff * 0.7 }, // plus assourdi
|
||||
\amp, Pfunc { ~melodyAmp * ~layerMelodyAmp } * ~hVel.value,
|
||||
\pan, Pfunc { (~melodyPan ? 0).neg * 0.5 }, // pan oppose attenue
|
||||
\scBus, ~scBus.index,
|
||||
\scAmt, Pfunc { ~leadScAmt * 1.2 },
|
||||
));
|
||||
Pdef(\harmonyLayer, Pbind(
|
||||
\instrument, Pfunc { ~layerHarmonyInst },
|
||||
\dur, Pfunc { ~harmonyDur ? 0.25 },
|
||||
\timingOffset, ~hTime.value,
|
||||
\type, Pseq(~harmonyNotes.collect { |n| if(n > 0) {\note} {\rest} }, inf),
|
||||
\midinote, Pseq(~harmonyNotes.collect { |n| if(n > 0) {n} {60} }, inf) + ~hDet.value,
|
||||
\attack, Pfunc { ~harmonyAttack * 1.5 },
|
||||
\release, Pfunc { ~harmonyRelease * 1.3 },
|
||||
\amp, Pfunc { ~harmonyAmp * ~layerHarmonyAmp } * ~hVel.value,
|
||||
\scBus, ~scBus.index,
|
||||
\scAmt, Pfunc { ~leadScAmt * 1.2 },
|
||||
));
|
||||
Pdef(\acidLayer, Pbind(
|
||||
\instrument, Pfunc { ~layerAcidInst },
|
||||
\dur, 0.25,
|
||||
\timingOffset, ~hTime.value,
|
||||
\midinote, Pseq(~acidNotes.collect { |n| n + (12 * ~layerAcidOctave) }, inf),
|
||||
\amp, Pfunc { ~acidAmp * ~layerAcidAmp } * ~hVel.value,
|
||||
\scBus, ~scBus.index,
|
||||
\scAmt, Pfunc { ~acidScAmt * 1.5 },
|
||||
));
|
||||
Pdef(\melodyLayer).play;
|
||||
Pdef(\harmonyLayer).play;
|
||||
Pdef(\acidLayer).play;
|
||||
"[auto_layer] ON -- 3 layers en parallele".postln;
|
||||
} {
|
||||
Pdef(\melodyLayer).stop;
|
||||
Pdef(\harmonyLayer).stop;
|
||||
Pdef(\acidLayer).stop;
|
||||
"[auto_layer] OFF".postln;
|
||||
};
|
||||
};
|
||||
|
||||
"[auto_layer] loaded - ~setAutoLayer.(true) to enable".postln;
|
||||
)
|
||||
@@ -0,0 +1,92 @@
|
||||
// =====================================================================
|
||||
// synth_rotation.scd -- Rotation auto entre les versions de SynthDef
|
||||
//
|
||||
// La palette dispose de ~20 versions de chaque SynthDef (acid_v1..v19,
|
||||
// bass808_v1..v19, lead_v1..v19, etc.) qui sont sous-utilisees parce
|
||||
// que les tracks figent l'instrument. Ce module fournit un Pfunc qui
|
||||
// cycle/randomise sur des banques par categorie, chaque N notes.
|
||||
//
|
||||
// USAGE :
|
||||
// ~rotateAcid.value -> Pfunc qui renvoie un nom \acid_vXX
|
||||
// ~rotateLead.value -> idem pour lead
|
||||
// ~rotateBass.value -> idem pour bass808
|
||||
// ~setRotationOn.(true) -> active toutes les rotations
|
||||
// ~setRotationOn.(false)-> revient aux SynthDefs canoniques
|
||||
//
|
||||
// Integration : remplacer dans engine.scd
|
||||
// \instrument, \acid -> \instrument, ~rotateAcid.value
|
||||
// \instrument, ~melodyInst -> \instrument, ~rotateMelody.value
|
||||
//
|
||||
// =====================================================================
|
||||
|
||||
(
|
||||
~rotationOn = ~rotationOn ? false; // OFF par defaut, opt-in
|
||||
~rotationEvery = ~rotationEvery ? 8; // change tous les N events
|
||||
|
||||
~setRotationOn = { |b|
|
||||
~rotationOn = b;
|
||||
("[rotation] " ++ if(b) {"ON"} {"OFF"} ++
|
||||
" (every " ++ ~rotationEvery ++ " events)").postln;
|
||||
};
|
||||
|
||||
// Banques de versions pour chaque famille. On peut elargir/restreindre
|
||||
// pour piloter le caractere global (ex: que des versions saturees).
|
||||
~rotPoolAcid = (1..19).collect { |i| ("acid_v" ++ i).asSymbol };
|
||||
~rotPoolBass = (1..19).collect { |i| ("bass808_v" ++ i).asSymbol };
|
||||
~rotPoolLead = (1..19).collect { |i| ("lead_v" ++ i).asSymbol };
|
||||
~rotPoolPad = (1..19).collect { |i| ("pad_v" ++ i).asSymbol };
|
||||
~rotPoolPluck = (1..19).collect { |i| ("pluck_v" ++ i).asSymbol };
|
||||
~rotPoolKick = (1..19).collect { |i| ("kick_v" ++ i).asSymbol };
|
||||
~rotPoolHat = (1..19).collect { |i| ("hat_v" ++ i).asSymbol };
|
||||
~rotPoolSnare = (1..19).collect { |i| ("snare_v" ++ i).asSymbol };
|
||||
|
||||
// Compteur global d'events pour switch synchro.
|
||||
~rotIdx = ~rotIdx ? 0;
|
||||
|
||||
// Construit un Pfunc qui renvoie un instrument :
|
||||
// - default fallback si rotation OFF
|
||||
// - sinon : pioche dans le pool toutes les ~rotationEvery notes
|
||||
~makeRotator = { |pool, fallback|
|
||||
Pfunc {
|
||||
if (~rotationOn and: { pool.size > 0 }) {
|
||||
// Index avance par groupe de ~rotationEvery
|
||||
var groupIdx = (~rotIdx div: ~rotationEvery) % pool.size;
|
||||
~rotIdx = ~rotIdx + 1;
|
||||
// Verifie que le SynthDef existe avant de le renvoyer
|
||||
if (SynthDescLib.global.at(pool[groupIdx]).notNil) {
|
||||
pool[groupIdx]
|
||||
} { fallback }
|
||||
} { fallback }
|
||||
}
|
||||
};
|
||||
|
||||
~rotateAcid = { ~makeRotator.(~rotPoolAcid, \acid) };
|
||||
~rotateBass = { ~makeRotator.(~rotPoolBass, \bass808) };
|
||||
~rotateLead = { ~makeRotator.(~rotPoolLead, \lead) };
|
||||
~rotatePad = { ~makeRotator.(~rotPoolPad, \pad) };
|
||||
~rotatePluck = { ~makeRotator.(~rotPoolPluck, \pluck) };
|
||||
~rotateKick = { ~makeRotator.(~rotPoolKick, \kick) };
|
||||
~rotateHat = { ~makeRotator.(~rotPoolHat, \hat) };
|
||||
~rotateSnare = { ~makeRotator.(~rotPoolSnare, \snare) };
|
||||
|
||||
// Rotator generique pour la melody (utilise ~melodyInst comme fallback)
|
||||
~rotateMelody = {
|
||||
Pfunc {
|
||||
if (~rotationOn) {
|
||||
// Choisit dans le pool correspondant a ~melodyInst
|
||||
var pool = (
|
||||
lead: ~rotPoolLead,
|
||||
pad: ~rotPoolPad,
|
||||
pluck: ~rotPoolPluck,
|
||||
).at(~melodyInst);
|
||||
if (pool.notNil and: { pool.size > 0 }) {
|
||||
var groupIdx = (~rotIdx div: ~rotationEvery) % pool.size;
|
||||
~rotIdx = ~rotIdx + 1;
|
||||
pool[groupIdx]
|
||||
} { ~melodyInst }
|
||||
} { ~melodyInst }
|
||||
}
|
||||
};
|
||||
|
||||
"[synth_rotation] loaded - ~setRotationOn.(true) to enable".postln;
|
||||
)
|
||||
Reference in New Issue
Block a user