Tipologie di controllo
Il mouse del computer può diventare uno strumento di controllo dinamico di uno o più parametri di un qualsiasi algoritmo di sintesi o di elaborazione del suono (interazione uomo/macchina).
Utilizzando questo strumento abbiamo a disposizione due possibili tipologie di controllo:
Controllo continuo: trascinando il mouse otteniamo un flusso continuo di valori limitrofi.
Controllo discreto: cliccando sui pulsanti presenti (due o tre) otteniamo valori discreti (campionati) che possiamo eventualmente considerare come trigger(s) di eventi.
Il mouse può diventare sia uno strumento musicale indipendente che un semplice strumento di servizio utile all'interazione con oggetti grafici (GUI).
Nello specifico ambiente di SuperCollider abbiamo tre diverse possibilità per recuperare i valori di alcuni parametri del mouse per poi eventualmente mapparli su di un qualche parametro di sintesi o di elaborazione del suono:
Client side: i valori sono recuperati discretamente nell'Interprete ed eventualmente inviati al Server nel modo usuale.
Server side: i valori sono recuperati direttamente nel Server sotto forma di segnali di controllo.
Server to Client: i valori sono recuperati nel Server come segnali di controllo per poi essere discretizzati (sottocampionati) e inviati all'Interprete.
Client side
Se vogliamo interagire con il mouse del computer e recuperare i valori nell'Interprete dobbiamo obbligatoriamente creare una finestra GUI per poi invocare su di essa diversi metodi. Questo è necessario per evitare che cliccando sullo schermo si selezionino involontariamente altre applicazioni nel corso di una performance aprendo così la possibilità a spiacevoli inprevisti. Ad ogni metodo corrisponde un'azione differente:
.mouseDownAction_({})
Valuta la funzione specificata come argomento ogni volta che clicchiamo sul bottone del mouse (trigger):
( w = Window.new("mouse"); w.view.mouseDownAction_({arg ...args; args.postln; // tutte le info args[1].postln; // solo posizione x }); w.front; w.alwaysOnTop_(true); w.onClose_({w.free}); )
In questo caso ogni volta che clicchiamo con un tasto del mouse otteniamo un Array i cui items corrispondono a precise informazioni e che possimo richiamare singolarmente con i metodi [].at(id) oppure Array[id]:
- [0] view = la finestra sulla quale abbiamo cliccato
- [1] x = il punto sull'asse x nella finestra in pixel (0 = punto più a sinistra)
- [2] y = il punto sull'asse y nella finestra in pixel (0 = punto più in alto)
- [3] modifiers = se c'è un tasto modificatore premuto
- [4] botton n = numero del bottone premuto (0 = sinistro, 1 = destro, 2 = centrale)
- [5] counter = il numero di click veloci (1 = singolo, 2 = doppio click)
Se recuperiamo i singoli items e li mappiamo su diversi parametri di uno o più algoritmi di sintesi abbiamo a disposizione uno strumento di controllo piuttosto complesso.
( s.boot; s.scope(1); s.plotTree; w = Window.new("mouse"); w.view.mouseDownAction_({arg ...args; if(args[4] == 0, {Synth(\sine,[\freq, args[1].linlin(0,400,300,2000), // Se clicco tasto sx... \amp,1-args[2].linlin(0,400,0,1.0), \t_gate, 1])}, {a.set(\amp, 1-args[2].linlin(0,400,0,1.0))}) // Se clicco tasto dx... }); w.front; w.alwaysOnTop_(true); w.onClose_({w.free;s.freeAll}); SynthDef(\sine , {arg freq=0, amp=0, t_gate=0; var sig,env; // Con inviluppo d'ampiezza sig = SinOsc.ar(freq); env = EnvGen.kr(Env.perc,t_gate,doneAction:2); Out.ar(0,sig*env*amp); }).add; SynthDef(\noise , {arg amp=0; var sig; sig = PinkNoise.ar; Out.ar(0,sig*amp.lag(3)); // fade dell'ampiezza di 3 secondi }).add; {a = Synth(\noise)}.defer(0.2); )
.mouseUpAction_({})
Esattamente come il precedente ma riporta le informazioni al rilascio del tasto.
( w = Window.new("mouse"); w.view.mouseUpAction_({arg ...args; args.postln; // tutte le info args[1].postln; // solo posizione x }); w.front; w.alwaysOnTop_(true); w.onClose_({w.free}); )
Possiamo ad esempio utilizzarlo in combinazione con .mouseDownAction_({}) come note On/note Off del generatore di rumore rosa nell'esempio precedente:
( s.boot; s.scope(1); s.plotTree; w = Window.new("mouse"); w.view.mouseDownAction_({arg ...args; if(args[4] == 0, {Synth(\sine,[\freq, args[1].linlin(0,400,300,2000), \amp,1-args[2].linlin(0,400,0,1.0), \t_gate, 1])}, {a.set(\amp, args[2].linlin(0,400,0,1.0))}) // note On (proporzioni invertite) }); w.view.mouseUpAction_({arg ...args; a.set(\amp,0) // note Off }); w.front; w.alwaysOnTop_(true); w.onClose_({w.free;s.freeAll}); SynthDef(\sine , {arg freq=0, amp=0, t_gate=0; var sig,env; sig = SinOsc.ar(freq); env = EnvGen.kr(Env.perc,t_gate,doneAction:2); Out.ar(0,sig*env*amp); }).add; SynthDef(\noise , {arg amp=0; var sig; sig = PinkNoise.ar; Out.ar(0,sig*amp.lag(0.2)); // fade di 0.2 secondi }).add; {a = Synth(\noise)}.defer(0.2); )
.mouseMoveAction_({})
Riporta le informazioni solo quando muoviamo il mouse con il bottone premuto ritornando solo i primi quattro argomenti: [view,x,y,modifiers]. A differenza dei due controlli precedenti che potevano essere utilizzati solo come triggers di valori discreti in questo caso abbiamo anche un flusso continuo di valori limitrofi.
( w = Window.new("mouse"); w.view.mouseMoveAction_({arg ...args; args.postln; // tutte le info args[1].postln; // solo posizione x }); w.front; w.alwaysOnTop_(true); w.onClose_({w.free}); )
Nell'esempio seguente viene illustrata una combinazione di controlli discreti (tirgger) e continui (numeric stream) nonchè una gestione corretta della polifonia (voice allocation)
( s.boot; s.scope(1); s.plotTree; w = Window.new("mouse"); w.view.mouseDownAction_({arg ...args; // note On (trigger) a = Synth(\spectra, [\freq, args[1].linlin(0,400,300,2000), \gate, 1]); }); w.view.mouseMoveAction_({arg ...args; // controllo continuo a.set(\amp,1-args[2].linlin(0,400,0,1.0), \harm, args[1].linlin(0,400,1,20)) }); w.view.mouseUpAction_({arg ...args; a.set(\gate,0) // note Off (trigger) }); w.front; w.alwaysOnTop_(true); w.onClose_({w.free;s.freeAll}); SynthDef(\spectra, {arg freq=0, amp=0, harm=1, gate=0; var sig,env; sig = Blip.ar(freq,harm); env = EnvGen.kr(Env.asr(0.3,1,5),gate,doneAction:2); Out.ar(0,sig*env*amp); }).add; )
.mouseOverAction_({})
Come il precedente ma riporta le informazioni quando muoviamo il mouse sulla Window anche senza premere il bottone ritornando solo i primi tre argomenti [view,x,y]. In questo caso otteniamo solo un flusso di dati e non un trigger. Quando creiamo la window dobbiamo invocare su di essa il metodo .acceptsMouseOver_(true);
( w = Window.new("mouse").acceptsMouseOver_(true);// bisogna specificare; w.view.mouseOverAction_({arg ...args; args.postln; // tutte le info args[1].postln; // solo posizione x }); w.front; w.alwaysOnTop_(true); w.onClose_({w.free}); )
Passando sopra la finestra effettueremo uno scratch.
( s.boot; s.scope(1); s.plotTree; w = Window.new("mouse").acceptsMouseOver_(true); w.view.mouseOverAction_({arg ...args; a.set(\pos, args[1].linlin(0,400,0,1), // posizione x \amp,1-args[2].linlin(0,400,0,1.0)) // posizione y }); w.front; w.alwaysOnTop_(true); w.onClose_({w.free;a.free;b.free}); b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav"); SynthDef(\scratch, {arg pos=0, amp=0, buf=0, smooth=0.2; var sig,punt; punt = BufFrames.ir(buf)*K2A.ar(pos).varlag(smooth); sig = BufRd.ar(1,b,punt); Out.ar(0,sig*amp.lag(0.2)) }).add; {a = Synth(\scratch, [\buf,b])}.defer(0.2); )
Server side
Se vogliamo recuperare i valori dell'interazione con il mouse direttamente nel Server sotto forma di segnali di controllo abbiamo a disposizione tre diverse UGens e non è necessario creare una GUI:
- MouseX.kr() - riporta la posizione del mouse nello schermo sull'asse delle ascisse.
- MouseY.kr() - riporta la posizione del mouse nello schermo sull'asse delle ordinate.
- MouseButton.kr() - riporta le interazioni con i bottoni.
Tutte e tre hanno gli stessi argomenti ad eccezione della curva che in MouseButton.kr() ovviamente non può essere specificata:
- 0 = curva (interpolazione) lineare tra i valori.
- 1 = curva (interpolazione) esponenziale tra i valori (non è possibile specificare 0).
MouseX.kr(valore a sinistra, valore a destra, curva, tempo_di_Lag)
{[MouseX.kr(0,1,0,0.2), MouseX.kr(0.0001,1,1,0.2)]}.scope; // lineare vs esponenziale {[MouseX.kr(0,1,0,5), MouseX.kr(0.0001,1,1,5)]}.scope; // lag time di 5 secondi...
Ecco un esempio di J.Parmenter che utilizza due delle UGens appena illustrate:
( s.boot; s.scope(1); s.plotTree; a = Buffer.read(s, "/Applications/SuperCollider/SuperCollider.app/Contents/Resources/sounds/a11wlk01.wav"); SynthDef(\scratch, {arg gate = 1, buffer; var buf, speed, env; env = Env.new([0,1,0], [0.1, 0.1], \sin, 1).kr(20,gate); speed = MouseX.kr(-10, 10); speed = speed - DelayN.kr(speed, 0.1, 0.1); speed = MouseButton.kr(1, 0, 0.3) + speed ; buf = PlayBuf.ar(1, buffer, speed * BufRateScale.kr(buffer), loop: 1); Out.ar(0, (buf * env).dup ); }).add; {b = Synth(\scratch, [\buffer, a])}.defer(0.3); ) b.set(\gate, 0); a.free; b.free;
Server to Client
Se infine vogliamo utilizzare i valori generati delle UGens appena illustrate (ad esempio per visualizzare su una GUI l'interazione) dobbiamo inviare messaggi OSC dal Server all'Interprete attraverso l'utilizzo congiunto della UGen SendReply.kr() e della Classe OSCFunc.new({}):
( s.boot; s.meter(2); s.plotTree; // ------------------------------ SynthDef SynthDef(\mouseval, {var x,y,click,sig; x = MouseX.kr(0,1); // Panpot... y = MouseY.kr(0.0001,1,1); // Ampiezza...no 0... click = MouseButton.kr.round; // Note on/off SendReply.kr(Impulse.kr(50), // rata di campionamento '/pos', // indirizzo o nome [x, y, click]); // segnali da campionare sig = Pan2.ar(SinOsc.ar*y*click,x*2-1); // Audio Out.ar(0,sig) } ).add; {a = Synth(\mouseval)}.defer(0.1); // ------------------------------ GUI w = Window.new("mouse", Rect(0,0,210,210)); h = Slider2D.new(w, Rect(5,5,200,200)); h.knobColor_(Color.green); w.front; w.alwaysOnTop_(true); w.onClose_({a.free;h.free;w.free}); // ------------------------------ OSC OSCFunc.new({arg msg; msg.postln; // stampa il messaggio OSC {h.x_(msg[3]); // assegna i singoli valori h.y_(msg[4]); if(msg[5] == 1, {h.knobColor_(Color.red)}, {h.knobColor_(Color.green)}) }.defer; // AppCLock per messaggi dinamici alla GUI }, '/pos', // indirizzo o nome s.addr); // eventuale NetAddr )