Riproduttori

Se vogliamo un semplice playback di una o più registrazioni memorizzate in un sound file senza alcun tipo di elaborazione abbiamo generalmente a disposizione due possibilità:

  1. Leggere i dati contenuti nel file accedendo continuamente al supporto sul quale si trovano (Hard disk o altro).

  2. Caricare il file in un buffer (RAM o memoria temporanea) per poi leggerne il contenuto senza accedere al supporto.

In entrambi i casi dobbiamo richiamare gli indici dei campioni memorizzati uno alla volta a una determinata rata di campionamento che deve corrispondere sia con quella utilizzata in fase di registrazione sia con quella utilizzata dal software che stiamo utilizzando per la rilettura.

Lettura da supporto

Questa tecnica si utilizza principalmente quando abbiamo la necessità di effettuare una semplice riproduzione di suond files molto lunghi senza alcun tipo di elaborazione. L'operazione in realtà avviene attraverso la lettura consecutiva di porzioni del sound file caricate dinamicamente in un Buffer di lunghezza ridotta che deve corrispondere a un multiplo di 2 * block size del sistema audio impiegato.

  1. Allochiamo un Buffer sul Server e carichiamo la prima porzione di Sound file invocando su di esso il metodo .cueSoundFile():

    (
    s.boot;
    s.scope;
    s.meter;
    s.plotTree;
    )
    
    (
    b = Buffer.cueSoundFile(s,                          // Server
                            "bach.wav".resolveRelative, // Path del Sound File da caricare
                            0,                          // Il primo frame da leggere
                            2,                          // Numero di canali da caricare
                            32768                       // buffersize temporaneo (multiplo di 2*block size)
                            );
    )
    
  2. Leggiamo il Buffer con la UGen DiskIn.ar() che, al termine della lettura ne sostituisce dinamicamente il contenuto caricando la porzione di Sound file successiva. Abbiamo anche la possibilità di leggere l'intero Sound file in loop specificandolo come terzo argomento.

    (
    SynthDef(\diskIn1, {arg buf=0, amp=1, loop=0;
                        var sig;
                            sig  = DiskIn.ar(2, buf, loop); // n.canali, n_buffer, loop
                        Out.ar(0,sig * amp.lag(0.2))
             }).add;
    
    {a = Synth(\diskIn1, [\buf, b,  // Buffer temporaneo
                          \amp, 1,
                          \loop,1]) // 0 = no loop / 1 = loop
    }.defer(0.5)
    )
    
    a.set(\loop,0);                 // unico modo per stopparlo (quando ha finito l'ultima lettura)
    
  3. Aggiungiamo un fade In e un fade Out "di servizio" per correggere eventuali discontinuità nel segnale all'inizio e alla fine della riproduzione (Linen.ar()))

    (
    p = "bach.wav".resolveRelative;                    // Path del file    
    
    SynthDef(\diskIn2, {arg buf=0, atk=0, rel=0.01, amp=1,loop=0,gate=0;
                        var sig,fade;
                            sig  = DiskIn.ar(2, buf, loop);
                            fade = Linen.kr(gate,atk,amp.lag(0.2),rel,doneAction:2); // Fade 
                        Out.ar(0,sig * fade)
            }).add;
    
    {
    b = Buffer.cueSoundFile(s,p,0,2); // nel caso dovessimo stoppare la riproduzione ricarica 
                                      // l'inizio del Sound File nel Buffer temporaneo 
                                      // (altrimenti ripartirebbe da  dove l'abbiamo stoppata
                                      // diventando fuori sincro (sfasato) con l'inviluppo)
    a = Synth(\diskIn2, [\buf,  b,
                         \atk,  0.01,// Tempo di Fade in
                         \rel,  5,   // Tempo di Fade Out
                         \amp,  1,
                         \loop, 1,
                         \gate, 1    // Primo e ultimo inviluppo
                        ])
    }.defer(0.5)
    )
    
    a.release(5);   // possiamo stopparlo quando vogliamo (e distrugge l'istanza).
    a.set(\loop,0); // si ferma alla fine del loop ma non non effettua il fadeout e non distrugge l'istanza
    

    Facciamo attenzione che se fermiamo la riproduzione con .release o .set(\gate,0) distruggendo di fatto l'istanza di Synth, nel Buffer temporaneo rimane memorizzato l'ultima porzione di Sound file letta. Se volessimo ricominciare dall'inizio una eventuale lettura successiva, dovremmo necessariamente ricaricare nel Buffer la porzione di Sound file iniziale.

  4. Aggiungiamo un secondo inviluppo trapezoidale (Env.linen()) senza fase di sostegno nel caso ci siano discontinuità all'inizio e alla fine del soundfile.

    In questo caso dobbiamo recuperare automaticamente la durata in secondi del soundfile per calcolare la durata della fase di sostegno:

    (
    p = "bach.wav".resolveRelative;
    d = SoundFile.openRead(p).duration;        // recupera la durata del Sound File e la assegna a 'd'
    
    SynthDef(\diskIn3, {arg buf=0, dur=1, atk=0, rel=0.01,amp=1,loop=0,gate=0;
                        var sig,fade,env;
                            sig = DiskIn.ar(2, buf, loop);
                            fade = Linen.kr(gate,atk,amp.lag(0.2),rel,doneAction:2);
                            env = Env.linen(atk,
                                            dur-atk-rel,                // calcola il tempo della fase di sostegno
                                            rel).kr(0,                  // doneAction:0 per l'eventuale loop
                                                    Impulse.kr(1/dur)); // gate ad ogni inizio di lettura
                       Out.ar(0,sig * env * fade)
                       }).add;
    
    {
    b = Buffer.cueSoundFile(s,p,0,2); 
    a = Synth(\diskIn3, [\buf,  b,
                         \dur,  d,   // invia la durata dell'intero Sound File
                         \atk,  1,   // Tempo di Fade in
                         \rel,  5,   // Tempo di Fade Out
                         \amp,  1,
                         \loop, 1,
                         \gate, 1    // Primo e ultimo inviluppo
                        ])
    }.defer(0.5)
    )
    
    a.release(0.2);  
    a.set(\loop,0);  
    

