Questo Notebook vuole fornire le conoscenze di base necessarie alla prototipazione e al controllo dei parametri di uno strumento virtuale generico attraverso due software rappresentativi di ambienti operativi informatici differenti:
In questo scritto ho sono illustrati i concetti fondamentali legati alla prototipazione di uno strumento virtuale attraverso due software in parallelo.
Questi software sono rappresentativi di due ambienti operativi differenti impiegati dalla maggior parte dei software dedicati alla ricerca e creazione musicale e multimediale come Processing, Lilypond, Arduino, Python, Chuck, etc..
Scarica i materiali utilizzati in questo notebook.
Super Collider è un software Open Source che utilizza un linguaggio di programmazione (Smalltalk) che segue il paradigma della programmazione orientata agli oggetti (OOP - Object Oriented Programming).
In questo tipo di ambiente informatico dobbiamo scrivere i comandi che vogliamo fare eseguire al computer sotto forma di linee di codice salvate in files di testo chiamati script.
Quando lanciamo l’applicazione (doppio click sull’icona) compare l’interfaccia utente che, dalla versione 3.4 in poi è cambiata radicalmente. La figura sottostante mostra la schermata iniziale delle versioni più recenti (IDE) che è identica su tutti i sistemi operativi ed è divisa in quattro parti:
from IPython.display import HTML
HTML('<center><img src="media/sc_inizio.png" width="80%"></center>')
SuperCollider (nonostante abbia un'unica interfaccia utente) è formato da due applicazioni separate che comunicano tra loro attraverso il protocollo OSC.
Possiamo pensare l’Interprete come un foglio sul quale sono scritte le istruzioni necessarie alla costruzione di uno strumento musicale virtuale, le tecniche strumentali con le quali sarà suonato ed eventualmente una partitura con indicate le azioni da compiere nel tempo per suonarlo.
Questo foglio viene inviato via OSC al Server, che come un liutaio/strumentista virtuale interpreta le istruzioni di montaggio, in base a queste costruisce lo strumento per poi suonarlo eseguendo le azioni specificate nella partitura o quelle inviate in tempo reale da un esecutore reale o virtuale esterno al software.
Quando lanciamo l’applicazione, vengono creati automaticamente due Server di default:
New file
MAC: cmd+n, WIN: ctrl+n
Open file
MAC: cmd+o, WIN: ctrl+o
Dopo aver creato o aperto un file, se vogliamo far compiere un’azione a SuperCollider (come eseguire un’operazione matematica, scrivere una partitura, definire un algoritmo di sintesi, cambiare i parametri di un Synth, farlo suonare, stopparlo, etc.) dobbiamo:
Linee di codice
L’Interprete è suddiviso in linee numerate da 1 a n e ogni linea deve terminare con un ; (punto e virgola).
Se scriviamo su più linee ma non mettiamo punto e virgola prima di andare a capo, SuperCollider le interpreta come una sola.
Se invece scriviamo del codice sulla stessa linea dopo un punto e virgola SuperCollider lo interpreta come se fosse scritto una linea sotto.
1+2; // questa e' una riga
( // ma anche da qua...
1+
2/
3; // a qua per SC e' una sola riga...
1+2; 3*7; // Queste sono due righe...
)
Possiamo raggruppare più linee in blocchi di codice includendoli tra parentesi tonde.
(
"ciao".postln;
a = 12 +24; // Questo è un blocco di codice
a.postln;
)
Eseguire il codice (Evaluate)
Possiamo eseguire il codice scritto nelle celle di questo Notebook copiandolo e incollandolo il contenuto nell'Interprete di SuperCollider oppure riscrivendolo (consigliato come allenamento manuale/mnemonico).
Tre possibilità:
2+2; // Posta il risultato nella Post window
(
13.postln;
[12,"ciao", \miao, 45.567].postln;
(12 + 56).postln
)
Ordine di esecuzione
In queste ultime due modalità le operazioni sono eseguite consecutivamente da sinistra a destra, riga dopo riga, dall’alto verso il basso in quasi tutti i casi (vedremo le eccezioni di volta in volta nel dettaglio) alla maggiore velocità fornita dalla potenza del computer.
Messaggi di errore
Eseguiamo il seguente codice che contiene volutamente un errore (manca la virgola prima di 67):
a = [12,34,56 67]
Leggiamo nella post window cosa ci dice SuperCollider: le freccette o il pallino indicano dove si è interrotta la compilazione, quindi l’errore sarà necessariamente prima e tocca a noi scovarlo.
Cancellare il contenuto della post window
MAC: cmd+shift+p - WIN: ctrl+shift+p
Commenti
E' molto utile inserire dei commenti nel codice sia per agevolare la memoria nel caso volessimo modificare un patch a distanza di tempo, sia nel caso volessimo passarlo a qualcuno che sarebbe così aiutato nella comprensione del codice. I commenti sono colorati di rosso e ci sono due possibili sintassi:
2+2; // somma
Su più righe:
/*
...commento...
*/
Help files
Gli Help files sono i nostri migliori amici quando incontriamo qualche oggetto (parola) di SuperCollider sul quale vogliamo ottenere informazioni.
Per richiamare un help file dobbiamo selezionare la parola (Doppio click o Click and drag).
SinOsc;
MAC: cmd + d - WIN: ctrl + d.
Save file
MAC: cmd+s - WIN: alt+s.
Si apre un menù a comparsa nel quale possiamo specificare quattro formati diversi:
N.B. Prestiamo attenzione che in questi formati viene salvato il codice scritto nell’interprete sotto forma di file di testo, non l’audio eventualmente generato da esso.
Tipi di data
Come in tutti i linguaggi formali in SuperCollider ci sono diversi tipi di data (data type) che definiscono:
I principali sono.
12 // Int
12.23 // Float
\ciao // Symbol
'ciao ciao' // Symbol
$c // Char
"vado al mare" // String
3 + 5; // Restituisce 8
"3" + "5"; // Restituisce 35
Boot del Server
Se vogliamo generare o elaborare segnali audio, prima dobbiamo accendere il Server (boot) e poi valutare il codice.
Possiamo paragonare quest’azione all’accendere o spegnere la corrente che alimenta un sistema elettroacustico (player, mixer, amplificatore e altoparlanti).
Per farlo abbiamo a disposizione tre modi:
Dall'interfaccia utente:
Boot (power on): clicchiamo su 0.00% a destra della scritta ”Server” tenendo schiacciato il tasto del mouse. Compare un menù a tendina e selezioniamo ”Boot Server”. I numeri diventano verdi e alcuni cominciano a cambiare.
Quit (power off): idem, selezionando ”Quit Server”. I numeri tornano bianchi e si azzerano.
s.boot; // Power on
s.quit; // Power off
Quando effettuiamo il ”Boot” del Server, accendiamo la computazione audio in SuperCollider, se però non inviamo alcun segnale all’uscita, non udiremo alcun suono.
(
b = {PMOsc.ar(440, MouseY.kr(1, 550), MouseX.kr(1, 15))*Line.kr(0,0.3,1)};
b.play;
b.plot(0.5);
b.scope;
b.freqscope;
)
Stop dei processi in corso, audio compreso
MAC: cmd+. - WIN: alt+.
Quando invece eseguiamo il codice e fermiamo l’esecuzione come abbiamo visto, è come se schiacciassimo i pulsanti ”Play” e ”Stop” di un CD player o di un altro generatore di suono collegato al sistema elettroacustico di cui sopra.
Max è un software commerciale che utilizza un linguaggio di programmazione visuale (VPL - Visual Programming Language) che fornisce in un primo momento un approccio più intuitivo nello sviluppo di algoritmi di sintesi ed elaborazione del suono.
In questo tipo di ambiente informatico possiamo realizzare applicazioni collegando tra loro diverse tipologie di oggetti grafici all'interno di files chiamati patch dall'aspetto simile ai diagrammi di flusso.
Pure Data è un'applicazione Open source simile a Max ma prestiamo attenzione in quanto nonostante la grande somiglianza grafica i due software sono sensibilmente differenti sotto diversi aspetti.
Quando lanciamo l’applicazione (doppio click sull’icona) compare l’interfaccia utente che è anche chiamata patch window:
from IPython.display import HTML
HTML('<center><img src="media/patch_1.png" width="60%"></center>')
La patch window può assumere due stati:
Si passa da uno stato all'altro sia cliccando sul lucchetto con il mouse sia schiacciando il tasto 'e'.
Se schiacciamo cmd+m oppure cmd+shift+m apriamo o chiudiamo la Max console che corrisponde alla Post window di SuperCollider.
Quando un patch è unlocked (lucchetto aperto) possiamo creare e posizionare due tipologie di elementi:
Object box
Questo tipo di oggetto rappresenta un opcode o codice operativo ovvero un sottoprogramma che è stato creato per svolgere un compito specifico.
Per crearne uno dobbiamo schiacciare 'n' (new) sulla tastiera (oppure click sulla prima icona sulla barra in alto a sinistra).
Compare un oggetto con all'interno un cursore che lampeggia, dovremo scrivere il nome dell'opcode che vogliamo creare tra quelli disponibili e compresi in un'elenco che possiamo richiamare cliccando sulla terza icona dall'alto nella barra di sinistra.
Come esempio scriviamo '*' Questo opcode ha come compito il moltiplicare tra loro due **valori.
from IPython.display import HTML
HTML('<center><img src="media/obj-box.png" width="15%"></center>')
Oggetti GUI
Tutti gli altri oggetti che possiamo inserire in un patch sono Graphic User Interface (GUI) ovvero oggetti dedicati a svolgere due compiti:
Nell'immagine seguente sono visualizzati i principali oggetti di questo tipo che possiamo creare sia scrivendone il nome in un object box, sia schiacciando l'abbreviazione da tastiera indicata, sia cercandoli nella barra in alto:
from IPython.display import HTML
HTML('<center><img src="media/gui.png" width="50%"></center>')
Tutti gli oggetti (anche GUI) possono avere uno o più inlet(s) e/o uno o più outlet(s):
from IPython.display import HTML
HTML('<center><img src="media/inout.png" width="15%"></center>')
Questi rappresentano delle "porte" attraverso le quali fare uscire o entrare un qualche tipo di dato (o un flusso di dati) permettendo di collegare tra loro più oggetti attraverso patch chords:
from IPython.display import HTML
HTML('<center><img src="media/patch_c.png" width="30%"></center>')
Ricoridamoci che per poter interagire con una GUI dobbiamo mettere il patch in locked mode...
New file
MAC: cmd+n, WIN: ctrl+n
Open file
MAC: cmd+o, WIN: ctrl+o
Cancellare la Max console
Cliccare sull'icona.
from IPython.display import HTML
HTML('<center><img src="media/console.png" width="70%"></center>')
Commenti
E' molto utile inserire dei commenti nel codice sia per agevolare la memoria nel caso volessimo modificare un patch a distanza di tempo, sia nel caso volessimo passarlo a qualcuno che sarebbe così aiutato nella comprensione del codice. I commenti sono come gli oggetti GUI e possiamo crearli schiacciando la lettera 'c' oppure cliccando sulla terza icona da sinistra della barra in alto.
Help files
Gli Help files sono i nostri migliori amici quando vogliamo recuperare informazioni su qualche oggetto di Max.
Per richiamare un help file dobbiamo essere in unlocked mode e selezionare l'oggetto con il tasto destro. Dal menù a comparsa selezionare 'Open ... Help'.
from IPython.display import HTML
HTML('<center><img src="media/help_2.png" width="95%"></center>')
Save file
MAC: cmd+s - WIN: alt+s.
Si apre un menù a comparsa nel quale possiamo specificare diversi formati, per ora sceglieremo sempre Patcher (nome_del_file.maxpat) che salva il patch nel formato di Max. Se Max non è stato lanciato e facciamo doppio click sull’icona di un file ’.maxpat’ il software si apre automaticamente.
N.B. Prestiamo attenzione che in questi formati viene salvato il codice scritto neò patch sotto forma di file di testo, non l’audio eventualmente generato da esso.
Anche in Max per generare o elaborare segnali audio dobbiamo accenderne il motore (come il boot del Server in SuperCollider). Per farlo abbiamo due opzioni principali:
from IPython.display import HTML
HTML('<center><img src="media/dac~.png" width="37%"></center>')
Questo patch rappresenta il nostro primo algoritmo di sintesi e contiene tutti gli elementi illustrati finora, introducendo inoltre un'ulteriore differenza tra gli oggetti:
SuperCollider
Max
Apriamo il Capitolo dedicato.
Come illustrato nel paragrafo dedicato alla sua architettura, SuperCollider è formato da due applicazioni che operano in parallelo e che, se vogliamo leggere, generare o modificare segnali audio dobbiamo accenderne (boot) una di esse (il Server).
Se non specifichiamo nulla, quando eseguiamo il boot il Server si collega ai driver audio utilizzati in quel momento dal sistema operativo del computer.
Se invece vogliamo utilizzare una scheda audio esterna dobbiamo selezionarla dalle preferenze di sistema come per tutti gli altri software.
Quando effettuiamo il boot sel Server SuperCollider restituisce nella Post window alcune informazioni che possono tornare utili:
Booting server 'localhost' on address 127.0.0.1:57110.
Found 0 LADSPA plugins Number of Devices: 5 0 : "Microfono MacBook Air" 1 : "Altoparlanti MacBook Air" 2 : "Microsoft Teams Audio" 3 : "VB-Cable" 4 : "ZoomAudioD"
"Microfono MacBook Air" Input Device Streams: 1 0 channels 1 "Altoparlanti MacBook Air" Output Device Streams: 1 0 channels 2
SC_AudioDriver: sample rate = 44100.000000, driver's block size = 512
SuperCollider 3 server ready. Requested notification messages from server 'localhost' localhost: server process's maxLogins (1) matches with my options. localhost: keeping clientID (0) as confirmed by server process. Shared memory server interface initialized
In Max possiamo trovare le stesse informazioni aprendo la finestra Audio Status dal menù Options.
from IPython.display import HTML
HTML('<center><img src="media/audio_status.png" width="35%"></center>')
In questo caso il block size è chiamato I/O Vector size per distinguerlo dal Signal Vector Size che è un valore riguardante il funzionamento interno (MSP Routines) dei patch di Max.
Con l'audio spento possiamo anche modificare le impostazioni direttamente dall'interfaccia senza farlo dalle preferenze di sistema.
Predisposto l'ambiente elettroacustico e collegato correttamente tra loro i dispositivi della catena audio possiamo cominciare a generare dei suoni.
Nel paragrafo dedicato all'architettura informatica di SuperCollider abbiamo visto che ci sono due Server di default.
Possiamo scegliere quale utilizzare.
s = Server.local; // Settiamo il Server locale
La lettera s è una variabile.
La parola Server è una classe.
La parola .local è un messaggio.
Ma cos’è una variabile)?
Pensiamola come una porzione di memoria del computer destinata a contenere dei dati (numeri, caratteri, audio files, synth, tabelle, array, etc.), suscettibili di modifica nel corso dell’esecuzione di un programma.
Per distinguere la singola porzione di memoria tra le tante, dobbiamo contrassegnarla con un nome (o etichetta o indirizzo).
La linea di codice nella cella precedente chiama con il nome 's' una porzione di memoria e la assegna al Server locale.
In SuperCollider esistono tre tipi di variabili:
La parola Server è una classe ovvero uno stampino attraverso il quale possiamo costruire tante copie di un oggetto (istanze).
Agli oggetti possono essere inviati dei messaggi che assumono due funzionalità:
Possiamo inviare messaggi agli oggetti separandoli con un punto.
In questo caso abbiamo definito il Server come 'locale' inviando il messaggio .local alla classe Server e ne abbiamo assegnato una copia (istanza) alla variabile 's' secondo una delle più comuni strutture sintattiche impiegate in SuperCollider.
// Nome_variabile assegnazione Oggetto.messaggio_a_oggetto
s = Server.local;
s.boot; // Invia un nuovo messaggio a Server.local
s.quit;
s.reboot;
Possiamo ora intuire a cosa servono le variabili: rendere il codice più leggibile e meno ridondante.
Senza l'utilizzo della variabile s avremmo dovuto scrivere.
Server.local.boot;
Server.local.quit;
Server.local.reboot;
Ricordiamo che il codice sulla stessa linea viene eseguito da sinistra a destra: prima dichiara il Serve poi lo definisce come locale e poi lo accende, spegne, etc. ( richiesta di compiere un'azione ).
Attraverso la sintassi appena illustrata possiamo impostare dal codice diversi parametri del Server.
Definire quale Server utilizzare nello script corrente.
s = Server.internal;
s = Server.local;
La lettera s è assegnata di default al Server locale.
Se vogliamo modificare il Server di default che sarà assegnato alla variabile globale s in assenza di ulteriori messaggi.
Server.default = Server.internal;
Server.default = Server.local;
Principali messaggi che possiamo inviare al Server in uso.
s = Server.local;
s.boot; // Boot del Server
s.quit; // Chiude il Server
s.reboot; // Chiude e riapre il Server
s.waitForBoot({...}) // Accende il Server e terminato il booting esegue il codice successivo tra le parentesi
s.freeAll; // Libera tutti i nodi nel Server
s.volume = 0; // Setta il voume in uscita (in dB)
s.mute; // Mute del Server
s.unmute; // Unmute
In assenza di indicazioni SuperCollider riceve e invia tutti i segnali audio ai driver selezionati nel sistema operativo.
Possiamo modificare questa scelta dal codice attraverso le ServerOptions.
Otteniamo un elenco di dispositivi connessi al computer.
ServerOptions.devices;
ServerOptions.inDevices;
ServerOptions.outDevices;
Selezioniamo quello da utilizzare.
s = Server.local;
o = s.options;
o.device; // riporta il device in uso
o.device = "Soundflower (2ch)"; // seleziona il device specificando il nome
o.device_("Soundflower (2ch)"); // altra sintassi...
s.reboot; // Se già acceso dobbiamo spegnere e riaccendere...
o.device = nil; // 'nil' specifica i driver in uso dal sistema
s.reboot;
Dispositivi differenti per i segnali in entrata e quelli in uscita.
o.inDevice = "Built-in Microph";
o.outDevice = "Soundflower (2ch)";
s.reboot;
Modificare il numero di canali.
o.numOutputBusChannels.postln; // riporta l'informazione nella post window
o.numOutputBusChannels = 8; // modifica i settings
o.numOutputBusChannels_(8); // altra sintassi...
o.numInputBusChannels.postln; // riporta
o.numInputBusChannels = 8; // modifica
o.numInputBusChannels(8); // altra sintassi...
s.reboot;
Modificare rata di campionamento e block size.
o.sampleRate; // riporta
o.sampleRate = 44100; // modifica
o.sampleRate_(44100);
o.blockSize; // riporta
o.blockSize = 64; // modifica
o.blockSize_(64);
s.reboot;
Ora che sappiamo accendere il Server di SuperCollider e ricevere o inviare alle porte del computer segnali audio vediamo come generarli o modificarli utilizzando una tipologia di oggetti chiamati UGens o Unit Generator.
Le UGens sono particolari classi ottimizzate per la generazione o modifica di segnali audio.
Le UGens 'vivono' nel Server.
Come tutte le classi cominciano sempre con una lettera maiuscola e di default sono colorate di blu.
Possono ricevere solo tre messaggi che ne definiscono la rata ovvero quanti valori devono generare o modificare nel tempo (azioni da compiere).
Nella figura seguente lo stesso segnale generato ad audio rate (sopra) e a control rate (sotto)
{[SinOsc.ar,SinOsc.kr]}.plot(1/50, bounds:1000@500);
I segnali a audio rate possono sia essere collegati agli inputs (adc) o outputs (dac) che utilizzati come segnali di controllo dei parametri di altre UGens mentre i segnali a control rate non possono essere collegati agli inputs e agli outputs.
Mentre i tre messaggi precedenti definiscono quanti valori vengono generati nel tempo i nomi delle specifiche UGen indicano quale tipo di relazione intercorre fra i valori generati (forma d'onda).
Possiamo ottenere un elenco di tutte le UGens andando nella finestra degli Help files e cliccando prima su Browse e poi su UGens.
from IPython.display import HTML
HTML('<center><img src="media/broswe.png" width="65%"></center>')
Tutti e tre i metodi invocabili sulle UGen possiedono degli argomenti che specificano i valori di alcuni parametri del segnale da generare o modificare come ad esempio frequenza, ampiezza o altro.
Gli argomenti sono specificati all'interno di parentesi tonde e sono differenti per ogni UGen sulla quale sono invocati i metodi.
Per conoscere quali sono gli argomenti di una specifica UGen dobbiamo richiamarne l'Help file.
Come esempio osserviamo quelli di SinOsc che è un oscillatore sinusoidale.
from IPython.display import HTML
HTML('<center><img src="media/args.png" width="50%"></center>')
Il primo specifica la frequenza in Hertz.
(
{[SinOsc.ar(1320),
SinOsc.ar(880),
SinOsc.ar(440)]
}.plot(minval:-1,maxval:1)
)
Il secondo specifica la fase iniziale in radianti tra -1 e 1.
(
{[SinOsc.ar(440,0.0),
SinOsc.ar(440,0.5),
SinOsc.ar(440,1.0)]
}.plot(minval:-1,maxval:1)
)
Il terzo specifica un fattore di moltiplicazione dei valori in uscita (usualmente compresi tra -1.0 e + 1.0).
(
{[SinOsc.ar(440,0,1.0),
SinOsc.ar(440,0,0.5),
SinOsc.ar(440,0,0.2)]
}.plot(minval:-1,maxval:1)
)
Il quarto specifica un valore da sommare ai valori in uscita.
(
{[SinOsc.ar(440,0,1.0),
SinOsc.ar(440,0,0.5),
SinOsc.ar(440,0,0.2)]
}.plot(minval:-1,maxval:1)
)
Questi ultimi due argomenti (mul e add) sono compresi in quasi tutte le UGens e impiegati di prassi per riscalare il range in uscita.
Non è necessario specificare tutti gli argomenti.
Quelli non indicati assumeranno il valore di default che possiamo trovare nell'help file.
Possiamo utilizzare due sintassi differenti.
SinOsc.ar(440,0,0.3);
SinOsc.ar(mul:0.7, freq:500);
Nel proseguio di questo scritto ci soffermeremo sulle caratteristiche delle singole UGens, per ora possiamo curiosare richiamando l'Help file di alcune tra quelle in elenco.
I monitors che riguardano l'audio sono di tre tipi:
In SuperCollider possiamo monitorare:
Attività computazionale
Abbiamo due tipologie di monitor numerici:
1 - Leggere le informazioni nell'IDE (in basso a destra).
from IPython.display import HTML
HTML('<center><img src="media/monitor_1.png" width="45%"></center>')
Da sinistra a destra:
Se clicchiamo sui numeri appare la seguente finestra dove possiamo attivare o disattivare diversi comandi.
from IPython.display import HTML
HTML('<center><img src="media/monitor_6.png" width="40%"></center>')
2 - Ottenere i valori invocando metodi nel codice.
s.status; // Riporta il nome del Server in uso
s.avgCPU; // Riporta l'utilizzo medio della CPU
s.peakCPU; // Riporta l'utilizzo di picco della CPU
s.numUGens; // Riporta il numero di UGens
s.numSynths; // Riporta il numero di Synths
s.numGroups; // Riporta il numero di Gruppi
s.numSynthDefs; // Riporta il numero di SynthDefs
s.volume; // Riporta il volume massimo in uscita (dB)
s.mute; // Muting
s.unmute; // Toglie il Mute
Una delle ragioni di poter ottenere queste informazioni dal codice sta nel fatto che potremmo utilizzarle come valori di controllo di una qualche tecnica di sintesi o di elaborazione del segnale oppure riportarli su un'interfaccia grafica (GUI) per un monitoraggio visivo personalizzato.
Segnali in ingresso e uscita dal Server
Richiamiamo interfacce grafiche (GUI) dedicate.
s.meter; // Visualizza segnali in input e ouput
s.scope(2); // Oscilloscopio del master out (il numero di canali si accorda
// con quello specificato nelle ServerOption oppure possiamo spe-
// cificarlo come argomento)
s.freqscope; // Spettroscopio del master out (monofonico, possiamo scegliere
// quale canale (Bus) visualizzare specificandone l'ID nel box
// a destra. Possiamo inoltre specificare se la visualizzazione
// deve essere lineare o logaritmica (di default) e anche il li-
// mite inferiore in dB -(96 di default)
{Pan2.ar(Mix(SinOsc.ar(Array.rand(20,200,5000),0,0.1)),0.3)}.play // Test
Segnali in uscita dalle UGens
Uditivo
Un modo veloce per far suonare e monitorare attraverso l'ascolto una o più UGens consiste nell'includerla tra parentesi graffe e invocare il metodo .play.
E' un'abbreviazione sintattica che possiamo utilizzare esclusivamente per prototipare velocemente algoritmi di sintesi.
{SinOsc.ar}.play;
{SinOsc.ar + WhiteNoise.ar}.play;
Grafico
Per avere un monitoraggio visivo della forma d'onda (oscillogramma) del segnale in uscita di una specifica UGen (o da un network di UGens) possiamo invocare il metodo .scope che sostituisce play.
{Saw.ar}.scope;
{SinOsc.ar(LFNoise0.kr(15,1000,2000))}.scope;
Possiamo utilizzare i diversi metodi in contemporanea.
(
{
SinOsc.ar(2000 + (1000 * LFNoise0.kr(5).scope(\pitch)) // Scope segnale di controllo
).scope(\sine) // Scope segnale audio
}.play; // Monitor uditivo
s.scope; // Scope del Server
)
Due considerazioni.
La prima consiste nel fatto che l'argomento di .scope() è il nome che vogliamo dare alla finestra grafica.
La seconda riguarda il colore che differenzia lo scoping dei segnali a audio rate (gialli) da quelli a control rate (verdi).
Possiamo anche visualizzare in un plot i valori dell'output di una UGen invocando l'omonimo metodo.
Nell'esempio seguente sono illustrati i principali argomenti per questo tipo di plot.
{Pulse.ar}.plot;
{Pulse.ar + Saw.ar}.plot(duration: 0.01 ); // Durata della finestra in secondi
{Pulse.ar * Saw.ar}.plot(bounds: Rect(0,0,500,100)); // Dimensioni grafiche finestra
{Pulse.ar * PinkNoise.ar}.plot(bounds: 500@100); // Abbreviazione
{Pulse.ar + WhiteNoise.kr}.plot(minval: -2, maxval: 2); // Minimo e massimo asse y
Numerico
Infine per un monitoraggio numerico dei valori dei singoli campioni possiamo utilizzare il metodo .poll invocato direttamente su una UGen.
Questo metodo stampa semplicemente nella Post window i valori in uscita.
L'argomento è il numero di campioni per secondo da stampare (sottocampionamento).
Questo metodo serve solo per monitorare, non possiamo assegnare i singoli valori ad una variabile per poi riutilizzarli all'interno del codice.
{SinOsc.ar.poll(1)}.play;
{SinOsc.ar(MouseX.kr(40,10000,1).poll(10), 0, 0.1) }.play;
Gli Help files delle UGens sono principalmente divisi in tre sezioni.
from IPython.display import HTML
HTML('<center><img src="media/sc_help_1.png" width="60%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/sc_help_2.png" width="60%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/sc_help_3.png" width="60%"></center>')
In SuperCollider il codice viene eseguito dall'alto verso il basso, da sinistra a destra, linea dopo linea.
In Max tutti le operazioni effettuate dagli oggetti posizionati più a destra sono eseguite prima.
from IPython.display import HTML
HTML('<center><img src="media/max_ordine.png" width="25%"></center>')
Se un object box ha più di un outlet l'ordine di uscita dei valori va anch'esso da destra a sinistra.
from IPython.display import HTML
HTML('<center><img src="media/max_ordine_out.png" width="19%"></center>')
Se un object box ha più di un inlet:
il primo a sinistra è l'inlet caldo ovvero memorizza il dato in ingresso, esegue l'operazione e manda il risultato in uscita dall'outlet.
tutti quelli più a destra del primo sono inlet freddi ovvero memorizzano il il dato in ingresso in quella posizione senza triggerare il risultato in uscita.
Se un singolo patch cord entra in più inlets l'ordine è sempre da destra a sinistra.
from IPython.display import HTML
HTML('<center><img src="media/max_ordine_3.png" width="55%"></center>')
Per recuperare segnali audio in ingresso possiamo utilizzare l'oggetto adc~ che accetta come argomenti il numero di canale(i) al quale collegarsi.
Per inviare segnali audio in uscita possiamo utilizzare l'oggetto dac~ che accetta come argomenti il numero di canale(i) al quale collegarsi.
Possiamo accendere o spegnere l'audio inviando 1 (on) o 0 (off) nel loro inlet di sinistra sia attraverso message box che con un toggle.
from IPython.display import HTML
HTML('<center><img src="media/max_inout.png" width="20%"></center>')
In locked mode se eseguiamo doppio click su di essi compare l'audio status dal quale possiamo impostare i diversi dispositivi collegati nonchè la Sampling Rate e l'I/O Vector Size (block size di SuperCollider).
Possiamo anche monitorare l'attività computazionale.
from IPython.display import HTML
HTML('<center><img src="media/audio_status.png" width="37%"></center>')
Anche in Max ci sono oggetti audio che generano segnali (oscillatori) e che li modificano.
I segnali possono essere monitorati principalmente attraverso tre GUI.
Accettano i segnali da monitorare negli inlet di sinistra.
from IPython.display import HTML
HTML('<center><img src="media/max_monitors.png" width="70%"></center>')
Gli Help files di Max sono dei veri e propri patch e sono strutturalmente simili a quelli di SuperCollider.
from IPython.display import HTML
HTML('<center><img src="media/max_help_1.png" width="95%"></center>')
L’altezza (o frequenza) di un suono è il parametro legato alla sensazione di gravità/acutezza che si percepisce e dipende dalle frequenze di variazione delle onde elementari che compongono l’onda sonora.
Possiamo specificare questo parametro in tre diversi tipi di notazione simbolica:
from IPython.display import HTML
HTML('<center><img src="media/Simboli.png" width="40%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/Nomi.png" width="40%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/Midi.png" width="40%"></center>')
Oltre ai valori midi esistono altre rappresentazioni simboliche assolute impiegate:
from IPython.display import HTML
HTML('<center><img src="media/Altezze.png" width="63%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/Frequenze.png" width="40%"></center>')
Un'onda periodica è data da un fenomeno vibratorio che si ripete identico a se stesso a intervalli di tempo uguali.
Possiamo rappresentare graficamente un onda attraverso un sistema di riferimento cartesiano:
La frequenza di un suono periodico è misurata in Hertz o cps (cicli per secondo) ovvero si misura quante volte il periodo si ripete uguale in un secondo.
import os
import sys
sys.path.insert(0, os.path.abspath('moduli'))
import parametri as par
%matplotlib inline
freq = 4# Frequenza (Hz o cps)
par.sine(freq)
L'orecchio umano nelle sue condizioni ottimali percepisce suoni compresi tra 20 e 20.000 Hz.
Nella seguente tabella i rapporti tra alcune unità di misura appena esposte.
Nota | MIDI | Rapporto | Frequenza (Hz) | |
---|---|---|---|---|
Do | 60 | $1:1$ | 261.6 | |
Do# o Reb | 61 | $\sqrt[12]{2}$ | 277.2 | |
Re | 62 | $\sqrt[12]{2^2}$ | 293.7 | |
Re# o Mib | 63 | $\sqrt[12]{2^3}$ | 311.1 | |
Mi | 64 | $\sqrt[12]{2^4}$ | 329.6 | |
Fa | 65 | $\sqrt[12]{2^5}$ | 349.2 | |
Fa# o Solb | 66 | $\sqrt[12]{2^6}$ | 370.0 | |
Sol | 67 | $\sqrt[12]{2^7}$ | 390.0 | |
Sol# o Lab | 68 | $\sqrt[12]{2^8}$ | 415.3 | |
La | 69 | $\sqrt[12]{2^9}$ | 440.0 | |
La# o Sib | 70 | $\sqrt[12]{2^{10}}$ | 466.2 | |
Si | 71 | $\sqrt[12]{2^{11}}$ | 493.9 | |
Do | 72 | $2:1$ | 523.3 |
Questo parametro in termini musicali è dato dal rapporto tra i suoni più deboli (pianissimissimo) e quelli più forti che caratterizzano la dinamica di un brano.
L'intensità di un suono in fisica si definisce con il termine ampiezza in quanto descrive l’ampiezza delle variazioni dell’onda sonora rispetto allo stato di quiete e fornisce una misura dell’energia da essa trasportata.
Se prendiamo in considerazione l'aspetto fisico acustico e non la percezione umana questo parametro è indipendente dalla frequenza e per convenzione rappresentato da valori compresi tra -1.0 e +1.0 e che nell'audio digitale corrisponde al valore del singolo campione.
import os
import sys
sys.path.insert(0, os.path.abspath('moduli'))
import parametri as par
%matplotlib inline
freq = 8 # Frequenza (Hz o cps)
amp = 0.2 # Ampiezza (tra 0.0 e 1.0)
par.vsine(freq, amp)
par.sine(freq, amp)
La funzione sinusoidale può rappresentare una legge oraria chiamata moto armonico semplice che è un moto periodico lungo un asse rettilineo detto origine (O).
$x(t) = a * sin(\omega t + \varphi)$
Dal punto di vista cinematico può essere visto come:
from IPython.display import HTML
HTML('<center><video width="95%" controls loop autoplay> <source src="media/sine.mp4"></video></center>')
import os
import sys
sys.path.insert(0, os.path.abspath('moduli'))
import parametri as par
%matplotlib inline
freq = 5 # Cambia i parametri...
amp = 1
fase = 0.5
par.sinepar(freq,amp,fase)
Il timbro è quella particolare qualità del suono che permette di distinguere due suoni con uguale ampiezza e altezza e consente all'ascoltatore di identificare la fonte sonora, rendendola distinguibile da ogni altra.
Il timbro dei suoni naturali è influenzato da moltissimi parametri variabili come ad esempio le caratteristiche fisiche dei materiali della sorgente sonora e dal modo in cui è messo in vibrazione.
Tutti questi parametri concorrono a formare una rappresentazione fisica del timbro di quel suono ovvero la sua forma d'onda, che descrive come varia nel tempo la pressione atmosferica (o il voltaggio di un segnale) nel produrre quel determinato suono.
Nelle celle sottostanti sono illustrate tre diverse forme d'onda:
import os
import sys
import IPython.display as ipd
sys.path.insert(0, os.path.abspath('moduli'))
import parametri as par
%matplotlib inline
par.puro()
ipd.Audio('media/puro.mp3')
/Users/andreavigani/anaconda3/lib/python3.11/site-packages/matplotlib/cbook/__init__.py:1340: ComplexWarning: Casting complex values to real discards the imaginary part return np.asarray(x, float)
par.periodico()
ipd.Audio('media/periodico.mp3')
par.noise()
ipd.Audio('media/noise.mp3')