Inviluppi dinamici
Prima di addentrarci nella descrizione delle diverse possibilità che abbiamo a disposizione per modificare dinamicamente i parametri degli inviluppi (così come abbiamo fatto per l'ampiezza) poniamo l'attenzione su tre argomenti che ci torneranno utili nello sviluppo dei temi successivi:
Possiamo utilizzare un'abbreviazione sintattica che unisce idealmente EnvGen con Env trasformando una BPF in segnale:
s.boot; s.scope(1); s.meter(1,1); ( SynthDef(\envi, {arg freq=890, t_gate=0; var sig,env; sig = SinOsc.ar(freq); env = Env.new([0,1,0.3,0],[0.01,0.2,0.3],\cub).kr(doneAction:0, gate:t_gate); Out.ar(0,sig*env) } ).add; {a = Synth(\envi)}.defer(0.1); ) a.set(\t_gate,1);
Personalmente trovo questa abbreviazione sintattica più chiara del codice scritto per esteso e la adotteremo negli esempi seguenti.
Nei codici di esempio precedenti e in quelli seguenti abbiamo utilizzato quasi sempre una scelta randomica dei valori (non deterministica). Questa scelta è stata fatta esclusivamente per scopi esemplificativi, tutto quanto esposto vale anche per l'utilizzo di tecniche deterministiche.
Abbiamo visto come ci siano tre diversi modi di specificare i valori temporali di un inviluppo:
- un'Array di tempi delta [1,1,1] come nel caso di Env.new, Env.perc, Env.adsr, etc.
- un'Array di onsets [0,1,2] come nel caso di Env.pair e Env.xyc
- la Durata totale come nel caso di Env.triangle e Env.sine
Tra questi il modo più vicino al pensiero musicale tradizionale è quest'ultimo ovvero specificare la durata totale di un suono:
( SynthDef(\envi, {arg dur=0.1,t_gate=0; var sig,amp; sig = SinOsc.ar(1200); amp = Env.triangle(dur).kr(0,t_gate); Out.ar(0,sig*amp) } ).add; {a = Synth(\envi)}.defer(0.1); ) a.set(\dur,rrand(0.01,1),\t_gate,1);
ma, siccome non tutti i metodi dedicati agli inviluppi accettano questo parametro, ricordiamo alcune utili conversioni di valori temporali tra le diverse modalità appena esposte:
[1,2,3,4].sum // Tempi delta --> durata totale [1,2,3,4].normalizeSum // Riscala tutti i valori tra 0 e 1 [1,2,3,4].integrate // Tempi delta --> onsets [0]++[1,2,3,4].integrate; // aggiunge il tempo 0 (inizio [0,1,3,6,10].drop(1); // rimuove il primo item (tempo 0) [1,3,6,10].differentiate // Onsets --> tempi delta
Esattamente come abbiamo fatto per le ampiezze possiamo modificare dinamicamente nel tempo tutti i parametri di un invilupo utilizzando le stesse tecniche che già conosciamo.
Client side
La prima consiste nell'inviare dall'Interprete al Server i diversi parametri con il messaggio .set(). Ognuno dei tre parametri principali degli inviluppi ha delle specificità. Vediamole nel dettaglio:
Livelli
Se vogliamo controllare dinamicamente i livelli degli inviluppi inviando i valori dall'Interprete al Synth (Server) abbiamo a disposizione due tecniche (o modi di pensare) differenti, ognuna è propria di una specifica esigenza musicale:
modificare dinamicamente tutti i livelli specificandoli in valori assoluti (così come abbiamo fatto finora):
s.scope(1) s.meter(1,1); ( s.waitForBoot{ // ------------------------------ SynthDef e Synth SynthDef(\envi, {arg t_gate=0,liv=#[0,0.65,0.43,0]; // literal Array... var sig,env; sig = SinOsc.ar(986); env = Env.new(liv,[0.02,0.3,0.2],\cub).kr(0,t_gate); Out.ar(0,sig*env) } ).add; {a = Synth(\envi)}.defer(0.1); // ------------------------------ GUI w = Window.new("EnvelopeView", 500@400); w.alwaysOnTop; w.onClose_({e.free;w.free;r.stop;}); w.front; e = EnvelopeView.new(w, Rect(10,10,480,380)).resize_(5); // ------------------------------ Sequencing r = Routine.new({ inf.do({var liv; liv = [0]++({rand(1.0)}!2)++[0]; // livelli random ad eccezione // del primo e dell'ultimo a.set(\liv,liv,\t_gate,1); // invia al Synth {e.value_([ // invia all'interfaccia [0]++[0.02,0.3,0.2].normalizeSum.integrate, // onsets tra 0 e 1 liv // livelli ]) }.defer(0); 0.6.wait // Deve essere >= della durata dell'inviluppo }) }).reset.play; } ) r.stop;
Due cose da notare:
Nella SynthDef l'Array specificato come argomento di default è un Literal Array.
Se un trigger arriva prima che l'inviluppo sia finito questo riparte dal valore in cui si trova.
modificare dinamicamente solo l'ampiezza di picco lasciando invariata la forma dell'inviluppo. In questo caso dobbiamo specificare tutti i livelli in valori relativi (proporzioni) con 0 come minimo e 1 come massimo per poi moltiplicarli per un fattore di ampiezza così come abbiamo fatto per gli inviluppi con forma d'onda fissa (triangolare, sinusoidale, trapezoidale, etc).
( s.waitForBoot{ // ------------------------------ SynthDef e Synth SynthDef(\envi, {arg t_gate=0,amp=0; var sig,env; sig = SinOsc.ar(1986); env = Env.new([0,1,0.35,0],[0.02,0.02,0.2],\cub).kr(0,t_gate)*amp; // min=0 max=1 * ampiezza Out.ar(0,sig*env) } ).add; {a = Synth(\envi)}.defer(0.1); // ------------------------------ GUI w = Window.new("EnvelopeView", 500@400); w.alwaysOnTop; w.onClose_({e.free;w.free;r.stop;}); w.front; e = EnvelopeView.new(w, Rect(10,10,480,380)).resize_(5); // ------------------------------ Sequencing r = Routine.new({ inf.do({var amp=rand(1.0); a.set(\amp,amp,\t_gate,1); {e.value_([ [0]++[0.02,0.02,0.2].normalizeSum.integrate, [0,1,0.35,0]*amp ]) }.defer(0); 0.6.wait }) }).reset.play; } ) r.stop;
Tempi
Anche per quello che riguarda i cambiamenti dinamici del tempo o dei tempi di un'inviluppo abbiamo a disposizione le stesse due modalità che abbiamo appena visto per i livelli:
modificare dinamicamente tutti i tempi dei singoli nodi specificandoli in valori assoluti (così come abbiamo fatto finora): In questo caso dobbiamo però distinguere tra i diversi tipi di inviluppo:
Inviluppi con i tempi specificati in tempi delta come Env.new() e Env.circle():
( s.waitForBoot{ // ------------------------------ SynthDef e Synth SynthDef(\envi, {arg t_gate = 0, liv = #[0,0.65,0.34,0.78,0], delta = #[0.1,0.3,0.4,0.5]; var sig,env; sig = SinOsc.ar(986); env = Env.new(liv,delta,\cub).kr(0,t_gate); sig = SinOsc.ar(986); Out.ar(0,sig*env) } ).add; {a = Synth(\envi)}.defer(0.1); // ------------------------------ GUI w = Window.new("EnvelopeView", 500@400); w.alwaysOnTop; w.onClose_({e.free;w.free;r.stop;}); w.front; e = EnvelopeView.new(w, Rect(10,10,480,380)).resize_(5); // ------------------------------ Sequencing r = Routine.new({ inf.do({var amp,delta,dur; amp = [0.8,0.3,0.5]; //{rand(1.0)}!3; delta = {rrand(0.01,1)}!4; dur = delta.sum; // Durata totale a.set(\liv,[0]++amp++[0], // invia i valori al Synth \delta, delta, \t_gate, 1 ); {e.value_([ // invia i valori all'interfaccia [0]++delta.normalizeSum.integrate, // onsets tra 0 e 1 [0]++amp++[0] ]) }.defer(0); // AppClock dur.wait }) }).reset.play; } ) r.stop
Inviluppi con i tempi specificati in onsets come Env.pairs() e Env.xyc():
( s.waitForBoot{ // ------------------------------ SynthDef e Synth SynthDef(\envi, {arg t_gate = 0, n1=#[0,0.001], // SynthDef non accetta Array 2D... n2=#[0.2,1], n3=#[0.5,0.6], n4=#[2,0.001]; var sig,bpf,env; sig = SinOsc.ar(986); env = Env.pairs([n1,n2,n3,n4],\cub).kr(0,t_gate); Out.ar(0,sig*env) } ).add; {a = Synth(\envi)}.defer(0.1); // ------------------------------ GUI w = Window.new("EnvelopeView", 500@400); w.alwaysOnTop; w.onClose_({e.free;w.free;r.stop;}); w.front; e = EnvelopeView.new(w, Rect(10,10,480,380)).resize_(5); // ------------------------------ Sequencing r = Routine.new({ inf.do({var amp,tempi,dur,xy,tempi_e; amp = [0,0.7,0.3,0]; tempi = {1.0.rand}!4; // Array di tempi delta tempi = tempi.putFirst(0); // primo = 0 tempi_e = tempi.normalizeSum.integrate; // per visualizzazione tempi = tempi.integrate; // Tempi delta --> onsets dur = tempi.last; // Durata globale xy = [tempi,amp].flop; // Crea Array 2D dei nodi xy.postln; // Stampa a.set( // invia i valori al Synth \n1, xy[0], \n2, xy[1], \n3, xy[2], \n4, xy[3], \t_gate, 1 ); {e.value_([tempi_e, amp])}.defer(0); // invia i valori all'interfaccia dur.wait }) }).reset.play; } ) r.stop
Inviluppi con i tempi specificati in durata totale come Env.triangle(), Env.sine() e Env.perc():
( s.waitForBoot{ // ------------------------------ SynthDef e Synth SynthDef(\envi, {arg t_gate=0, dur=1; var sig,env; sig = SinOsc.ar(986); env = Env.triangle(dur).kr(0,t_gate); Out.ar(0,sig*env) } ).add; {a = Synth(\envi)}.defer(0.1); // ------------------------------ GUI w = Window.new("EnvelopeView", 500@400); w.alwaysOnTop; w.onClose_({e.free;w.free;r.stop;}); w.front; e = EnvelopeView.new(w, Rect(10,10,480,380)).resize_(5); // ------------------------------ Sequencing r = Routine.new({ inf.do({var dur; dur=rrand(0.1,1); a.set(\dur, dur,\t_gate, 1); {e.setEnv(Env.triangle(dur))}.defer(0); // .setEnv() al posto di .value_() dur.wait }) }).reset.play; } ) r.stop
Inviluppi con i tempi specificati in valori legati alle loro caratteristiche come Env.linen(), Env.asr() e Env.adsr():
( s.waitForBoot{ // ------------------------------ SynthDef e Synth SynthDef(\envi, {arg t_gate = 0, atk=0.02, sust=0.5, rel=0.02, amp=0; var sig,env; sig = SinOsc.ar(986); env = Env.linen(atk,sust,rel,amp,\cub).kr(0,t_gate); Out.ar(0,sig*env) } ).add; {a = Synth(\envi)}.defer(0.1); // ------------------------------ GUI w = Window.new("EnvelopeView", 500@400); w.alwaysOnTop; w.onClose_({e.free;w.free;r.stop;}); w.front; e = EnvelopeView.new(w, Rect(10,10,480,380)).resize_(5); // ------------------------------ Sequencing r = Routine.new({ inf.do({var atk,sust,rel,dur,amp; atk = rrand(0.01,0.2); sust= rrand(0.1,0.7); rel = rrand(0.01,0.7); dur = atk+sust+rel; amp = 0.7; a.set( \atk, atk, \sust, sust, \rel, rel, \amp, amp, \t_gate, 1 ); {e.value_([ [0]++[atk,sust,rel].normalizeSum.integrate, [0,amp,amp,0]]) }.defer(0); dur.wait }) }).reset.play; } ) r.stop
- modificare dinamicamente solo la durata totale lasciando invariata la forma dell'inviluppo. In questo caso dobbiamo specificare tutti
i tempi in valori relativi (proporzionali) compresi tra 0 e 1 per poi moltiplicarli per un fattore di durata così come abbiamo fatto per le
ampiezze. In alternativa se i valori specificati per qualche motivo eccedono il range possiamo invocare su un'istanza di Env
il metodo .duration_(n) che riscala dinamicamente le porporzioni temporali nella durata desiderata. Nel caso si debbano specificare i
tempi delta la loro somma deve essere 1 e il metodo .normalizeSum può risultare estremamente utile in quanto riscala i
valori di un Array facendo si che la somma sia 1.
[23,34,65,87].normalizeSum; // Riscalati con somma = 1
Un altra possibile strategia consiste nello specificare i segmenti temporali in valori percentuali (tra 0 e 100):
[5,45,50].normalizeSum; [23,45,50].normalizeSum; // anche se la somma supera 100 la converzione riporta entro 1
Il codice seguente riporta due esempi esemplificativi validi per tutti i tipi di inviluppo, nel primo i valori sono tempi delta e vengono moltiplicati per la durata, il secondo sono espressi in onsets e utilizza il metodo .duration_(dur)
( s.waitForBoot{ var liv,delta,dur; // ------------------------------ SynthDef e Synth SynthDef(\envi, {arg t_gate = 0, liv = #[0,0.65,0.34,0.78,0], delta = #[0.1,0.3,0.4,0.5], // Valori proporzionali dur = 1, // Durata totale amp = 0; var sig,env; sig = SinOsc.ar(986); env = Env.new(liv,delta*dur,\cub).kr(0,t_gate); // Durate dinamiche Out.ar(0,sig*env) } ).add; {a = Synth(\envi)}.defer(0.1); // ------------------------------ GUI w = Window.new("EnvelopeView", 500@400); w.alwaysOnTop; w.onClose_({e.free;w.free;r.stop;}); w.front; e = EnvelopeView.new(w, Rect(10,10,480,380)).resize_(5); liv = [0,1,0.3,0.6,0]; delta = {rrand(0.01,1)}!4; delta = delta.normalizeSum; // tempi delta porporzionali a.set(\liv,liv,\delta, delta,\amp,1); // setta i livelli, i tempi delta e l'ampiezza solo una volta // ------------------------------ Sequencing r = Routine.new({ inf.do({var dur; dur = rrand(0.1,1); dur.postln; a.set(\dur,dur,\t_gate, 1); // durata totale {e.value_([ [0]++delta.integrate, liv ])}.defer(0); dur.wait }) }).reset.play; } ) r.stop // ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------ ( s.waitForBoot{ // ------------------------------ SynthDef e Synth SynthDef(\envi, {arg t_gate = 0,dur=1,amp=0; var sig,env; sig = SinOsc.ar(986); env = Env.pairs([[0,0],[0.01,0.51],[0.03,0.23],[0.12,0.51], [0.33,0.76],[0.59,0.47],[0.84,0.84],[1,0]], \cub ).duration_(dur).kr(0,t_gate)*amp; // metodo .duration_() Out.ar(0,sig*env) } ).add; {a = Synth(\envi)}.defer(0.1); // ------------------------------ GUI w = Window.new("EnvelopeView", 500@400); w.alwaysOnTop; w.onClose_({e.free;w.free;r.stop;}); w.front; e = EnvelopeView.new(w, Rect(10,10,480,380)).resize_(5); e.setEnv(Env.pairs([[0,0],[0.01,0.51],[0.03,0.23],[0.12,0.51], [0.33,0.76],[0.59,0.47],[0.84,0.84],[1,0]], \cub)); // ------------------------------ Sequencing r = Routine.new({ inf.do({var dur; dur = rrand(1,5); dur.postln; a.set(\dur,dur,\amp,0.7,\t_gate, 1); dur.wait }) }).reset.play; } ) r.stop
Curve
Per quanto riguarda le curve possiamo modificare dinamicamente:
Un solo tipo di curva per tutti i segmenti:
( s.waitForBoot{ // ------------------------------ SynthDef e Synth SynthDef(\envi, {arg freq=890,t_gate=0,curva=1.0; var sig,env; sig = SinOsc.ar(freq); env = Env.new([0,1,0.3,0],[0.01,0.2,0.3],curva).kr(0,t_gate); Out.ar(0,sig*env) } ).add; {a = Synth(\envi)}.defer(0.1); // ------------------------------ GUI w = Window.new("EnvelopeView", 500@400); w.alwaysOnTop; w.onClose_({e.free;w.free;r.stop;}); w.front; e = EnvelopeView.new(w, Rect(10,10,480,380)).resize_(5); // ------------------------------ Sequencing r = Routine({ inf.do({var curva; curva = rand2(10.0); curva.postln; a.set(\curva, curva,\t_gate,1); {e.setEnv(Env.new([0,1,0.3,0],[0.01,0.2,0.3],curva))}.defer(0); 0.6.wait; }) }).reset.play; } ) r.stop;
Un tipo diverso di curva per ogni segmento:
( s.waitForBoot{ // ------------------------------ SynthDef e Synth SynthDef(\envi, {arg freq=890,t_gate=0,curva=1.0; var sig,env; sig = SinOsc.ar(freq); env = Env.new([0,1,0.3,0],[0.01,0.2,0.3],curva).kr(0,t_gate); Out.ar(0,sig*env) } ).add; {a = Synth(\envi)}.defer(0.1); // ------------------------------ GUI w = Window.new("EnvelopeView", 500@400); w.alwaysOnTop; w.onClose_({e.free;w.free;r.stop;}); w.front; e = EnvelopeView.new(w, Rect(10,10,480,380)).resize_(5); // ------------------------------ Sequencing r = Routine({ inf.do({var curve; curve = 3.collect({rand2(10)}); curve.postln; a.set(\curva, curve,\t_gate,1); {e.setEnv(Env.new([0,1,0.3,0],[0.01,0.2,0.3],curve))}.defer(0); 0.6.wait; }) }).reset.play; } ) r.stop;
Env.newClear()
Un'altra tecnica che possiamo utilizzare per modificare dinamicamente nel tempo un inviluppo consiste nel crearne uno vuoto con un numero predefinito di nodi e passargli successivamente tutti i valori in modo dinamico modificandone la forma. L'unico limite è dato dal fatto che il numero di nodi non è modificabile dinamicamente.
creiamo un monitor visivo:
( w = Window("EnvelopeView", 250@250); // Crea una finestra e = EnvelopeView(w, 250@250); // Crea un EnvelopeWiev e.drawLines_(true); // Visualizza le linee tra i nodi e.drawRects_(true); // Visualizza i punti dei nodi e.thumbSize_(7); // Cambia il size dei nodi e.strokeColor_(Color.black); // Colore delle connessioni e.fillColor_(Color.black); // Colore dei nodi e.selectionColor_(Color.blue); // Colore della selezione e.gridColor_(Color.gray(0.8)); // Colore della cornice e.resize_(5); // Permette il resize della finestra e.step_(0.01); // Risoluzione in decimali e.action_({arg b; [b.index, b.value].postln}); // Stampa nella Post window le mo- // difiche del mouse w.front; // Visualizza la finestra w.onClose = {e.free}; // Libera la variabile 'e' quando si chiude la fi- )
creiamo un'istanza di Env con il metodo .newClear formata da n nodi senza specificarne i valori. Questo metodo accetta come argomento il numero di nodi. N.B. non funziona con il multicanale.
( SynthDef(\envi, {arg t_trig=0, freq=600, dur=1; var env, bpf,senv,sig; env = Env.newClear(4); // Crea un inviluppo vuoto di 4 nodi bpf = \env.kr(env.asArray); // Crea un controllo dell'inviluppo senv= EnvGen.kr(bpf, t_trig); // Genera l'inviluppo sig = SinOsc.ar(freq); Out.ar(0,sig*senv) } ).add; {a = Synth(\envi)}.defer(0.1); )
generiamo gli inviluppi che vogliamo per poi inviarli al controllo con il metodo .set()
( x = Env.new([0,rand(1.0),rand(1.0),0], // Crea un Env {rrand(0.01, 0.5)}!3, // durata totale: val+val+val \cub); a.set(\env, x, \t_trig,1); // lo invia e.setEnv(x) // lo visualizza sull'interfaccia )
Nel caso precedente la durata globale dell'inviluppo è data dalla somma dei valori dei tre segmenti e va da un minimo di 0.03 a un massimo di 1.5. Se invece volessimo specificare una durata globale e scegliere i valori dei singoli segmenti all'interno di questa, possiamo utilizzare il metodo .duration_() già illustrato in precedenza.
( d = 0.02; // Definiamo una durata y = {rand(0.5)}!3; // Creiamo un Array di 3 valori random x = Env.new([0.001,rand(1.0),rand(1.0),0.001],y,\cub).duration_(d); // riscaliamo a.set(\env,x, \t_trig,1); // Inviamolo al Synth e.setEnv(x) // Visualizzazione dul GUI )
Infine vediamo come sia possibile creare un database di inviluppi e richiamarli dinamicamente uno alla volta.
( var env; env = [Env.perc(0.1,1,0.3), Env.perc(1,0.1,0.7), Env.perc(0.3,0.5,0.4), Env.perc(0.9,0.01,0.7), Env.perc(0.01,0.2,0.9), ].choose; a.set(\env,env,\t_trig,1); e.setEnv(env) )
Interazione e GUI
MIDI
Se vogliamo modificare dinamicamente i diversi parametri degli inviuppi interagendo con devices esterni o GUI dobbiamo:
stabilire quali sono i parametri sonori o musicali (argomenti della SynthDef) che vogliamo poter controllare dinamicamente interagendo con l'interfaccia e che nel caso degli inviluppi potrebbero essere:
- Trigger (gate:0 e gate:1)
- Ampiezza di picco
- Durata
- Curva
- I valori xy dei singoli nodi
- ...
mettere in relazione (mappare) i dati provenienti da un singolo slider (o bottone o knob o altro cc) con uno dei parametri scelti nel punto precedente, separandolo dai dati provenienti dagli altri. Per farlo dobbiamo innanzitutto monitorare su quali canali MIDI o control number (CC) arrivano i valori dei diversi fader (o knob o bottoni) nel modo che già conosciamo:
MIDIIn.connectAll; // 1 Connettere tutti i devices MIDI MIDIFunc.trace(true); // 2 Stampare tutti i messaggi midi in entrata per verificare il numero // del controller (num:) di due o più slider MIDIFunc.trace(false); // 3 spegnere il monitoraggio
Dopodichè invece che ricevere i valori provenienti da tutti i canali e da tutti i controller come abbiamo fatto in precedenza:
MIDIdef.cc(\mioK1, {arg ...args; args.postln})
Possiamo separarli e filtrarli specificando due ulteriori argomenti di MIDIdef.cc():
MIDIdef.cc(\test1, {arg ...args; args.postln}, 1); // riceve solo i valori dal cc 1 MIDIdef.cc(\test2, {arg ...args; args.postln}, 1, 1); // riceve solo i valori dal cc 1 e dal canale 1
A questo punto possiamo creare una MIDIdef.cc() per ogni slider (knob, etc.) che vogliamo utilizzare, specificando questi argomenti con i valori monitorati in precedenza.
I codici seguenti illustrano tre esempi significativi di questa modalità di controllo dei parametri degli inviluppi. I primi due riguardano i due diversi tipi di inviluppo (con e senza sfase di sostegno) mentre il terzo la modularità di questo processo e rappresenta un semplice esempio di live sequencing.
Inviluppi senza fase di sostegno
s.scope(1); s.meter(1,1); ( s.waitForBoot{ // ------------------------------ SynthDef e Synth SynthDef(\no_sust, {arg t_gate=0,amp=0,dur=1,curva=1; var sig,env; sig = SinOsc.ar(1970); env = Env.new([0,1,0.3,0],[0.01,0.2,1], // tempi, livelli K2A.ar(curva).lag(0.2) // curva ).duration_(K2A.ar(dur).lag(0.02)) // durata globale .kr(0,t_gate)* // trigger K2A.ar(amp).lag(0.02); // ampiezza Out.ar(0,sig*env) } ).add; {a = Synth(\no_sust)}.defer(0.1); // ------------------------------ GUI w = Window.new("Controlli", 200@230); w.onClose_({a.free;e.free}).front.alwaysOnTop; MIDIIn.connectAll; c = CheckBox.new(w,Rect(77, 10)).action_({arg i; a.set(\t_gate,i.value.asInt)}); d = StaticText.new(w,Rect(95,10, 50, 15)).string = "Gate"; MIDIdef.cc(\mioK1, {arg val; {c.valueAction_(val/127)}.defer(0)},41); // cc number 41 (Play) e = Slider.new(w,Rect(10, 50, 50, 170)).action_({arg i; a.set(\amp, i.value)}); f = StaticText.new(w,Rect(22, 30, 50, 15)).string = "Amp"; MIDIdef.cc(\mioK2, {arg val; {e.valueAction_(val/127)}.defer(0)},0); g = Slider.new(w,Rect(75, 50, 50, 170)).action_({arg i;a.set(\dur, i.value*0.9+0.1)}); h = StaticText.new(w,Rect(77, 30, 50, 15)).string = "Tempo"; MIDIdef.cc(\mioK3, {arg val; {g.valueAction_(val/127)}.defer(0)},1); i = Slider.new(w,Rect(135, 50, 50, 170)).action_({arg i;a.set(\curva, i.value*10+10)}); // da -5 a +5 l = StaticText.new(w,Rect(140, 30, 50, 15)).string = "Curva"; MIDIdef.cc(\mioK4, {arg val; {i.valueAction_(val/127)}.defer(0)},2); } )
Inviluppi con fase di sostegno
// N.B. le durate non possono essere riscalate ( s.waitForBoot{ // ------------------------------ SynthDef e Synth SynthDef(\sust, {arg gate=0,amp=0,dur=1,curva=1; var sig,env; sig = SinOsc.ar(1970); env = Env.new([0,1,0.3,0],[0.01,0.2,1], K2A.ar(curva).lag(0.2),2 ).kr(0,gate)* K2A.ar(amp).lag(0.02); Out.ar(0,sig*env) } ).add; {a = Synth(\sust)}.defer(0.1); // ------------------------------ GUI w = Window.new("Controlli", 135@230); w.onClose_({a.free;e.free}).front.alwaysOnTop; MIDIIn.connectAll; c = CheckBox.new(w,Rect(50, 10)).action_({arg i; a.set(\gate,i.value.asInt)}); d = StaticText.new(w,Rect(70,10, 50, 15)).string = "Note"; MIDIdef.cc(\mioK1, {arg val; {c.valueAction_(val/127)}.defer(0)},41); // cc number 41 (Play) e = Slider.new(w,Rect(10, 50, 50, 170)).action_({arg i; a.set(\amp, i.value)}); f = StaticText.new(w,Rect(22, 30, 50, 15)).string = "Amp"; MIDIdef.cc(\mioK2, {arg val; {e.valueAction_(val/127)}.defer(0)},0); g = Slider.new(w,Rect(75, 50, 50, 170)).action_({arg i;a.set(\curva, i.value*10+10)}); h = StaticText.new(w,Rect(77, 30, 50, 15)).string = "Curva"; MIDIdef.cc(\mioK3, {arg val; {g.valueAction_(val/127)}.defer(0)},1); } )
Live sequencing
( s.waitForBoot{ // ------------------------------ SynthDef e Synth SynthDef(\no_sust, {arg t_gate=0,amp=0,dur=1,curva=1; var sig,env; sig = SinOsc.ar(1970); env = Env.new([0,1,0.3,0],[0.01,0.2,1], // tempi, livelli K2A.ar(curva).lag(0.2) // curva ).duration_(K2A.ar(dur).lag(0.02)) // durata globale .kr(0,t_gate)* // trigger K2A.ar(amp).lag(0.02); // ampiezza Out.ar(0,sig*env) } ).add; {a = Synth(\no_sust)}.defer(0.1); // ------------------------------ GUI w = Window.new("Controlli", 200@230); w.onClose_({a.free;e.free;r.free}).front.alwaysOnTop; MIDIIn.connectAll; c = CheckBox.new(w,Rect(77, 10)).action_({arg i; if(i.value == true, {r.reset.play},{r.stop}) }); d = StaticText.new(w,Rect(95,10, 100, 15)).string = "Play Seq"; MIDIdef.cc(\mioK1, {arg val; {c.valueAction_(val/127)}.defer(0)},41); // cc number 41 (Play) e = Slider.new(w,Rect(10, 50, 50, 170)).action_({arg i; a.set(\amp, i.value)}); f = StaticText.new(w,Rect(22, 30, 50, 15)).string = "Amp"; MIDIdef.cc(\mioK2, {arg val; {e.valueAction_(val/127)}.defer(0)},0); g = Slider.new(w,Rect(75, 50, 50, 170)).action_({arg i;a.set(\dur, i.value*0.9+0.1)}); h = StaticText.new(w,Rect(77, 30, 50, 15)).string = "Tempo"; MIDIdef.cc(\mioK3, {arg val; {g.valueAction_(val/127)}.defer(0)},1); i = Slider.new(w,Rect(135, 50, 50, 170)).action_({arg i;a.set(\curva, i.value*10+10)}); // da -5 a +5 l = StaticText.new(w,Rect(140, 30, 50, 15)).string = "Curva"; MIDIdef.cc(\mioK4, {arg val; {i.valueAction_(val/127)}.defer(0)},2); // ------------------------------ Sequencing r = Routine.new({ inf.do({ a.set(\t_gate,rand(2)); 0.1.wait; }) }); } )