Lettura da Buffer

Un'alternativa alla tecnica appena illustrata consiste nel leggere i campioni all'interno di un Buffer con le funzioni basilari della UGen PlayBuf.ar(). In questo caso possiamo sia caricare il contenuto di un sound files nel Buffer che registrarlo in tempo reale recuperandone la durata dinamicamente all'interno del Synth con la UGen BufDur.kr().

(
p = "bach.wav".resolveRelative;         // Path del file    
b = Buffer.read(s,p);                   // Lo carica interamente nel Buffer

SynthDef(\buf1, {arg buf=0,atk=0,rel=0.01,amp=1,loop=0,gate=0;
                 var sig,dur,fade,env;
                     sig  = PlayBuf.ar(2, buf, loop:loop);  // PlayBuf al posto di DiskIn
                     dur  = BufDur.kr(buf);
                     fade = Linen.kr(gate,atk,amp.lag(0.2),rel,doneAction:2);
                     env  = Env.linen(atk,dur-atk-rel,rel).kr(0,Impulse.kr(1/dur));
                 Out.ar(0,sig * env * fade * amp)
                 }).add;

{
a = Synth(\buf1, [\buf,b,
                  \atk,   1,   // Tempo di Fade in
                  \rel,   5,   // Tempo di Fade Out
                  \amp,   1,
                  \loop,  1,
                  \gate,  1    // Primo e ultimo inviluppo
                  ])
}.defer(0.5)
)

a.release(0.2);  
a.set(\loop,0);   

Impiegando questa tecnica a differenza della precedente possiamo creare quante istanze di player vogliamo senza particolari problemi in quanto l'accesso alla memoria temporanea del computer (nella quale sono allocati i Buffer) è molto meno complesso rispetto all'accesso alla memoria su supporto.

Come regola generale è da preferire sempre questa seconda modalità tranne nel caso di Sound files particolarmente lunghi che occuperebbero troppa memoria temporanea.

Cross players

Nella programmazione di playback complessi all'interno di un brano di musica mista o di uno spettacolo teatrale, può essere pratico utilizzare due o più players che si alternano automaticamente nella riproduzione di una sequenza di Buffers (o sound files) realizzati attraverso il taglio in più parti (cues) di una registrazione di ampia durata.

Rispetto all'utilizzo di un solo soundfile dall'inizio alla fine del brano, questa tecnica infatti ci permette maggiore flessibilità sia nella gestione del tempo musicale o drammaturgico che nella gestione pratica delle prove attraverso la possibilità di sovrapporre per qualche secondo il suono del player che finisce con quello che comincia. Così facendo liberiamo l'esecutore della parte strumentale o l'attore o il performer dalla preoccupazione di dover seguire uno stretto tempo cronometrico, limitiamo i danni in caso di eventuali errori umani che potrebbero accadere nel corso della performance e abbiamo la possibilità di riprendere l'esecuzione da più punti nel corso del brano durante le prove.

