Slicing
Una tecnica classica di elaborazione di un segnale memorizzato in un Buffer consiste nel destrutturare la sequenza temporale lineare "spezzettandolo" in frammenti (window) di varia lunghezza per poi ricomporli adottando diverse strategie.
I parametri che possiamo controllare sono:
- posizione delle finestre (onsets) ovvero il punto all'interno del Buffer dove comincia la lettura. Questo può essere specificato in secondi, in frames o in un fattore di moltiplicazione della durata del Buffer compreso tra 0.0 e 1.0) per poi essere riscalato nell'unità di misura accettata dalla UGen.
- durata delle finestre (in secondi).
- inviluppo d'ampiezza da applicare alla finestra (in genere normalizzato a 1).
- ampiezza di picco da applicare alla finestra (tra 0 e 1).
- trasposizione. Fattore di velocità di lettura della finestra (in valori Midi: +/- 1 = un semitono).
- direzione della lettura (recto = 1, verso = -1).
- panning. Posizione nel fronte stereofonico o multicanale (sinistra = -1, centro = 0, destra = 1).
La tipologia di Synth che meglio si presta a questa tecnica è quella del Sample player oscillattor.
Singolo evento
In questo caso possiamo specificare tutti i parametri come argomenti e controllarli attraverso la semplice valutazione del codice come negli esempi seguenti, attraverso GUI o comandi del mouse, utilizzando i tasti del computer oppure messaggi MIDI o OSC.
s.boot; s.plotTree; s.meter; ( Buffer.freeAll; b = Buffer.read(s,"Bach.wav".resolveRelative); SynthDef(\sli1, {arg buf=0,pos=0,dur=0.2,amp=0,trsp=1,dir=1,pan=0,t_gate=0,done=2; var sig,bpf,env,pann; sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf) * trsp.midiratio // Trasposizione... * dir, // ...e direzione t_gate, // Trigger BufSampleRate.kr(buf)* pos // Posizione ); bpf = Env.linen(0.01,dur-0.02,0.01); env = EnvGen.kr(bpf,t_gate,doneAction:done); pann = Pan2.ar(sig*env*amp,pan); Out.ar(0,pann) }).add; )
La trasposizione è resa dinamica specificando la deviazione del pitch in semitoni MIDI convertiti automaticamente in velocità di lettura con il metodo .midiratio e riscalati su eventuali differenze tra la rata di campionamento con la quale sono stati registrati i suoni memorizzati nel Buffer e quella utilizzata dal sistema in fase di rilettura moltipicandoli per un valore ottenuto con la UGen BufRateScale.kr(buf).
La direzione della lettura è specificata attraverso due fattori di moltiplicazione della velocità di lettura: 1 = recto, -1 = verso.
Il trigger è sincronizzato con l'inviluppo.
La posizione (onset) in questo caso è specificata in secondi e poi convertita in frames moltiplicandone il valore per la rata di campionamento del Buffer ottenuta attraverso la UGen BufSampleRate.kr(buf). Se non conosciamo la lunghezza del Buffer in secondi possiamo specificare le posizioni in fattori di moltiplicazione compresi tra 0.0 e 1.0 per poi riscalarli sulla durata del Buffer (0 = inizio, 1 = fine) ottenuta automaticamente con BufDur.kr()
Valori deterministici
// Synth Polifonico ( Synth(\sli1,[\buf,b, \pos, 0.5, \dur, 0.2, \amp,1, \trsp, 1, \dir, 1, \pan, 1, \done,2, // Autodistruzione \t_gate,1 ]) ) // Synth Monofonico a = Synth(\sli1); ( a.set(\buf,b, \pos, 0.5, \dur, 0.2, \amp,1, \trsp, 1, \dir, 1, \pan, -1, \done,0, // No autodistruzione \t_gate,1) ) a.free;
Valori non deterministici
// Synth Polifonico ( ~dur = rrand(0.1,0.8); // va calcolata prima per sottrarre il valore alla scelta della posizione Synth(\sli1,[\buf,b, \pos, rrand(0,b.duration-~dur), // tra 0 e la lunghezza del Buffer - durata \dur, ~dur, \amp,rand(1.0), \trsp, rand2(4.0), // +/- due toni (float = non temperato) \dir, [1,-1].choose, \pan, rand2(1.0), \done,2, \t_gate,1 ]) ) // Synth Monofonico a = Synth(\sli1); ( ~dur = rrand(0.1,0.8); a.set(\buf,b, \pos, rrand(0,b.duration-~dur), \dur, ~dur, \amp,rand(1.0), \trsp, rand2(4.0), \dir, [1,-1].choose, \pan, rand2(1.0), \done,0, \t_gate,1) ) a.free;
Flusso dinamico
Per ottenere flussi dinamici di eventi possiamo impiegare sia tecniche di sequencing (Client side) che segnali di controllo (Server side).
Client side
In questo caso possiamo utilizzare lo stesso Synth che abbiamo programmato in precedenza.
( Buffer.freeAll; b = Buffer.read(s,"Bach.wav".resolveRelative); SynthDef(\sli1, {arg buf=0,pos=0,dur=0.2,amp=0,trsp=1,dir=1,pan=0,t_gate=0,done=2; var sig,bpf,env,pann; sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf) * trsp.midiratio // Trasposizione... * dir, // ...e direzione t_gate, // Trigger BufSampleRate.kr(buf)* pos // Posizione ); bpf = Env.linen(0.01,dur-0.02,0.01); env = EnvGen.kr(bpf,t_gate,doneAction:done); pann = Pan2.ar(sig*env*amp,pan); Out.ar(0,pann) }).add; )
- Sequenze deterministiche
// Sequenza... ~nfrag = 8; // Numero di frammenti ~pos = ~nfrag.collect{rrand(0.02,0.7)}.round(0.01); ~dur = ~nfrag.collect{rrand(0.02,0.7)}.round(0.01); ~amp = ~nfrag.collect{rand(1.0)}.round(0.01); ~del = ~nfrag.collect{rrand(0.5,3.5)}.round(0.01); ~dir = ~nfrag.collect{[1,-1].choose}.round(0.01); ( ~pos = [5.82,9.11,9.76,0.39,9.21,2.85,2.31,0.05]; ~dur = [1.40,0.46,1.18,2.39,0.69,1.15,3.41,1.55]; ~amp = [0.40,0.65,0.85,1.00,0.70,0.50,0.30,0.10]; ~del = [1.48,0.54,2.41,1.26,3.07,0.89,1.16,0.89]; ~dir = [1, -1, -1, 1, 1, -1, 1, 1 ]; ~pan = [-1,-0.8,-0.6,-0.4, 0, 0.4,0.6, 1 ]; r = Routine.new({ ~del.do({arg i,id; Synth(\sli1, [\buf,b, \pos, ~pos[id], \dur, ~dur[id], \amp, ~amp[id], \trsp, 0, \dir,~dir[id], \pan,~pan[id], \done,2, \t_gate,1 ]); i.wait }) }).play ) // Time stretching... ( ~nfrag = 20; // Numero di frammenti ~dur = b.duration/~nfrag; // durata del singolo frammento ~pos = 0; // posizione iniziale ~pausa = -0.1; // se > 0 = pausa se < 0 = sovrapposizione (time stretching) r = Routine.new({ ~nfrag.do({ Synth(\sli1, [\buf,b, \pos, ~pos.postln, \dur, ~dur, \amp,1, //rand(1.0), \trsp,0, //rand2(3), \dir,1, // [1,-1].choose \pan,0, \done,2, \t_gate,1 ]); ~pos = ~pos+~dur; // incremento della posizione ad ogni loop (~dur+~pausa).wait }) }).play )
- Sequenze non deterministiche
// Flash... ( r = Routine.new({ inf.do({ Synth(\sli1, [\buf,b, \out, rand(2), \pos,rand(b.duration)-1, \dur,rrand(0.05,0.3), \amp,rand(1.0), \trsp,rand2(5), \dir, [1,-1].choose, \pan,rand2(1.0), \done,2, \t_gate,1 ]); rrand(0.05,1).wait }) }).reset.play ) // Fisarmonica... ( r = Routine.new({ inf.do({var dt = [0.1,0.5,1].choose; Synth(\sli1, [\buf,b, \pos,[1,2,4].choose, \dur,dt, \amp,0.4, \dir, [1,-1].choose, \pan,rand2(1.0), \done,2, \t_gate,1 ]); dt.wait }) }).reset.play )
Server side
Un altro approccio consiste nel programmare non le singole finestre (o singoli eventi musicali) ma interi processi o sequenze controllando dall'Interprete solamente parametri di alto livello. Per farlo dobbiamo programmare un nuovo Synth che può essere solo monofonico.
s.boot; s.plotTree; s.meter; ( Buffer.freeAll; b = Buffer.read(s,"Bach.wav".resolveRelative); SynthDef(\sli2, {arg buf=0, freq=1, // Frequenza del trigger pos=#[0,0.98], // Valori limite pausa=#[0,0], amp=#[1,1], trsp=#[0,0], dir=#[1,1], pan=#[-1.0,1], gate=0; // Fade in/out dell'intera sequenza var trig,pau,dur,onset,tsp,dire,sig,bpf,env,amps,fade,out,pann; trig = Impulse.kr(freq.lag(0.2)); // Trigger pau = 1-TRand.kr(pausa[0],pausa[1],trig); // Pause dur = 1/freq * pau; // Durata onset = TRand.kr(pos[0],pos[1],trig) * BufFrames.kr(buf); // Provare con Stepper.ar tsp = TRand.kr(trsp[0],trsp[1],trig); dire = TRand.kr(dir[0],dir[1],trig); sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf) * tsp.midiratio // Trasposizione... * dire, // ...e direzione trig, // Trigger onset // Posizione ); bpf = Env.linen(0.01,dur-0.02,0.01); env = EnvGen.kr(bpf,trig); amps = TRand.kr(amp[0],amp[1],trig); fade = Linen.kr(gate,0.01,1,2,doneAction:2); out = sig * env * amps.lag(0.2) * fade; pann = Pan2.ar(out, TRand.kr(pan[0],pan[1],trig).varlag(1/freq)); // Panning lento Out.ar(0,pann) }).add; ) a = Synth(\sli2,[\buf,b,\gate,1]); a.set(\pausa,#[0.1,0.5]); a.set(\trsp,#[-4, 4]); a.set(\pos,#[0.4,0.4]); // Specificando due valori uguali sarà deterministico... a.set(\freq,2); a.set(\amp, #[0.2,1]); a.set(\pan, #[-1,1]); a.set(\gate,0); b = Synth(\sli2,[\buf,b,\gate,1]); // Eventuale seconda voce...
Nell'esempio precedenti i parametri sono specificati in modo non deterministico ma possiamo ad esempio sostituire le UGens TRand.kr con altre che generano valori deterministici sottocampionate attraverso tecniche di sample and hold oppure adottare una qualsiasi tecnica di triggering illustrata nel paragrafo dedicato ai segnali di controllo.
Enveloping
Questa tecnica consiste nel modificare l'inviluppo d'ampiezza originale di un suono applicandone uno nuovo in fase di riproduzione stravolgendone le caratteristiche dinamiche.
Singolo evento
Inviluppo costante. Nel paragrafo dedicato alle tecniche di windowing abbiamo già visto come applicare un inviluppo costante (sempre uguale) a qualsiasi alla rilettura di un Buffer o di una porzione di esso. Il solo parametro che cambierà sarà la durata.
s.boot; s.plotTree; s.meter; ( Buffer.freeAll; b = Buffer.read(s,"Bach.wav".resolveRelative); SynthDef(\sli1, {arg buf=0,pos=0,dur=0.2,amp=0,trsp=0,dir=1,pan=0,t_gate=0,done=2; var sig,bpf,env,pann; sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf) * trsp.midiratio * dir, t_gate, BufSampleRate.kr(buf)* pos ); bpf = Env.perc(0.01,dur-0.01); // Cambiare tipologia inviluppo env = EnvGen.kr(bpf,t_gate,doneAction:done); pann = Pan2.ar(sig*env*amp,pan); Out.ar(0,pann) }).add; ) Synth(\sli1, [\buf,b,\amp,rand(1.0),\pos,rand(4.0),\done,2,\dur,rrand(0.1,1).postln,\t_gate,1]); // Polifonico a = Synth(\sli1, [\buf,b,\amp,1,\done,0]); // Monofonico a.set(\dur,rrand(0.1,1).postln,\t_gate,1);
Come specificato nel paragrafo dedicato possiamo modificare dinamicamente attraverso argomenti i parametri propri dell'inviluppo scelto (ad esempio nel caso precedente oltre la durata anche il tempo di attacco e decadimento).
( Buffer.freeAll; b = Buffer.read(s,"Bach.wav".resolveRelative); SynthDef(\sli1, {arg buf=0,pos=0,dur=0.2,amp=0,trsp=0,dir=1,pan=0,t_gate=0,atk=0.01,done=2; var sig,bpf,env,pann; sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf) * trsp.midiratio * dir, t_gate, BufSampleRate.kr(buf)* pos ); bpf = Env.perc(dur*atk,dur-(dur*atk)); // tempo attacco dinamico env = EnvGen.kr(bpf,t_gate,doneAction:done); pann = Pan2.ar(sig*env*amp,pan); Out.ar(0,pann) }).add; ) Synth(\sli1, [\buf,b,\amp,1,\pos,rand(4.0),\done,2,\dur,0.5,\atk,rrand(0.1,0.9),\t_gate,1]); // Polifonico a = Synth(\sli1, [\buf,b,\amp,1,\done,0]); // Monofonico a.set(\dur,0.5,\atk,rrand(0.01,0.9),\t_gate,1);
Inviluppo dinamico. In questo caso andiamo a modificare ad ogni singolo evento non soli i parametri ma la tipologia di inviluppo come illustrato nel paragrafo dedicato
( w = Window("EnvelopeView", 250@250); e = EnvelopeView(w, 250@250).drawLines_(true).drawRects_(true); w.front; w.onClose = {e.free;w.free;d.free;f.free}; Buffer.freeAll; b = Buffer.read(s,"Bach.wav".resolveRelative); SynthDef(\sli1, {arg buf=0,pos=0,amp=0,trsp=0,dir=1,pan=0,t_gate=0,done=2; var sig,envi,bpf,env,pann; sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf) * trsp.midiratio * dir, t_gate, BufSampleRate.kr(buf)* pos ); envi = Env.newClear(4); // Crea un inviluppo vuoto di 4 nodi bpf = \env.kr(envi.asArray); // Crea un controllo dell'inviluppo env = EnvGen.kr(bpf,t_gate,doneAction:done); pann = Pan2.ar(sig*env*amp,pan); Out.ar(0,pann) }).add; ) ( d = 0.5; // Definiamo una durata f = [Env.perc(0.1,1).duration_(d), Env.triangle(d), Env.sine(d), Env.linen(0.1,1,0.7).duration_(d) ].choose; // ne sceglie uno a caso Synth(\sli1, [\buf,b, \amp,1, \pos,1, \done,2, \env, f, \t_gate,1]); // Polifonico e.setEnv(f) // lo visualizza sull'interfaccia )
Flusso dinamico
Per ottenere flussi dinamici di eventi possiamo anche in questo caso impiegare sia tecniche di sequencing (Client side) che segnali di controllo (Server side).
Client side
In questo caso possiamo utilizzare lo stesso Synth che abbiamo programmato in precedenza.
( w = Window("EnvelopeView", 250@250); e = EnvelopeView(w, 250@250).drawLines_(true).drawRects_(true); w.front; w.onClose = {e.free;w.free;d.free;y.free;x.free}; Buffer.freeAll; b = Buffer.read(s,"Bach.wav".resolveRelative); SynthDef(\sli1, {arg buf=0,pos=0,amp=0,trsp=0,dir=1,pan=0,t_gate=0,done=2; var sig,envi,bpf,env,pann; sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf) * trsp.midiratio * dir, t_gate, BufSampleRate.kr(buf)* pos ); envi = Env.newClear(4); // Crea un inviluppo vuoto di 4 nodi bpf = \env.kr(envi.asArray); // Crea un controllo dell'inviluppo env = EnvGen.kr(bpf,t_gate,doneAction:done); pann = Pan2.ar(sig*env*amp,pan); Out.ar(0,pann) }).add; )
- Sequenze deterministiche
( ~durs = [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9]; ~pos = [0.1,0.3,0.5,0.7,0.9,1.1,1.3,1.5,1.7]; ~envs = [Env.perc(0.1,0.9), Env.perc(0.2,0.8), Env.perc(0.3,0.7), Env.perc(0.4,0.6), Env.perc(0.5,0.5), Env.perc(0.6,0.4), Env.perc(0.7,0.3), Env.perc(0.8,0.2), Env.perc(0.9,0.1), ]; r = Routine.new({ ~durs.do({arg item,id; Synth(\sli1, [\buf,b, \amp,1, \pos, ~pos.at(id), \done,2, \env, ~envs.at(id).duration_(~durs.at(id)), \t_gate,1]); {e.setEnv(~envs.at(id))}.defer; // AppClock... item.wait }) }).play )
- Sequenze non deterministiche
( ~envs = 5.collect({Env.perc(rrand(0.01,0.1),rrand(0.1,0.2))}); r = Routine.new({ inf.do({var envi; envi = ~envs.choose; Synth(\sli1, [\buf,b, \amp,1, \pos, rand(4.0), \done,2, \env, envi.duration_(rrand(0.01,0.5)), \t_gate,1]); {e.setEnv(envi)}.defer; // AppClock... rrand(0.01,0.4).wait }) }).play )
Server side
Attraverso questa tecnica possiamo generare flussi sonori composti da finestre con inviluppi d'ampiezza che cambiano ad ogni trigger. Due esempi.
Invilppi custom con parametri che cambiano ad ogni trigger.
( Buffer.freeAll; b = Buffer.read(s,"Bach.wav".resolveRelative); SynthDef(\sli2, {arg buf=0, freq=1, // Frequenza del trigger pos=#[0,0.98], // Valori limite pausa=#[0,0], amp=#[1,1], trsp=#[0,0], dir=#[1,1], pan=#[-1.0,1], gate=0; var trig,pau,dur,onset,tsp,dire,sig,env,amps,fade,out,pann; trig = Impulse.kr(freq.lag(0.2)); pau = 1-TRand.kr(pausa[0],pausa[1],trig); dur = 1/freq * pau; onset = TRand.kr(pos[0],pos[1],trig) * BufFrames.kr(buf); tsp = TRand.kr(trsp[0],trsp[1],trig); dire = TRand.kr(dir[0],dir[1],trig); sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf) * tsp.midiratio * dire, trig, onset ); env = EnvGen.kr(Env.new([0,TRand.kr(0,1.0,trig),TRand.kr(0,1.0,trig),0], [TRand.kr(0.01,0.02,trig), TRand.kr(0.01,0.21,trig), TRand.kr(0.01,0.1,trig)]).duration_(dur), trig); amps = TRand.kr(amp[0],amp[1],trig); fade = Linen.kr(gate,0.01,1,2,doneAction:2); out = sig * env * amps.lag(0.2) * fade; pann = Pan2.ar(out, TRand.kr(pan[0],pan[1],trig).varlag(1/freq)); Out.ar(0,pann) }).add; ) a = Synth(\sli2,[\buf,b,\gate,1]); a.set(\pausa,#[0.1,0.5]); a.set(\trsp,#[-4, 4]); a.set(\pos,#[0.4,0.4]); a.set(\freq,2); a.set(\amp, #[0.2,1]); a.set(\pan, #[-1,1]); a.set(\gate,0);
Array di invilppi all'interno del quale scegliere ad ogni trigger.
( Buffer.freeAll; b = Buffer.read(s,"Bach.wav".resolveRelative); SynthDef(\sli2, {arg buf=0, freq=1, // Frequenza del trigger pos=#[0,0.98], // Valori limite pausa=#[0,0], amp=#[1,1], trsp=#[0,0], dir=#[1,1], pan=#[-1.0,1], gate=0; var trig,pau,dur,onset,tsp,dire,sig,env,amps,fade,out,pann; trig = Impulse.kr(freq.lag(0.2)); pau = 1-TRand.kr(pausa[0],pausa[1],trig); dur = 1/freq * pau; onset = TRand.kr(pos[0],pos[1],trig) * BufFrames.kr(buf); tsp = TRand.kr(trsp[0],trsp[1],trig); dire = TRand.kr(dir[0],dir[1],trig); sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf) * tsp.midiratio * dire, trig, onset ); env = Select.kr(TIRand.kr(0,3,trig), // quale Inviluppo tra [EnvGen.kr(Env.perc(0.01,1).duration_(dur),trig), EnvGen.kr(Env.triangle(dur),trig), EnvGen.kr(Env.sine(dur),trig), EnvGen.kr(Env.linen(0.01,1,0.3).duration_(dur),trig)]); amps = TRand.kr(amp[0],amp[1],trig); fade = Linen.kr(gate,0.01,1,2,doneAction:2); out = sig * env * amps.lag(0.2) * fade; pann = Pan2.ar(out, TRand.kr(pan[0],pan[1],trig).varlag(1/freq)); Out.ar(0,pann) }).add; ) a = Synth(\sli2,[\buf,b,\gate,1]); a.set(\gate,0);
Looping
Le diverse tecniche di looping permettono di iterare la lettura del contenuto di un intero Buffer o di una o più parti di esso in modo continuo. Per questa loro peculiarità dobbiamo programmare un segnale di controllo che gestisca il flusso sonoro risultante all'interno del Synth. A seconda delle diverse esigenze musicali ne sceglieremo una o l'altra.
Looping di un intero Buffer
Per eseguire in loop il contenuto di un intero Buffer abbiamo due possibilità:
Se il sound file caricato nel Buffer non ha discontinuità all'inizio e alla fine applichiamo un fade in e un fade out all'inizio e alla fine dell'intero flusso sonoro ma non tra le singole riletture. L'iterazione è controllata dall'argomento loop:1 di PlayBuf.ar().
s.boot; s.plotTree; s.meter; ( Buffer.freeAll; b = Buffer.read(s,"Bach.wav".resolveRelative); SynthDef(\looper_1, {arg buf=0,amp=0,trsp=0,dir=1,fIn=0.2,fOut=1,pan=0,done=2; var sig,fade,pann; sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf)*trsp.midiratio*dir, 1, // Play loop:1 // Aggiungiamo semplicemente questo argomento ); fade = Linen.kr(gate,fIn,amp,fOut,done); // inviluppo generale pann = Pan2.ar(sig*amp*fade,pan); Out.ar(0,pann) }).add; ) a = Synth(\looper_1, [\buf,b,\amp,1,\gate,1]); a.set(\trsp,rrand(0.5,4)); a.set(\gate,0); a.release(5); // Uguale al precedente specifica il tempo di fade out
Se il sound file caricato nel Buffer ha discontinuità all'inizio e alla fine oppure se vogliamo scriverne dinamicamente il contenuto (live) dobbiamo aggiungere un inviluppo d'ampiezza trapezoidale ad ogni lettura e sincronizzarne il trigger.
In questo caso dobbiamo anche recuperare dinamicamente la durata del Buffer ed eventualmente riscalarla sul fattore di trasposizione
( Buffer.freeAll; b = Buffer.read(s,"Bach.wav".resolveRelative); SynthDef(\looper_2, {arg buf=0,amp=0,trsp=0,dir=1,fIn=0.2,fOut=1,pan=0,gate=0,done=2; var dur,trig,sig,bpf,env,fade,pann; dur = BufDur.kr(buf) * trsp.midiratio; // durata del Buffer (sec) trig = Impulse.ar(1/dur * trsp.midiratio); // Trigger automatico (Hz) sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf)*trsp.midiratio*dir, trig, loop:0 // loop:0 ); bpf = Env.linen(0.01,dur-0.02,0.01); // Inviluppo trapezoidale env = EnvGen.ar(bpf,trig); fade = Linen.kr(gate,fIn,amp,fOut,done); // Fade inizio/fine flusso pann = Pan2.ar(sig*env*fade,pan); Out.ar(0,pann) }).add; ) a = Synth(\looper_2, [\buf,b,\amp,1,\gate,1]); a.set(\trsp,rrand(0.5,4)); a.set(\gate,0);
N.B. Se modifichiamo dinamicamente la trasposizione (e quindi la durata) nel corso di una singola ripetizione l'inviluppo non si riscala alla nuova durata ed il fadeout diventerà asincrono. In questi casi dovremo utilizzare la tecnica esposta in seguito per la rilettura di porzioni di Buffer specificando la durata dell'intero Buffer come porzione.
Una variante della tecnica appena illustrata è il muting che è particolarmente utile nella maggior parte delle situazioni. Consiste nel sostituire l'inviluppo trapezoidale con uno "al contrario":
Env.new([1,0,1,1],[0.01,0.01,0.98]).plot;
Per poi ritardare del tempo di fade out il trigger che fa partire la lettura del Buffer.
( Buffer.freeAll; b = Buffer.read(s,"Bach.wav".resolveRelative); SynthDef(\looper_3, {arg buf=0,amp=0,trsp=0,dir=1,fIn=0.2,fOut=1,pan=0,gate=0,done=2; var dur,trig,sig,bpf,env,fade,pann; dur = BufDur.kr(buf) * trsp.midiratio; trig = Impulse.ar(1/dur * trsp.midiratio); sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf)*trsp.midiratio*dir, DelayN.ar(trig,0.01,0.01), // Ritarda del tempo // di fadeout loop:0 ); bpf = Env.new([1,0,1,1], [0.01,0.01,dur-0.02]); // Muting env = EnvGen.ar(bpf,trig); fade = Linen.kr(gate,fIn,amp,fOut,done); pann = Pan2.ar(sig*env*fade,pan); Out.ar(0,pann) }).add; ) a = Synth(\looper_3, [\buf,b,\amp,1,\gate,1]); a.set(\trsp,rrand(0.5,4)); a.set(\gate,0);
In questo caso si verifica una latenza iniziale pari a tempo di fade out che, se inferiore ai 20 millisecondi, risulta comunque percettivamente irrilevante.
Esempi
-
Il codice precedente è valido anche se vogliamo scrivere e riprodurre il contenuto del Buffer in tempo reale senza la necessità di precisione ritmico/melodica e con una durata prestabilita.
( Buffer.freeAll; b = Buffer.alloc(s, s.sampleRate * 2, 1); // 2 secondi di Buffer vuoto SynthDef(\rec_1,{arg buf=0, bus=0, gain=0,loop=0,mix=0,done=2; // Synth che registra var in; in = SoundIn.ar(bus)*gain.lag(0.2); RecordBuf.ar(in, buf, preLevel:mix,loop:loop,doneAction:done); }).add; SynthDef(\looper_3, {arg buf=0,amp=0,trsp=0,dir=1,fIn=0.2,fOut=1,pan=0,gate=0,done=2; var dur,trig,sig,bpf,env,fade,pann; dur = BufDur.kr(buf)* trsp.midiratio; trig = Impulse.ar(1/dur * trsp.midiratio); sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf)*trsp.midiratio*dir, DelayN.ar(trig,0.01,0.01), loop:0 ); bpf = Env.new([1,0,1,1], [0.01,0.01,dur-0.02]); env = EnvGen.ar(bpf,trig); fade = Linen.kr(gate,fIn,amp,fOut,done); pann = Pan2.ar(sig*env*fade,pan); Out.ar(0,pann) }).add; ) a = Synth(\looper_3, [\buf,b,\amp,1]); // Prima crea il Player Synth(\rec_1, [\buf,b,\gain,1,\loop,0,\mix,1]); // Registra e si autodistrugge a.set(\gate,1); // Suona a.release(3); // Fade Out e distrugge il player b.zero; // Pulisce il Buffer
Possiamo anche programmare un pratico controllo da tastiera (o con qualsiasi altra interfaccia MIDI oppure OSC).
( w = Window.new("key"); w.view.keyDownAction_({arg ...args; // p = 112, s = 115, spazio = 32, enter = 13 switch(args[3], 112,{a = Synth(\looper_3, [\buf,b,\amp,1,\gate,1])}, // p = suona 115,{a.release(3)}, // s = fade out 32,{Synth(\rec_1, [\buf,b,\gain,1,\loop,0,\mix,1]); }, // spazio = registra 13,{b.zero}) // enter = pulisce }); w.front; w.alwaysOnTop_(true); w.onClose_({w.free}) )
Possiamo anche utilizzare più Buffers di diverse lunghezze memorizzati in un Array controllandoli in modo indipendente sia per quanto riguarda la registrazione che la riproduzione (in questo caso sono registrati in tempo reale, ma potrebbero anche contenere audio files differenti). Nell'esempio seguente abbiamo mappato alcuni tasti su tre diverse azioni ma qualsiasi tipo di controllo è possibile:
q, w, e, r ogni volta che premiamo un tasto parte la registrazione del Buffer corrispondente. Alla fine della registrazione il Synth si autodistrugge (dynamic voice allocation).
a, s, d, f ogni volta che premiamo un tasto puliamo il Buffer corrispondente.
1, 2, 3, 4 tenendo premuto uno o più tasti leggiamo in loop il Buffer corrispondente, al rilascio comincia un fade out alla fine del quale il Synth si autodistrugge per liberare memoria (dynamic voice allocation).
( Buffer.freeAll; ~durs = [2,4,0.2,1]*s.sampleRate; // Array di durate ~bufs = ~durs.collect({arg i; Buffer.alloc(s,i)}); // Array di Buffers SynthDef(\rec_2,{arg buf=0, bus=0, gain=0,loop=0,mix=0,done=2; var in; in = SoundIn.ar(bus)*gain.lag(0.2); RecordBuf.ar(in, buf, preLevel:mix,loop:loop,doneAction:done); }).add; SynthDef(\looper_3, {arg buf=0,amp=0,trsp=0,dir=1,fIn=0.2,fOut=1,pan=0,gate=0,done=2; var dur,trig,sig,bpf,env,fade,pann; dur = BufDur.kr(buf)* trsp.midiratio; trig = Impulse.ar(1/dur * trsp.midiratio); sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf)*trsp.midiratio*dir, DelayN.ar(trig,0.01,0.01), loop:0 ); bpf = Env.new([1,0,1,1], [0.01,0.01,dur-0.02]); env = EnvGen.ar(bpf,trig); fade = Linen.kr(gate,fIn,amp,fOut,done); pann = Pan2.ar(sig*env*fade,pan); Out.ar(0,pann) }).add; //================================ Controllo da tastiera ~prec = 0; // inizializzazione variabile w = Window.new("key"); w.view.keyDownAction_({arg ...args; var val; ~ora = args[3]; if((~ora != ~prec),{val = args[3];~prec = ~ora}); switch(val, 49, {a = Synth(\looper_3, [\buf,~bufs[0],\amp,1,\gate,1])}, // 0 50, {b = Synth(\looper_3, [\buf,~bufs[1],\amp,1,\gate,1])}, // 1 51, {c = Synth(\looper_3, [\buf,~bufs[2],\amp,1,\gate,1])}, // 2 52, {d = Synth(\looper_3, [\buf,~bufs[3],\amp,1,\gate,1])}, // 3 113, {Synth(\rec_2, [\buf,~bufs[0],\gain,1,\loop,0,\mix,0])}, // q 119, {Synth(\rec_2, [\buf,~bufs[1],\gain,1,\loop,0,\mix,0])}, // w 101, {Synth(\rec_2, [\buf,~bufs[2],\gain,1,\loop,0,\mix,0])}, // e 114, {Synth(\rec_2, [\buf,~bufs[3],\gain,1,\loop,0,\mix,0])}, // r 97, {~bufs[0].zero}, // a 115, {~bufs[1].zero}, // s 100, {~bufs[2].zero}, // d 102, {~bufs[3].zero}) // f }); w.view.keyUpAction_({arg ...args; if(args[3]==~ora,{~prec = 0}); // Reset switch(args[3], 49, {a.release(0.2)}, // 0 50, {b.release(0.2)}, // 1 51, {c.release(0.2)}, // 2 52, {d.release(0.2)}); // 3 }); w.front; w.alwaysOnTop_(true); w.onClose_({w.free}) )
Polifonia sincronizzata (metrica).
Se invece abbiamo la necessità di sincronizzare la lettura in loop di più Buffers per rimanere all'interno di una griglia ritmica precisa dobbiamo effettuare alcune operazioni:
Programmare un Synth (Rec Master) per registrare un Buffer (o una porzione di esso) e calcolarne automaticamente la durata oppure nel caso volessimo utilizzare un audio file caricato nel Buffer recuperarla con il metodo .duration. In entrambi i casi il valore viene scritto su un Bus di controllo.
Programmare un Synth (Rec Slave) per registrare tutti gli altri Buffers senza scriverne la durata sul Bus dedicato.
Programmare un Synth per generare un segnale periodico (Clock - onda a dente di sega) con periodo uguale alla durata recuperata dal Bus di controllo. Questo segnale è scritto su un secondo Bus di controllo e utilizzato da tutti i players per rileggere i Buffers.
Programmare uno o più Synth (Player 1...N) per leggere ciclicamente uno o più Buffers attraverso un trigger derivato dal segnale di clock in ingresso. Questo deve poter essere frazionato internamente ad ogni Synth in modo da generare eventuali suddivisioni del beat.
( Buffer.freeAll; // ------------> Buffers ~bufs = 4.collect({Buffer.alloc(s,s.sampleRate * 5)}); // Array di 4 Buffers (5 sec) // ------------> Bus controllo ~busSync = Bus.control; // Bus su cui scrivere il segnale di clock (sync) ~busDur = Bus.control; // Bus su cui scrivere la durata della registrazione // ------------> Rec Master SynthDef(\rec_M, {arg buf=0,busIn=0,gain=0,loop=0,mix=0,t_rec=0,t_stop=0,busDur=40; var in, gate, dur; in = SoundIn.ar(busIn)*gain.lag(0.2); gate = SetResetFF.kr(t_rec,t_stop); // flip flop 1 o 0 a kr per // filtrare i t_ // necessari a Timer.kr() dur = Timer.kr(t_rec+t_stop); // riporta il tempo delta (sec) Linen.kr(gate,doneAction:2); // Autodistruzione Out.kr(busDur,dur); // Scrive durata sul Bus RecordBuf.ar(in,buf,preLevel:mix,run:gate,loop:loop); }).add; // ------------> Rec Slave SynthDef(\rec_2,{arg buf=0, bus=0, gain=0,loop=0,mix=0,done=2; var in; in = SoundIn.ar(bus)*gain.lag(0.2); RecordBuf.ar(in, buf, preLevel:mix,loop:loop,doneAction:done); }).add; // ------------> Clock SynthDef(\syncSig, {arg busSync=41, busDur=40; var dur,sig; dur = In.kr(busDur); // Legge durata dal Bus sig = LFSaw.kr(1/dur); // sec --> Hz (dente di sega) Out.kr(busSync,sig); // Scrive sul Bus }).add; // ------------> Players SynthDef(\looper_4, {arg buf=0,amp=0,trsp=0,dir=1,fIn=0.2,fOut=1,pan=0,gate=0,done=2, div=1, // suddivisioni del beat principale busSync=41, // Bus dal quale leggere il segnale di sync busDur=40; // Bus dal quale leggere il segnale durata var sig,bpf,env,fade,pann, hz2sec, sample, clock, dur, wrap, trig; hz2sec = 1/div; // da Hertz a secondi sample = 1/SampleRate.ir; // delta in sec tra campioni clock = In.kr(busSync); // legge il segnale di clock in entrata dur = In.kr(busDur); // legge il segnale di clock in entrata wrap = Wrap.kr(clock, // Wrap around hz2sec * trsp.midiratio * -1, // soglia inferiore hz2sec * trsp.midiratio); // soglia superiore trig = Trig1.kr(wrap,sample); // Trigger sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf)*trsp.midiratio*dir, DelayN.ar(trig,0.01,0.01), loop:0); bpf = Env.new([1,0,1,1],[0.01,0.01,dur*trsp.midiratio-0.02]); env = EnvGen.ar(bpf,trig); fade = Linen.kr(gate,fIn,amp,fOut,done); pann = Pan2.ar(sig*env*fade,pan); Out.ar(0,pann) }).add; )
Definiti i Synths dobbiamo procedere secondo un'ordine preciso:
Registrare un Buffer e scrivere la durata di quanto registrato (tempo delta del beat) sul Bus dedicato con il registratore master.
a = Synth(\rec_M, [\buf,~bufs[0],\gain,1,\busDur,~busDur,\t_rec,1]); // Registra a.set(\t_stop,1); // Stoppa ~bufs[0].plot; // Verifiche ~busDur.get;
Creare il Synth che fornisce il clock.
b = Synth(\syncSig, [\busSync,~busSync,\busDur, ~busDur]); ~busSync.scope; // Verifica
Creare il Synth che rilegge il Buffer (se div=1 intera registrazione, altrimenti solo in parte).
c = Synth(\looper_4,[\buf,~bufs[0],\amp,1,\busSync,~busSync,\bufDur,~bufDur,\div,1]); c.set(\gate,1); c.set(\div,2); // Cambia la relazione ritmica con il Beat
Registrare uno o più nuovi Buffers con il registratore slave
Synth(\rec_2, [\buf,~bufs[1],\gain,1]);
Creare i Synth(s) che rileggono i Buffers (se div=1 intera registrazione, altrimenti solo in parte).
e = Synth(\looper_4,[\buf,~bufs[1],\amp,1,\busSync,~busSync,\bufDur,~bufDur,\div,1]); c.set(\gate,1); e.set(\div,2); c.set(\trsp,0); e.set(\trsp,0); // La trasposizione agisce anche sul tempo.... c.set(\gate,0); e.set(\gate,0); b.free;
Looping di una o più porzioni di Buffer
Per eseguire in loop porzioni di un Buffer dobbiamo stabilire due nuovi valori da specificare come argomenti:
Posizione - il punto all'interno del Buffer da dove far cominciare la lettura al ricevimento del trigger. Per comodità lo possiamo specificare in secondi o in un fattore di moltiplicazione compreso tra 0 e 1 (inizio e fine del Buffer) ma poi dobbiamo necessariamente convertirlo in frames in quanto PlayBuf.ar() lo accetta in questa unità di misura.
Durata - la durata della porzione in secondi da specificare nell'inviluppo di ampiezza.
La UGen PlayBuf.ar() accetta come argomento solo la posizione dalla quale far partire il playback che viene eseguito fino alla fine del Buffer. Se vogliamo stopparlo prima possiamo farlo solamente applicando un inviluppo di ampiezza che distrugge il Synth al termine del fade out.
( Buffer.freeAll; b = Buffer.read(s,"Bach.wav".resolveRelative); SynthDef(\looper_5, {arg buf=0,amp=0,trsp=0,dir=1,fIn=0.2,fOut=1,pan=0,gate=0,done=2, pos=0,dur=0.2; var durs,trig,sig,bpf,env,fade,pann; durs = dur * trsp.midiratio; // Durata trig = Impulse.ar(durs.reciprocal); sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf)*trsp.midiratio*dir, DelayN.ar(trig,0.01,0.01), BufSampleRate.kr(buf)*pos); // Posizione bpf = Env.new([1,0,1,1],[0.01,0.01,durs-0.02]); env = EnvGen.ar(bpf,trig); fade = Linen.kr(gate,fIn,amp,fOut,done); pann = Pan2.ar(sig*env*fade,pan); Out.ar(0,pann) }).add; ) a = Synth(\looper_5, [\buf,b,\amp,1,\gate,1]); a.set(\trsp,rand2(3),\dur,rrand(0.2,1),\pos,rand(3.0),\dir,[-1,1].choose) a.set(\trsp,0,\dur,rrand(0.1,1),\pos,rand(b.duration-2)); a.set(\gate,0)
Esempi
-
Possiamo delimitare le porzioni interagendo col mouse sulla visualizzazione del Buffer (forma d'onda) in una GUI:
( ~path = "Bach.wav".resolveRelative; Buffer.freeAll; b = Buffer.read(s,~path); // Carica snel Buffer f = SoundFile.openRead(~path); // Crea un'istanza di Soundfile f.duration; // Durata in secondi w = Window.new("SoundFileView", 1000@200); a = SoundFileView.new(w, Rect(5,5, 990, 190)); a.soundfile_(f.numChannels_(1)); // Assegna il Sounfile alla visualizzazione a.read(0, f.numFrames); // Legge il file dal frame 0 all'ultimo, // N.B. Per file molto lunghi è meglio usare: a.readWithTask; a.refresh; // Ripulisce e visualizza w.front; w.alwaysOnTop_(false); w.onClose_({w.free;a.free;f.free;r.stop;d.free}); a.gridOn_(false); // Elimina la Griglia a.timeCursorOn_(true); // Visualizza il cursore a.timeCursorColor_(Color.red); // Colore del cursore SynthDef(\looper_5, {arg buf=0,pos=0,dur=0.2,amp=0,trsp=0,dir=1,fIn=0.2,fOut=1,pan=0,gate=0,done=2; var durs,trig,sig,bpf,env,fade,pann; durs = dur * trsp.midiratio; trig = Impulse.ar(durs.reciprocal); sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf)*trsp.midiratio*dir, DelayN.ar(trig,0.01,0.01), BufSampleRate.kr(buf)*pos); bpf = Env.new([1,0,1,1],[0.01,0.01,durs-0.02]); env = EnvGen.ar(bpf,trig); fade = Linen.kr(gate,fIn,amp,fOut,done); pann = Pan2.ar(sig*env*fade,pan); Out.ar(0,pann) }).add; {d = Synth(\looper_5,[\buf,b])}.defer(0.5); // Crea il Synth a.mouseUpAction_({var csel, start, cpos, size, dir; csel = a.currentSelection; // selezione corrrente. cpos = a.timeCursorPosition; // posizione del cursore start = a.selection(csel)[0]; // in frames size = a.selection(csel)[1]; // in frames if(cpos==start, {dir = 1}, // Recto {dir = -1}); // verso [csel, start/f.sampleRate, size/f.sampleRate, dir].postln; d.set(\pos, start/f.sampleRate, // frames --> sec \dur, size/f.sampleRate, // frames --> sec \amp, 1, \trsp, 0, \dir, dir, \pan, 0, \gate,1); }) )
-
Possiamo delimitare le porzioni interagendo con controller MIDI oppure OSC e visualizzarle in una GUI:
( ~path = "Bach.wav".resolveRelative; b = Buffer.read(s,~path); // Carica snel Buffer f = SoundFile.openRead(~path); // Crea un'istanza di Soundfile f.duration; // Durata in secondi w = Window.new("SoundFileView", 1000@200); a = SoundFileView.new(w, Rect(5,5, 990, 190)); a.soundfile_(f.numChannels_(1)); // Assegna il Sounfile alla visualizzazione a.read(0, f.numFrames); // Legge il file dal frame 0 all'ultimo, // N.B. Per file molto lunghi è meglio usare: a.readWithTask; a.refresh; // Ripulisce e visualizza w.front; w.alwaysOnTop_(false); w.onClose_({w.free;a.free;f.free;r.stop;d.free}); a.gridOn_(false); // Elimina la Griglia a.timeCursorOn_(true); // Visualizza il cursore a.timeCursorColor_(Color.red); // Colore del cursore SynthDef(\looper_5, {arg buf=0,pos=0,dur=0.2,amp=0,trsp=0,dir=1, fIn=0.2,fOut=1,pan=0,gate=0,done=2; var durs,trig,sig,bpf,env,fade,pann; durs = dur * trsp.midiratio; trig = Impulse.ar(durs.reciprocal); sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf)*trsp.midiratio*dir, DelayN.ar(trig,0.01,0.01), BufSampleRate.kr(buf)*pos); bpf = Env.new([1,0,1,1],[0.01,0.01,durs-0.02]); env = EnvGen.ar(bpf,trig); fade = Linen.kr(gate,fIn,amp,fOut,done); pann = Pan2.ar(sig*env*fade,pan); Out.ar(0,pann) }).add; {d = Synth(\looper_5,[\buf,b,\amp,1,\trsp,0,\pan,0,\gate,1])}.defer(0.5); MIDIIn.disconnectAll; MIDIIn.connectAll; MIDIdef.cc(\start,{arg val; ~start = val.linlin(0,127,0,f.numFrames); // Riscala... {a.setSelectionStart(0,~start)}.defer(0); // all'interfaccia d.set(\pos, ~start/f.sampleRate); // al Synth in secondi },16); // CCN MIDIdef.cc(\dur,{arg val; ~size = val.linlin(0,127,10,f.numFrames); // Riscala... size > 0 {a.setSelectionSize(0, ~size)}.defer(0); // all'interfaccia d.set(\dur, ~size/f.sampleRate); // al Synth in secondi },17); // CCN )
-
In quest'ultimo esempio possiamo selezionare più porzioni dello stesso Buffer e farle eseguire da players diversi. C'è anche la possibilità di inserire una pausa espressa sotto forma di fattore di moltiplicazione della durata del frammento. In questo caso la pausa non è tra le ripetizioni dei frammenti ma interna al frammento stesso.
( MIDIIn.disconnectAll; MIDIIn.connectAll; ~path = "Bach.wav".resolveRelative; //--------------------------------------------------------------- Buffer Buffer.freeAll; b = Buffer.read(s,~path); //--------------------------------------------------------------- Scelta tasti controllo ~nsel = 8; // Numero selezioni ~tastiE = [$1, $2, $3, $4, $5, $6, $7, $8]; // Char --> Tasti scelta edit ~tastiP = [$q, $w, $e, $r, $t, $y, $u, $i]; // Char --> Tasti scelta play ~color = ~nsel.collect({Color.rand}); // Array di Colori //--------------------------------------------------------------- SynthDef SynthDef(\looper_6, {arg buf=0,pos=0,dur=0.2,pausa=0,amp=0, trsp=0,dir=1,fIn=0.2,fOut=1,pan=0,gate=0,done=2; var durs,trig,sig,bpf,env,fade,pann; durs = dur * trsp.midiratio; trig = Impulse.ar(durs.reciprocal); sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf)*trsp.midiratio*dir, DelayN.ar(trig,0.01,0.01), BufSampleRate.kr(buf)*pos); bpf = Env.new([1,0,0,1,1],[0.01,pausa*durs,0.01,durs-0.02-(pausa*durs)]); // Muting con pausa env = EnvGen.ar(bpf,trig); fade = Linen.kr(gate,fIn,1,fOut,done); pann = Pan2.ar(sig*env*fade*amp.lag(0.2),pan); Out.ar(0,pann) }).add; //--------------------------------------------------------------- Array di Synth (1 per selezione) {~synths = ~nsel.collect({Synth(\looper_6,[\buf,b,\done,0])})}.defer(0.5); //--------------------------------------------------------------- GUI w = Window.new("SoundFileView", 900@550); w.addFlowLayout( 10@10, 10@5 ); // Pixel cornice x@y, distanza tra gli elementi x@y w.front; w.alwaysOnTop_(false); w.onClose_({w.free;a.free;f.free;r.stop;d.free;s.freeAll}); //--------------------------------------------------------------- SoundFileWiew f = SoundFile.openRead(~path); // Crea un'istanza di Soundfile a = SoundFileView.new(w, 879@150); // Crea un'istanza di SoundfileView a.soundfile_(f.numChannels_(1)); // Assegna il Sounfile alla visualizzazione a.read(0, f.numFrames); // Legge il file dal frame 0 all'ultimo, // N.B. Per file molto lunghi è meglio usare: a.readWithTask; a.refresh; // Ripulisce e visualizza a.gridOn_(false); // Elimina la Griglia a.timeCursorOn_(true); // Visualizza il cursore a.timeCursorColor_(Color.red); // Colore del cursore ~nsel.do({arg i; a.setSelectionColor(i,~color[i])}); // Colori --> selezioni // Selezione frammenti // Azioni col mouse sulla GUI --> Synth a.mouseUpAction_({var csel,start, cpos, size, dir; csel = a.currentSelection; // selezione corrente cpos = a.timeCursorPosition; // posizione del cursore start = a.selection(csel)[0]; // in frames size = a.selection(csel)[1]; // in frames if(cpos==start, {dir = 1}, // Recto {dir = -1}); // verso ~synths[csel].set(\pos, start/f.sampleRate, // frames --> sec \dur, size/f.sampleRate, // frames --> sec \dir, dir); // [csel, start/f.sampleRate, size/f.sampleRate, dir].postln; }); //--------------------------------------------------------------- Botton Edit & Play ~bottE = ~nsel.collect({arg i; // Array di Button Edit Button.new(w,101@40) .states_([[~tastiE[i],Color.black, ~color[i]], [~tastiE[i],Color.yellow,~color[i].multiply(0.8)] ]) }); ~bottP = ~nsel.collect({arg i; // Array di Button Play Button.new(w,101@40) .states_([[~tastiP[i],Color.black, ~color[i]], [~tastiP[i],Color.yellow,~color[i].multiply(0.8)] ]) }); //------------------------------------ Tastiera --> GUI w.view.keyDownAction_({arg ...args; ~tastiE.do({arg item,id; // il primo resetta gli altri Bottons (PORTA) if( (args[1]==item) && (~bottE[id].value == 0), // AND {~bottE[id].valueAction_(1)}, {~bottE[id].valueAction_(0)} // Reset ) }); ~tastiP.do({arg item,id; // nel secondo tutti i botton sono indipendenti (INTERRUTTORE) if(args[1]==item, {if(~bottP[id].value == 0, {~bottP[id].valueAction_(1)}, {~bottP[id].valueAction_(0)})} ) }); }); //------------------------------------ GUI --> Synth ~bottE.do({arg item,id; item.action_({arg sel; if(sel.value==1, {a.currentSelection_(id)}); // setta sel corrente }) }); ~bottP.do({arg item,id; item.action_({arg sel; if(sel.value==1, {~synths[id].set(\gate,1)}, // Play {~synths[id].set(\gate,0)} // Stop ); }) }); //--------------------------------------------------------------- Slider pause // Solo da GUI ~kPau = ~nsel.collect({arg i; Slider.new(w,101@40).background_(~color[i]) // Array di Slider .thumbSize_(5) .valueAction_(1)}); ~kPau.do({arg item,id; item.action_({arg val; // GUI --> Synth ~synths[id].set(\pausa,val.value.linlin(0,1,1,0)) }) }); //--------------------------------------------------------------- Knob Pan // Sia da GUI che da MIDI w.addFlowLayout( 20@300, 37@5 ); // Pixel cornice x@y, distanza tra gli elementi x@y CAMBIO ~kPan = ~nsel.collect({arg i; Knob.new(w,75@80).centered_(false) .color_([~color[i], Color.white,Color.white,Color.black]) .valueAction_(0.5)}); // Array di Knob ~kPan.do({arg item,id; MIDIdef.cc("pan_"++id, // MIDI --> GUI {arg val; {item.valueAction_(val.linlin(0,127,0,1))}.defer(0) },16+id); // ccn item.action_({arg val; // GUI --> Synth ~synths[id].set(\pan,val.value.linlin(0,1,-1.0,1)) }) }); //--------------------------------------------------------------- Slider amp // Sia da GUI che da MIDI ~kAmp = ~nsel.collect({arg i; Slider.new(w,75@150).background_(~color[i])}); // Array di Sliders ~kAmp.do({arg item,id; MIDIdef.cc("amp_"++id, // MIDI --> GUI {arg val; {item.valueAction_(val.linlin(0,127,0,1))}.defer(0) },id); // ccn item.action_({arg val; // GUI --> Synth ~synths[id].set(\amp,val.value) }) }); )
Scratching
Tutte le tecniche di scratching si basano sull'utilizzo di un segnale di controllo come puntatore per leggere gli indici di un Buffer.
Le carattteristiche del segnale puntatore (forma d'onda, eventuale frequenza se periodico, interpolazione, etc.) definiscono il tipo di elaborazione.
Singolo evento
In questo tipo di players dobbiamo specificare il punto dove inizia la lettura del Buffer e quello in cui termina (fine). La UGen BufRd.ar() accetta questi valori in frames ma musicalmente credo sia più pratico pensarli in secondi per poi convertirli automaticamente all'interno del Synth. La durata è calcolata anch'essa automaticamente dall'espressione abs(fine-inizio) e la direzione di lettura dipende da questi valori (se inizio < fine = recto, altrimenti verso). Se vogliamo invece eseguire l'intero Buffer basta specificare 0 come inizio e Buffer.duration come fine.
s.boot; s.plotTree; ( Buffer.freeAll; b = Buffer.read(s,"bach.wav".resolveRelative); SynthDef(\sctch_1, {arg buf=0,amp=0,start=0,end=0.2,trsp=0,pan=0; var dur,ini,fine,punta,sig,bpf,env,pann; dur = abs(start-end) * trsp.midiratio.reciprocal; // Durata in secondi ini = SampleRate.ir*start; // Inizio in frames fine = SampleRate.ir*end; // Fine in frames punta = Line.ar(ini, fine, dur); sig = BufRd.ar(1, buf, punta); bpf = Env.linen(0.01,dur-0.02,0.01); env = EnvGen.kr(bpf,1,doneAction:2); pann = Pan2.ar(sig*env*amp.lag(0.2),pan); Out.ar(0, pann) }).add; ) Synth(\sctch_1, [\buf,b,\amp,1,\trsp,0,\pan,0]); Synth(\sctch_1, [\buf,b,\amp,1,\trsp,4,\start,0,\end,b.duration]); // Intero Buffer Synth(\sctch_1, [\buf,b,\amp,1,\trsp,rand2(4),\start,rand(b.duration),\end,rand(b.duration)]);
Looper
Per realizzare un looper con questa tecnica dobbiamo utilizzare un fasore come segnale puntatore e applicare la tecnica del muting come inviluppo d'ampiezza. Entrambi sono sincronizzati da un segnale impulsivo (trigger).
( Buffer.freeAll; b = Buffer.read(s,"bach.wav".resolveRelative); SynthDef(\sctch_2, {arg buf=0, amp=0,start=0,end=0.2,trsp=0,fIn=0.2,fOut=1,pan=0,gate=0,done=2; var dur,ini,fine,trig,punta,sig,bpf,env,fade,pann; dur = abs(start-end) * trsp.midiratio.reciprocal; ini = SampleRate.ir*start; fine = SampleRate.ir*end; trig = Impulse.ar(dur.reciprocal); punta = Phasor.ar(DelayN.ar(trig,0.01,0.01), // ritardo per Muting BufRateScale.ir(buf)*trsp.midiratio, // Incremento ini,fine, ini); // Punto di reset sig = BufRd.ar(1, buf, punta); bpf = Env.new([1,0,1,1],[0.01,0.01,dur-0.02]); // Muting env = EnvGen.ar(bpf,trig); fade = Linen.kr(gate,fIn,1,fOut,done); pann = Pan2.ar(sig*env*amp.lag(0.2)*fade,pan); Out.ar(0, pann) }).add; ) a = Synth(\sctch_2, [\buf,b,\amp,1,\trsp,0,\pan,0]); a.set(\gate,1); a.set(\start,1.3,\end,2); a.set(\start,rand(2.0),\end,rrand(1.5,4.1)); a.set(\trsp,0); a.set(\start,0,\end,b.duration); // Intero buffer a.set(\gate,0);
Surfing
Nelle tecniche appena illustrate il contenuto del Buffer è letto interamente o in parte da un segnale puntatore che
generando una rampa lineare si muove da una posizione a un altra in un tempo dato e una velocità costante. Nelle
tecniche di surfing invece il puntatore si muove da una posizione ad un'altra controllato attraverso l'interazione con devices
fisici (mouse, dispositivi Midi o Osc, Hdi o microcontrollori come Arduino) oppure attraverso segnali di controllo non lineari.
Mouse
Quando utilizziamo devices fisici il parametro più importante nella realizzazione di flussi sonori con caratteristiche morfologiche differenti è il tempo di smoothing o interpolation time ovvero il tempo che il puntatore impiega a passare da un campione al successivo. Più è basso e più lo spostamento (la lettura) sarà pronto ed immediato, più è alto più il movimento avviene lentamente, ritardando la lettura rispetto al gesto che ha prodotto l'azione.
( Buffer.freeAll; b = Buffer.read(s,"bach.wav".resolveRelative); SynthDef(\sctch_3, {arg buf=0, amp=0,smooth=0.2,fIn=0.2,fOut=0.2,pan=0,gate=0,done=2; var punta,sig,fade,pann; punta = MouseX.kr(0,BufFrames.kr(buf),0,smooth); // Puntatore sig = BufRd.ar(1, buf, K2A.ar(punta)); fade = Linen.kr(gate,fIn,1,fOut,done); pann = Pan2.ar(sig*amp.lag(0.2)*fade,pan); Out.ar(0, pann) }).add; ) a = Synth(\sctch_3, [\buf,b,\amp,1,\pan,0]); a.set(\gate,1); a.set(\smooth,2); a.set(\smooth,0.02); a.set(\smooth,rrand(0.1,2).postln); a.set(\gate,0);
GUI
Nell'esempio precedente abbiamo controllato la posizione del puntatore impiegando la UGen MouseX.kr() che recupera i valori direttamente all'interno del Synth (Server side). Se invece vogliamo recuperarli da una GUI (Client side) dobbiamo farlo programmando questo parametro come argomento per poi realizzare lo smoothing all'interno del Synth e riscalare i valori a seconda dei diversi range accettati dalle UGens. In questo caso utilizziamo il metodo ..mouseOverAction_() ma qualunque altro tra quelli a disposizione può essere impiegato.
( Buffer.freeAll; ~path = "bach.wav".resolveRelative; b = Buffer.read(s,~path); f = SoundFile.openRead(~path); // Crea un'istanza di Soundfile SynthDef(\sctch_4, {arg buf=0, amp=0,pos=0,smooth=0.2,fIn=0.2,fOut=0.2,pan=0,gate=0,done=2; var punta,sig,fade,pann; punta = SampleRate.ir * pos.varlag(smooth); // Puntatore sig = BufRd.ar(1, buf, K2A.ar(punta)); fade = Linen.kr(gate,fIn,1,fOut,done); pann = Pan2.ar(sig*amp.lag(0.2)*fade,pan); Out.ar(0, pann) }).add; //--------------------------------------------------------------- // Synth {a = Synth(\sctch_4, [\buf,b,\amp,1,\pan,0])}.defer(0.5); //--------------------------------------------------------------- // GUI w = Window.new("SoundFileView", Rect.new(0,0,550,210)); w.acceptsMouseOver_(true); w.front; w.alwaysOnTop_(true); w.onClose_({w.free;a.free}); t = SoundFileView.new(w, Rect(5,5, 540, 200)); t.soundfile_(f); t.read(0, f.numFrames); // N.B. Per file molto lunghi è meglio usare: t.readWithTask; t.timeCursorOn_(true); t.timeCursorColor_(Color.red); t.gridOn_(false); t.refresh; //--------------------------------------------------------------- // Azioni t.mouseOverAction_({arg ...args; a.set(\pos,args[1].linlin(0,536,0,b.duration)); }); ) a.set(\gate,1); a.set(\smooth,1); a.set(\smooth,0.02); a.set(\smooth,0.5); a.set(\gate,0);
Midi/Osc
Se vogliamo controllare la posizione del puntatore attraverso un device MIDI oppure OSC possiamo utilizzare lo stesso Synth appena illustrato modificando solamente la modalità di invio dei valori a seconda del protocollo utilizzato. La posizione è visualizzata su una GUI.
( Buffer.freeAll; ~path = "bach.wav".resolveRelative; b = Buffer.read(s,~path); f = SoundFile.openRead(~path); // Crea un'istanza di Soundfile SynthDef(\sctch_4, {arg buf=0, amp=0,pos=0,smooth=0.2,fIn=0.2,fOut=0.2,pan=0,gate=0,done=2; var punta,sig,fade,pann; punta = SampleRate.ir * pos.varlag(smooth); // Puntatore sig = BufRd.ar(1, buf, K2A.ar(punta)); fade = Linen.kr(gate,fIn,1,fOut,done); pann = Pan2.ar(sig*amp.lag(0.2)*fade,pan); Out.ar(0, pann) }).add; //--------------------------------------------------------------- // Synth {a = Synth(\sctch_4, [\buf,b,\amp,1,\pan,0])}.defer(0.5); //--------------------------------------------------------------- // GUI (solo visualizzazione) w = Window.new("SoundFileView", Rect.new(0,0,550,210)); w.acceptsMouseOver_(true); w.front; w.alwaysOnTop_(true); w.onClose_({w.free;a.free}); t = SoundFileView.new(w, Rect(5,5, 540, 200)); t.soundfile_(f); t.read(0, f.numFrames); // N.B. Per file molto lunghi è meglio usare: t.readWithTask; t.timeCursorOn_(true); t.timeCursorColor_(Color.red); t.gridOn_(false); t.refresh; //--------------------------------------------------------------- // Azioni // MIDI MIDIClient.init; MIDIIn.disconnectAll; MIDIIn.connectAll; m = MIDIdef.cc(\midi,{arg pos; a.set(\pos, pos.linlin(0,127,0,b.duration)); // al Synth {t.timeCursorPosition_( // Alla GUI pos.linlin(0,127,0,b.numFrames)) }.defer(0) // AppClock }); // OSC OSCdef.freeAll; o = OSCdef.new(\osc, {arg pos; pos.postln; a.set(\pos, pos[1].linlin(0,127,0,b.duration)); // al Synth {t.timeCursorPosition_( // Alla GUI pos[1].linlin(0,127,0,b.numFrames)) }.defer(0) // AppClock }, '/pos', recvPort:8001) ) a.set(\gate,1); a.set(\smooth,1); a.set(\smooth,0.02); a.set(\smooth,0.5); a.set(\gate,0);
KSig
Nell'esempio di interazione con il mouse abbiamo utilizzato un segnale di controllo della posizione interno al Synth (MouseX.kr()). Possiamo sostituirlo con qualsiasi tipo di segnale debitamente riscalato. In questo caso l'argomento principale non è il tempo di smoothing ma la frequenza del segnale utilizzato.
( Buffer.freeAll; ~path = "bach.wav".resolveRelative; b = Buffer.read(s,~path); f = SoundFile.openRead(~path); // Crea un'istanza di Soundfile SynthDef(\sctch_5, {arg buf=0, amp=0,ksig=0,frq=0.1,fIn=0.2,fOut=0.2,pan=0,gate=0,done=2; var punta,sig,fade,pann; punta = Select.ar(ksig, // Puntatore [LFNoise1.ar(frq).range(0,BufFrames.kr(buf)), // 0 LFNoise2.ar(frq).range(0,BufFrames.kr(buf)), // 1 LFTri.ar(frq).range(0,BufFrames.kr(buf)), // 2 LFCub.ar(frq).range(0,BufFrames.kr(buf)) // 3 ]); sig = BufRd.ar(1, buf, K2A.ar(punta)); fade = Linen.kr(gate,fIn,1,fOut,done); pann = Pan2.ar(sig*amp.lag(0.2)*fade,pan); Out.ar(0, pann); // ------------------------------ Server --> Interprete x visualizzazione SendReply.kr(Impulse.kr(50), // rata di campionamento '/posa', // indirizzo o nome punta); // segnale da campionare }).add; //--------------------------------------------------------------- // Synth {a = Synth(\sctch_4, [\buf,b,\amp,1,\pan,0])}.defer(0.5); //--------------------------------------------------------------- // Visualizzazione su GUI w = Window.new("SoundFileView", Rect.new(0,0,550,210)); w.acceptsMouseOver_(true); w.front; w.alwaysOnTop_(true); w.onClose_({w.free;a.free}); t = SoundFileView.new(w, Rect(5,5, 540, 200)); t.soundfile_(f); t.read(0, f.numFrames); // N.B. Per file molto lunghi è meglio usare: t.readWithTask; t.timeCursorOn_(true); t.timeCursorColor_(Color.red); t.gridOn_(false); t.refresh; OSCdef.freeAll; o = OSCdef.new(\pos, {arg pos; {t.timeCursorPosition_(pos[3])}.defer(0)},'/posa') ) a.set(\gate,1); a.set(\ksig,rand(4).postln); a.set(\frq,1); a.set(\gate,0);