Quadrifonia
Oltre a sistemi sistemi di diffusione del suono monofonici o stereofonici utilizzati prevalentemente per la fruizione musicale (e multimediale) di largo consumo (radio, televisione, sistemi Hi-Fi, videogiochi, etc.) oppure per concerti e spettacoli pop, rock e jazz, nel corso del secolo scorso alcuni compositori di musica colta prima e i produttori di sistemi di diffusione per l'audio cinematografico in seguito hanno sviluppato per ragioni differenti numerose tecnologie per la diffusione multicanale ovvero attraverso più altoparlanti disposti in vari modi. La maggior parte di queste tecnologie erano (e sono in quanto la ricerca in questo ambito è ancora molto attiva) quasi tutte volte alla ricostruzione di uno spazio acustico virtuale atto a fornire un'esperienza d'ascolto di tipo immersivo sviluppata a 360° attorno all'ascoltatore. Anche in questi casi sono validi i diversi modi di pensare lo spazio esposti all'inizio del paragrafo precedente che si differenziano ora anche nell'utilizzo di UGens dedicate. Così come abbiamo fatto per la stereofonia, nei prossimi sottoparagrafi ci occuperemo solamente delle principali tecniche di panning multicanale realizzabili in SuperCollider, tralasciando diverse tecniche di spazializzazione che abbiamo a disposizione ai nostri giorni come gli acousmonium, la registrazione e diffusione bineaurale, la wave field synthesis, vbap, i sistemi ambisonic e gli array o cupole di altoparlanti. Chi volesse approfondire queste tecniche può trovare un valido supporto introduttivo nel Capitolo dedicato scritto da Marije A. J. Baalman e Scott Wilson del "The SuperCollider book". Il più tradizionale tra i sistemi di diffusione multicanale è la quadrifonia ovvero il delimitare la spazio acustico all'interno del quale si trova il pubblico con quattro altoparlanti posti idealmente agli angoli di un quadrato immaginario come illustrato nell'immagine seguente:
Possiamo notare come la numerazione degli altoparlanti (L1 - L4) può essere effettuata in due modi: per coppie stereofoniche (a sinistra) oppure circolare (a destra). Notiamo anche come in questo caso i bus di uscita da SupercCollider (da 0 a 3) rimangono invariati in entrambe le numerazioni. Esattamente come abbiamo fatto per il fronte stereofonico, dobbiamo suddividere l'ampiezza di ogni sorgente (segnale monofonico) non su due ma su quattro altoparlanti e per farlo possiamo considerare il quadrato delimitato dagli altoparlanti come un piano cartesiano sul quale andiamo a specificare ogni punto come coordinate x/y comprese tra 0.0 e 1.0 Il valore di x rappresenterà la posizione sul fronte stereofonico mentre il valore di y la profondità:
Il calcolo delle ampiezze di ogni altoparlante relative alla posizione della sorgente è illustrato nell'immagine seguente e può tornarci utile nell'impiego di softwares differenti da SuperCollider:
Pan4.ar()
Nel caso volessimo pensare lo spazio come nella tipologia descritta al punto 1 all'inizio del paragrafo precedente potremmo utilizzare una UGen dedicata: Pan4.ar() che ha come argomenti gli stessi parametri appena esposti ed effettua automaticamente il calcolo delle ampiezze relative alla posizione:
Pan4.ar(sig, x, y, level)
Come per Pan2.ar() i valori di x e y devono essere espressi in un ambito compreso tra +/- 1.0 mentre l'argomento level anche se presente in questo caso non è utilizzato per simulare la distanza ma per fare degli eventuali bilanciamenti o compensazioni di ampiezza tra sorgenti diverse presenti nello stesso spazio acustico virtuale.
s.options.numOutputBusChannels_(4); s.reboot; s.plotTree; s.scope; s.meter; ( SynthDef(\quadri, {arg x=0,y=0,smt=0.2; var sig,env,pan; sig = SinOsc.ar(Rand(400,2000)); env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); pan = Pan4.ar(sig*env,x.lag(smt),y.lag(smt)); Out.ar(0,pan) } ).add; {~synth = Synth(\quadri)}.defer(0.1); ) ~synth.set(\x,0,\y,0); // centro ~synth.set(\x,-1,\y,1); // L1 - bus 0 ~synth.set(\x,0, \y,1); // centro al fondo ~synth.set(\x,1, \y,1); // L2 - bus 1 ~synth.set(\x,1, \y,0); // centro a destra ~synth.set(\x,1,\y,-1); // L4 - bus 3 ~synth.set(\x, 0, \y,-1);// centro di fronte ~synth.set(\x,-1,\y,-1); // L3 - bus 2 ~synth.set(\x, -1, \y,0);// centro a sinistra ~synth.set(\x,0,\y,0); // centro ~synth.set(\x,rand2(1.0),\y,rand2(1.0));
Se invece vogliamo pensare lo spazio come al punto 2 tra quelli descritti all'inizio del paragrafo precedente ovvero alla quadrifonia come specifico sistema di diffusione vale tutto quanto diremo nel paragrafo seguente riguardo pentafonia, esafonia e ottofonia anche se quattro diffusori soli non si prestano particolarmente a tecniche di panning circolare o dedicato.
GUI per la quadrifonia
Per quanto riguarda la quadrifonia realizzata con la UGen Pan4.ar() dove come abbiamo appena visto le posizioni della sorgente sono specificate sotto forma di coordinate cartesiane (x/y) entro i limiti di +/-1.0:
per il controllo e la visualizzazione possiamo utilizzare Slider2D.ar()) mappando le coordinate in modo diretto (x --> x e y --> y) riscalando in modo corretto i valori.
( // ------------------------------ SynthDef e Synth SynthDef(\quadri_gui, {arg x=0.0,y=0.0,dur=0.2,dist=1.0; var sig,xpos,ypos,sdist,env,pan; sig = SinOsc.ar(Rand(400,2000)); xpos = VarLag.kr(x,dur); ypos = VarLag.kr(y,dur); sdist = Lag.kr(dist,dur); env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); pan = Pan4.ar(sig*env,xpos,ypos); SendReply.kr(Impulse.kr(50), '/pos', [xpos,ypos]); Out.ar(0,pan) } ).add; {~synth = Synth(\quadri_gui)}.defer(0.1); // ------------------------------ GUI w = Window.new("Pan", 200@220); w.alwaysOnTop; w.front; w.onClose_({~synth.free;w.free;e.free;c.free}); b = NumberBox.new(w, Rect(40,195,50,20)).value_(0); // NumberBox X c = NumberBox.new(w, Rect(120,195,50,20)).value_(0); // NumberBox Y d = StaticText.new(w,Rect(25,198,180,15)) // Static text .string_("X: Y:"); e = Slider2D.new(w, Rect(10, 10, 180, 180)) // Slider2D .x_(0.5).y_(0.5); OSCdef.new(\vedi, {arg msg; {b.value_(msg[3]); // al Number Box X (+/-1) c.value_(msg[4]); // al Number Box Y (+/-1) e.x_(msg[3].linlin(-1,1,0,1)); // alla x dello Slider2D e.y_(msg[4].linlin(-1,1,0,1)) // alla y dello Slider2D }.defer(0) },'/pos', s.addr); // ------------------------------ Operazioni di basso livello e.action_({arg i; ~synth.set(\x,i.x.linlin(0,1,-1,1), // al Synth (+/-1) \y,i.y.linlin(0,1,-1,1)); }); MIDIIn.connectAll; MIDIdef.cc(\knob, {arg val; ~synth.set(\x,val.linlin(0,127,-1,1)); // al Synth (+/-1) }, 16); // dal cc 16 MIDIdef.cc(\slid, {arg val; ~synth.set(\y,val.linlin(0,127,-1,1)); // al Synth }, 0); // dal cc 0 ) // ------------------------------ Sequencing ~synth.set(\x,rand2(1.0),\y,rand2(1.0),\dur,rand(2.0));
Movimenti dinamici
Client side
Il codice appena illustrato può essere utile per tutte le modalità di controllo da Interprete (dal codice, da GUI e da devices), utilizziamolo allora per illustrare alcune tecniche di sequencing.
Singolo valore. Per quanto riguarda l'invio di singoli valori vale quanto detto nel paragrafo corrispondente riguardante il panning stereo, ovvero possiamo specificare movimenti in qualsiasi punto nello spazio attraverso tre valori (x, y, durata):
(x:tra +/- 1.0,y:tra +/- 1.0, dur:tempo)
~synth.set(\x,0,\y,0,\dur,2); // centro ~synth.set(\x,-1,\y,1,\dur,2); // L1 - bus 0 ~synth.set(\x,0,\y,1,\dur,2); // centro al fondo ~synth.set(\x,1,\y,1,\dur,2); // L2 - bus 1 ~synth.set(\x,1,\y,0,\dur,2); // centro a destra ~synth.set(\x,1,\y,-1,\dur,2); // L4 - bus 3 ~synth.set(\x,0,\y,-1,\dur,2); // centro di fronte ~synth.set(\x,-1,\y,-1,\dur,2);// L3 - bus 2 ~synth.set(\x,-1,\y,0,\dur,2); // centro a sinistra ~synth.set(\x,0,\y,0,\dur,0); // centro ~synth.set(\x,rand2(1.0),\y,rand2(1.0),\dur,rand(2.0));
( var pos; pos = [[0,1,1],[-0.4,0.4,0.3],[0.6,-0.3,2],[0,0.4,1],[1,0,3],[-1,1,5]]; // [x,y,time] h = Routine.new({ pos.do({arg i; ~synth.set(\x,i[0],\y,i[1],\dur,i[2]); i[2].wait }) }).reset.play; ) h.stop;~synth.set(\x,0,\y,0,\dur,0);
( var x,y,dur; h = Routine.new({ inf.do({x = rand2(1.0); y = rand2(1.0); dur = rrand(0.01,3); ~synth.value(\x,x,\y,y,\dur,dur); dur.wait }) }).reset.play ) h.stop;~synth.set(\x,0,\y,0,\dur,0);
Rota. In questo caso rappresenta una rotazione sequenziale tra gli altoparlanti che può avvenire sia in senso orario che antiorario e rappresenta una delle prime tecniche di spazializzazione utilizzate nella musica elettronica:
// Senso orario ( var ciclo; ciclo = 4; // tempo di giro in secondi h = Routine.new({ inf.do({var dur = ciclo*0.25; // pos ~synth.set(\x,-1,\y,1,\dur,dur); dur.wait; ~synth.set(\x,1,\y,1,\dur,dur); dur.wait; ~synth.set(\x,1,\y,-1,\dur,dur); dur.wait; ~synth.set(\x,-1,\y,-1,\dur,dur); dur.wait; }) }).reset.play ) h.stop;~synth.set(\x,0,\y,0,\dur,0); // Senso antiorario ( var ciclo; ciclo = 4; // tempo di giro in secondi h = Routine.new({ inf.do({var dur = ciclo*0.25; // pos ~synth.set(\x,-1,\y,1,\dur,dur); dur.wait; ~synth.set(\x,-1,\y,-1,\dur,dur); dur.wait; ~synth.set(\x,1,\y,-1,\dur,dur); dur.wait; ~synth.set(\x,1,\y,1,\dur,dur); dur.wait; }) }).reset.play ) h.stop;~synth.set(\x,0,\y,0,\dur,0);
Spreading. Anche in questo caso basta aggiungere la coppia di valori che definisce l'asse delle x.
( var rx,ry,dur; rx = [-1,0]; ry = [1,0]; dur = 0.1; ~synth.set(\dur,dur); h = Routine.new({ inf.do({var x,y; x = rrand(rx[0],rx[1]); y = rrand(ry[0],ry[1]); ~synth.set(\x,x,\y,y,\dur,dur); dur.wait }) }).reset.play ) h.stop;~synth.set(\x,0,\y,0,\dur,0);
Devices OSC. Possiamo controllare facilmente la quadrifonia con un devices esterno o un altro software attraverso il protocollo OSC. In questo caso scarichiamo e lanciamo il patch di Max dal quale contolleremo la quadrifonia in SuperCollider.
( // ------------------------------ SynthDef e Synth SynthDef(\quadri_gui, {arg x=0.0,y=0.0,dur=0.2,dist=1.0; var sig,xpos,ypos,sdist,env,pan; sig = SinOsc.ar(Rand(400,2000)); xpos = VarLag.kr(x,dur); ypos = VarLag.kr(y,dur); sdist = Lag.kr(dist,dur); env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); pan = Pan4.ar(sig*env,xpos,ypos); SendReply.kr(Impulse.kr(50), '/posi', [xpos,ypos]); Out.ar(0,pan) } ).add; {~synth = Synth(\quadri_gui)}.defer(0.1); // ------------------------------ GUI w = Window.new("Pan", 200@220); w.alwaysOnTop; w.front; w.onClose_({~synth.free;w.free;b.free;c.free;d.free;e.free}); b = NumberBox.new(w, Rect(40,195,50,20)).value_(0); // NumberBox X c = NumberBox.new(w, Rect(120,195,50,20)).value_(0); // NumberBox Y d = StaticText.new(w,Rect(25,198,180,15)) // Static text .string_("X: Y:"); e = Slider2D.new(w, Rect(10, 10, 180, 180)) // Slider2D .x_(0.5).y_(0.5); OSCdef.new(\vedi, {arg msg; {b.value_(msg[3]); // al Number Box X (+/-1) c.value_(msg[4]); // al Number Box Y (+/-1) e.x_(msg[3].linlin(-1,1,0,1)); // alla x dello Slider2D e.y_(msg[4].linlin(-1,1,0,1)) // alla y dello Slider2D }.defer(0) },'/posi', s.addr); OSCdef.new(\daMax, {arg msg; ~synth.set(\x, msg[1].linlin(0,127,-1,1), // al Synth \y, msg[2].linlin(0,127,-1,1));}, '/pos', recvPort:57120); // ------------------------------ Operazioni di basso livello e.action_({arg i; ~synth.set(\x,i.x.linlin(0,1,-1,1), // al Synth (+/-1) \y,i.y.linlin(0,1,-1,1)); }); )
Server side
Segnali
Se vogliamo utilizzare segnali di controllo possiamo modificare il codice realizzato per il panning stereofonico, aggiungendo un secondo segnale di controllo per il parametro y con i propri argomenti specifici e sostituendo la GUI per la visualizzazione con uno Slider2D:
( // ------------------------------ SynthDef e Synth SynthDef(\quad_sig, {arg tipoX=1,velX=0.5,sprdX=1,initposX= 0, tipoY=1,velY=0.5,sprdY=1,initposY= 0.5pi, smt=0.2; var sig,env,ksigs,x,y,pan; sig = SinOsc.ar(Rand(400,2000)); env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); x = Select.kr(tipoX,[LFTri.kr(velX,initposX,sprdX), SinOsc.kr(velX,initposX,sprdX), LFNoise0.kr(velX,sprdX), LFNoise1.kr(velX,sprdX), LFNoise2.kr(velX,sprdX), ]); y = Select.kr(tipoY,[LFTri.kr(velY,initposY,sprdY), SinOsc.kr(velY,initposY,sprdY), LFNoise0.kr(velY,sprdY), LFNoise1.kr(velY,sprdY), LFNoise2.kr(velY,sprdY), ]); SendReply.kr(Impulse.kr(50), '/pos', [x,y]); // Invia Slider2D pan = Pan4.ar(sig*env,x,y); Out.ar(0,pan) } ).add; {~synth = Synth(\quad_sig)}.defer(0.1); // ------------------------------ GUI w = Window.new("Pan", 210@210); w.alwaysOnTop; w.front; w.onClose_({~synth.free;w.free;d.free;a.free}); d = Slider2D.new(w, Rect(5, 5, 200, 200)); // Slider2D OSCdef.new(\vedi, {arg msg; {d.x_(msg[3].linlin(-1,1,0,1)); // solo all'interfaccia d.y_(msg[4].linlin(-1,1,0,1))}.defer(0); },'/pos', s.addr); ) ~synth.run(false); ~synth.run(true);
Nel codice sottostante alcune combinazioni tipiche della musica elettronica:
~synth = Synth(\quad_sig,[\tipoX, 3, \tipoY, 3]).run; // Random ~synth.free; ~synth = Synth(\quad_sig,[\initposX,0,\initposY,0.5pi]).run; // Senso orario ~synth.run(false); ~synth = Synth(\quad_sig,[\initposX,0.5pi,\initposY,0]).run; // Senso anti-orario ~synth.run(false); ~synth.set(\tipoX,rand(4),\tipoY,rand(4)).run; // Combinazioni di segnali ~synth.run(false); ~synth.set(\tipoX,1,\tipoY,1,\velX,rrand(0.2,1),\velY,rrand(0.2,1)).run; // Combinazioni di velocità
Infine un controllo misto con una GUI dove un Knob controlla la velocità di rotazione mentre uno slider la distanza dal centro:
( // ------------------------------ SynthDef e Synth SynthDef(\quad_sig, {arg tipoX=1,velX=0.5,sprdX=1,initposX= 0, tipoY=1,velY=0.5,sprdY=1,initposY= 0.5pi, smt=0.2; var sig,env,ksigs,x,y,pan; sig = SinOsc.ar(Rand(400,2000)); env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); x = Select.kr(tipoX,[LFTri.kr(velX,initposX,sprdX), SinOsc.kr(velX,initposX,sprdX), LFNoise0.kr(velX,sprdX), LFNoise1.kr(velX,sprdX), LFNoise2.kr(velX,sprdX), ]); y = Select.kr(tipoY,[LFTri.kr(velY,initposY,sprdY), SinOsc.kr(velY,initposY,sprdY), LFNoise0.kr(velY,sprdY), LFNoise1.kr(velY,sprdY), LFNoise2.kr(velY,sprdY), ]); SendReply.kr(Impulse.kr(50), '/pos', [x,y]); // Invia Slider2D pan = Pan4.ar(sig*env,x,y); Out.ar(0,pan) } ).add; {~synth = Synth(\quad_sig)}.defer(0.1); // ------------------------------ GUI w = Window.new("Pan", 270@210); w.alwaysOnTop; w.front; w.onClose_({~synth.free;w.free;b.free;c.free;d.free}); b = Knob.new(w, Rect(210, 5, 50, 50)); // Knob c = Slider.new(w, Rect(210, 55, 50, 150)); // Slider d = Slider2D.new(w, Rect(5, 5, 200, 200)); // Slider2D // ------------------------------ Operazioni di basso livello b.action_({arg i; ~synth.set(\velX,i.value.linlin(0,1,0.1,5), // solo al Synth \velY,i.value.linlin(0,1,0.1,5))}); c.action_({arg i; ~synth.set(\sprdX,i.value,\sprdY,i.value)}); OSCdef.new(\vedi, {arg msg; {d.x_(msg[3].linlin(-1,1,0,1)); // solo all'interfaccia d.y_(msg[4].linlin(-1,1,0,1))}.defer(0); },'/pos', s.addr); MIDIIn.disconnectAll; MIDIIn.connectAll; MIDIdef.cc(\knob, {arg val; ~synth.set(\velX,val.linlin(0,127,0.1,5), // al Synth \velY,val.linlin(0,127,0.1,5)); {b.value_(val.linlin(0,127,0,1))}.defer(0) // al Knob }, 16); // dal cc 16 MIDIdef.cc(\slid, {arg val; ~synth.set(\sprdX,val.linlin(0,127,0,1), // al Synth \sprdY,val.linlin(0,127,0,1)); {c.value_(val.linlin(0,127,0,1))}.defer(0) // allo Slider }, 0); // dal cc 0 )
Inviluppi
Anche per quanto riguarda gli inviluppi possiamo modificare il codice già illustrato per il panning stereofonico:
( // ------------------------------ SynthDef e Synth SynthDef(\penvi, {arg t_gate=0; var sig,env,x,xbpf,xsenv,y,ybpf,ysenv,pan; sig = SinOsc.ar; env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); x = Env.newClear(4); // Crea un inviluppo vuoto di 4 nodi xbpf= \x.kr(x.asArray); // Crea un controllo dell'inviluppo xsenv= EnvGen.kr(xbpf, t_gate); // Genera l'inviluppo y = Env.newClear(4); ybpf= \y.kr(y.asArray); ysenv= EnvGen.kr(ybpf, t_gate); pan = Pan4.ar(sig*env,xsenv,ysenv); SendReply.kr(Impulse.kr(50), '/pos', [xsenv,ysenv]); Out.ar(0,pan) } ).add; {~synth = Synth(\penvi)}.defer(0.1); // ------------------------------ GUI w = Window.new("Pan", 210@210); w.alwaysOnTop; w.front; w.onClose_({~synth.free;w.free;d.free}); d = Slider2D.new(w, Rect(5, 5, 200, 200)); // Slider2D OSCdef.new(\vedi, {arg msg; {d.x_(msg[3].linlin(-1,1,0,1)); d.y_(msg[4].linlin(-1,1,0,1))}.defer(0); },'/pos', s.addr); ) // ------------------------------ Sequencing ( h = 2; // Definiamo una durata u = {[{rand2(1.0)}!4, {rand(0.5)}!3]}!2; // generiamo i valori di x e y u.postln; // stampa i valori x = Env.new(u[0][0],u[0][1]).duration_(h); y = Env.new(u[1][0],u[1][1]).duration_(h); [x,y].plot; // Plotter ~synth.set(\x, x, \y, y, \t_gate,1); // li inviamo al Synth )
Mouse
Il codice per il controllo del pan quadrifonico con il mouse è molto semplice:
( SynthDef(\pmouse, {var sig,env,x,y,pan; sig = SinOsc.ar; env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); x = MouseX.kr(-1,1); y = MouseY.kr(-1,1); // vicino in basso pan = Pan4.ar(sig*env,x,y); Out.ar(0,pan) } ).add; {a = Synth(\pmouse)}.defer(0.1); )
Anche in questo caso non è necessaria una GUI in quanto lo spazio è delimitato dallo schermo del computer.