image not found

Ovviamente non è la tecnica più adatta a brani musicali caratterizzati da griglie ritmiche estrememente precise. Bisognerà inoltre scegliere accuratamente gli onsets nella sequenza dei diversi Buffers che in generale dovranno coincidere con necessità di sincronizzazione tra la parte registrata e la parte eseguita dagli strumenti acustici.

(
Buffer.freeAll;
b = 10.collect({var onset,dur;            // Collezione di cues (possono essere anche 
                    onset = rand(10.0);   // di diversi sound files) e non scelti 
                    dur   = rrand(0.1,3); // randomicamente come in questo caso...
					
                Buffer.read(s,"bach.wav".resolveRelative,
                            onset*s.sampleRate,      // Onsets in frames
                            onset+dur*s.sampleRate)  // Durate in frames
                });
)

(
SynthDef(\cross, {arg buf=0, amp=0, fade=0.1, gate=0;
                  var sig,dur,fad,env;
                      sig  = PlayBuf.ar(2, buf);
                      dur  = BufDur.kr(buf);
                      fad  = Linen.kr(gate,fade,amp.lag(0.2),fade,doneAction:2); // Crossfade
                      env  = EnvGen.kr(Env.linen(0.2,dur-0.4,0.2),gate,doneAction:2);
                  Out.ar(0,sig * env * fad)
         }).add;

~cue = -1; // Valore di inizializzazione per cominciare dal cue 0
)

(                          // Ad ogni valutazione procede al Buffer successivo
~cue  = ~cue + 1 % b.size; // Counter per leggere i cues in sequenza con ordine crescente
~cue.postln;

if(s.numSynths > 0, {a.set(\gate,0)});                     // PRIMA Fade Out e distruzione
a = Synth(\cross, [\fade,2,\amp,1,\buf, b[~cue],\gate,1]); // POI creazione e Fade In
)					

In questo codice i players si creano e autodistruggono automaticamente e a differenza dei Synth creati in precedenza abbiamo stabilito un solo tempo di fade e tolto l'opzione di loop.

Una tecnica classica consiste nell'assegnare alla barra spaziatrice il trigger del counter per fare progredire i cues da visualizzare su di una GUI. Possiamo anche navigare tra i cues con le frecce della tastiera o scrivere direttamente nel NumberBox il valore del cue successivo che sarà però triggerato solo con la barra spaziatrice.

(
Buffer.freeAll;
b = 10.collect({var onset,dur;                       // Collezione di cues
                    onset = rand(10.0);
                    dur   = rrand(2,5);
	            Buffer.read(s,"bach.wav".resolveRelative,
		                    onset*s.sampleRate,      
		                    onset+dur*s.sampleRate)  
                });

SynthDef(\cross, {arg buf=0, amp=0, fade=0.1, gate=0;
                  var sig,dur,fad,env;
                      sig  = PlayBuf.ar(2, buf);
                      dur  = BufDur.kr(buf);
                      fad  = Linen.kr(gate,fade,amp.lag(0.2),fade,doneAction:2);     
                      env  = EnvGen.kr(Env.linen(0.2,dur-0.4,0.2),gate,doneAction:2);
                 Out.ar(0,sig * env * fad)
         }).add;

~cue = -1; // Valore di inizializzazione per cominciare dal cue 0

w = Window.new("Num", Rect.new(0,0,300,300));
h = NumberBox.new(w, Rect(5,5,290,290)).align_(\center).font_(Font("Helvetica", 200););

w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free;h.free});

w.view.keyDownAction_({arg ...args;
                       switch(args[3],
                              32, {~cue  = ~cue + 1 % b.size;  // barra spazio --> incrementa
                                   h.value = ~cue;             // visualizza
                                   if(s.numSynths > 0, {a.set(\gate,0)});      // Fade out               
                                   a = Synth(\cross, [\fade,2,\amp,1,\buf, b[~cue],\gate,1])},
										  
                              105, {~cue = -1;                  // 'i' --> reset a 0
                                    h.value = 0;
                                    if(s.numSynths > 0, {a.set(\gate,0)}); })
                       });
					   
h.action_({arg val;~cue = val.value-1}); // Recupera i valori dalle azioni sulla GUI
)	

Campionatori [...]