Home

Ambienti integrati per applicazioni audiovisive¶

Indice¶

  • Introduzione
  • Prerequisiti
  • Installazione
  • Ambienti operativi
  • Risorse in rete
  • Struttura dei files
  • Display
    • size() e createCanvas()
    • fullscreen(), displayWidth e displayHeight
    • Coordinate relative e assolute
    • translate() e rotate()
  • File(s) di lavoro
  • Tone.js
    • Segnali
    • Settare parametri
    • Instrument
    • PoliSynth
    • Sequenze mono
    • Sequenze di eventi
    • Tone-Rhythm.js
    • Unità di misura
    • Esercitazione n°1
  • Primo caso di studio - Staticità
    • Processing e SuperCollider
    • p5.js e Tone.js
      • Riverberi.
      • Rampe non lineari.
      • Sequencing con Array.
    • Esercitazione n°2
  • Elementi 2D
    • Figure geometriche
    • Immagini
    • Testo
  • Secondo caso di studio - Trigger e pannelli.
    • Random
    • Trigger
    • for() e while()
    • if() e switch()
    • Processing e SuperCollider
    • p5.js e Tone.js
      • Sound file player
  • Interazione in Processing e p5.js
    • Mouse
      • Variabili di sistema
      • Eventi
    • Tastiera
      • Variabili di sistema
      • Eventi
    • OSC
    • MIDI
      • Processing e SuperCollider
      • p5.js
  • Terzo caso di studio - Trigger e cues
    • Processing e SuperCollider
      • Cues (scene)
    • p5.js e Tone.js
      • Riproduzione video
      • Sequencing polifonico
    • Esercitazione n°3
  • Animazioni e GUI (in preparazione)
    • Movimenti
    • Controllo del Loop
    • Interazione con GUI
    • Elementi statici vs elementi dinamici.
    • Mapping audio percettivo.
    • Rappresentazoine sonora indipendente.
  • Quarto caso di studio - Mapping diretto (in preparazione)
    • Suono --> oggetto.
    • Valori OSC: SC --> Processing.
    • Elementi misti
    • Esercitazione n°4
  • Visualizzazione di dati audio (In preparazione)
  • Quinto caso di studio - Oggetti 3D (In preparazione)

Introduzione Indice

Questo Notebook vuole fornire le conoscenze di base per risolvere le problematiche artistiche e tecnico/informatiche legate alla realizzazione di lavori audiovisivi sia interattivi che formalmente strutturati.

Seguiremo un percorso parallelo per affrontare gli stessi argomenti in due ambienti funzionali e percettivi differenti (tipologie di progetti):

  • creazioni audiovisive performative (installazioni, concerto, etc.). Gli esempi sono realizzati con Processing per la parte visiva e SuperCollider per quella audio.
  • creazioni audiovisive per il web. Gli esempi sono realizzati in P5.js per la parte visiva e Tone.js per quella audio.

La differenza consiste nel fatto che nel primo caso abbiamo due applicazioni differenti basate su linguaggi diversi che comunicano tra loro attraverso il protocollo OSC mentre nel secondo lavoriamo con librerie dello stesso linguaggio (JavaScript).

Nel proseguio di questo scritto queste due modalità saranno separate.

Processing è un software open source basato su Java dedicato alla realizzazione di immagini, animazioni e interazioni.

Nel corso degli anni per facilitare l'utilizzo nei diversi ambienti operativi all'interno dei quali possiamo utilizzare le funzionalità di Processing sono stati sviluppati alcuni progetti derivati che ci permettono di utilizzare altri linguaggi di programmazione.

In [5]:
from IPython.display import HTML
HTML('<center><img src="media/famiglia.png" width="55%"></center>')
Out[5]:

Possiamo pensare all'insieme di questi linguaggi di programmazione come a diversi dialetti della stessa lingua.

Questo ci permette di imparare facilmente un nuovi linguaggi senza ripartire ogni volta da da zero in quanto ci sono vocaboli e sintassi simili.

Ad esempio p5.js è una libreria di JavaScript che include alcune funzionalità specifiche per la grafica e l'interazione.

In [6]:
from IPython.display import HTML
HTML('<center><img src="media/ling.png" width="70%"></center>')
Out[6]:

Processing si presta alla realizzazione di progetti memorizzati all'interno di un computer.

Il risulatato grafico è renderizzato in una finestra (Display window) che può occupare tutto lo schermo (fullsize) oppure essere visualizzata su di un qualsiasi schermo o proiettore collegato al computer.

p5.js invece si presta all'integrazione in pagine HTML memorizzate su di un server ed il risultato grafico viene renderizzato in un broswer come Chrome.

Prerequisiti Indice

  • Conoscenza degli elementi comuni a diversi linguaggi di programmazione come variabili e funzioni.
  • Conoscenza delle principali tecniche di sintesi, analisi ed elaborazione del suono.
  • Conoscenza di SuperCollider e dei principali protocolli di comunicazione dati per l'audio MIDI e OSC.

Installazione Indice

  • Installiamo SuperCollider.
  • Installiamo Processing.
  • Installiamo Node.js.
  • Installiamo Tone.js.

Ambienti operativi Indice

Processing Indice

Per questa tipologia di progetti utilizzeremo i due IDE (Integrated Development Environment) nativi.

In [7]:
from IPython.display import HTML
HTML('<center><img src="media/proc_ide1.png" width="55%"></center>')
Out[7]:

Come nolti editor di questo tipo è suddivisio in due parti:

  • una zona centrale all'interno della quale scrivere il codice.
  • una console in basso dovre poremo stampare a schermo informazioni o leggere di eventuali errori.

In alto sono presenti anche due bottoni: uno per eseguire il codice e l'altro per interrompere la computazione.

p5.js Indice

Per questa tipologia di progetti abbiamo a disposizione tre possibilità:

  • IDE nativo di Processing. Come vedremo nell'Intro Processing può essere programmato in tre diversi linguaggi informatici:

    • Java
    • p5.js (Javascript)
    • Python

    A seconda di quello che vogliamo utilizzare dobbiamo settare la modalità dal menù in alto a destra.

  • Editor on line. Essendo un'applicazione specifica per il web possiamo utilizzare il suo Editor nativo.
  • Editor off line. Possiamo utilizzare un Editor generico come VSCode.
    • Installiamo l'estensione p5.vscode.
    • Apriamo il Riquadro comandi (cmd + shift + p).
    • Digitiamo p5 e poi selezioniamo Create p5.js project.
    • Clicchiamo Nuova cartella.
    • Diamo un nome alla cartella.
    • Clicchiamo Apri.
    • Per eseguire il progetto clicchiamo su Go Live in basso a destra (attiviamo un Server js).
    • Si aprirà il browser predefinito con la visualizzazione del canvas.
    • Per stoppare la computazione clicchiamo nuovamente in basso a destra.
    • Per caricare librerie:
      • Apriamo il Riquadro comandi (cmd + shift + p).
      • Scriviamo p5 e selezioniamo Install p5 Contributor Library.
      • Dal menù a tendina scegliamo la libreria da installare.

In tutti i casi per eseguire gli esempi di questo Notebook possiamo copiarli dalle celle e incollarli negli Editor scelti anche se consiglio vivamente di riscriverli da tastiera.

Risorse in rete Indice

Processing

  • Happy Coding.
  • Vari.
  • OpenProcessing.
  • Slide intro.

p5.js

  • Computer Graphics
  • Learn
  • Tutorial video
  • Tutorial Tone.js

Struttura dei files Indice

I files di Processing e di p5.js si chiamano sketch (schizzi) e fondamentalmente hanno lo stesso schema sintattico.

Si aprono con la dichiarazione di eventuali variabili globali.

Nel compiere questa operazione ci sono alcune differenze in quanto Processing (basato su Java) è un linguaggio fortemente tipizzato e dobbiamo obbligatoriamente dichiarare il tipo di data mentre in p5.js (basato su JavaScript) e SuperCollider (basato su Smalltalk) no.

SuperCollider

In [ ]:
a = 12;          // int
b = 34.56;       // float
c = $c;          // char
d = \ciao;       // Symbol
d = 'ciao ciao';
e = "ciao miao"; // string
f = true;        // boleean
g = {arg  ;  };  // Function  
h = [];          // Array
// ...

Processing

In [ ]:
int    a = 12;            // integer
float  b = 34.45;         // floating point
char   d = 'c';           // character
String e = "ciao ciao";   // string
boolean  = false;         // boolean
void     = setup();       // function
int[]  f = {1,2,4,6};     // Array
// ...

p5.js

In p5 possiamo utilizzare la parola chiave let.

Sono variabili globali e/o locali (se dichiarate all'interno di un blocco di codice valgono solo la suo interno).

In [ ]:
let ciao = 20.5;

{
let ciao = 'ciao ciao';
console.log(ciao);           // al posto di print()...
}

console.log(ciao);          

Per leggere il risultato in output dobbiamo cercare la Console JavaScript nei menù a tendina (in genere sotto Visualizza).

L'eventuale dichiarazione di variabili globali è seguita da due funzioni:

  • setup - il codice al suo interno viene eseguito una sola volta quando lanciamo l'applicazione. Tipicamente contiene le istruzioni di inizializzazione.
  • draw - il codice al suo interno viene eseguito costantemente in loop ad una veolcità stabilita da una framerate (di default 60 fps).

In entrambe i casi l'esecuzione avviene dall'alto verso il basso, linea dopo linea, da sinistra a destra.

Processing

In [ ]:
int ciccio = 200;    // Dichiarazione data type obbligatoria

void setup() {     
              
}


void draw() {      
  
}

p5.js

In [ ]:
var ciaccio = 200;

function setup() {

}


function draw() {

}

In tutti e due i software le linee si chiudono con un punto e virgola

Display Indice

La cosa fondamentale per un software di grafica è generare immagini su di uno schermo.

In [8]:
from IPython.display import HTML
HTML('<center><img src="media/schermo_1.png" width="35%"></center>')
Out[8]:

Processing renderizza il risultato della computazione nella display window.

size() e createCanvas() Indice

In [ ]:
void setup() {  
              size(480, 120); // crea una display window di 480x120 pixels
              
}


void draw() {       
}

p5.js renderizza il risultato in un broswer qualsiasi come Chrome.

In [ ]:
function setup() {
                createCanvas(480, 120); // crea un 'foglio' di 480x120 pixels
                background(220);        // per visualizzarlo...
}


function draw() {             
}

Notiamo come in entrambi i linguaggi viene utilizzata la functional notation.

Scriviamo il nome di un oggetto e gli passiamo gli argomenti all'interno delle parentesi tonde.

Ricordiamo che in SuperCollider possiamo utilizzare anche la reciver notation.

In [ ]:
rand(12);      // functional notation
12.rand;       // reciver notation

Ma che argomenti (parametri) abbiamo passato a size() e createCanva()?

Lo schermo di un computer è come una griglia di elementi luminosi chiamati pixels (matrice bidimensionale) e il loro numero sull'asse orizzontale (x) e verticale (y) ne definisce la risoluzione.

In [9]:
from IPython.display import HTML
HTML('<center><img src="media/risoluzioni.png" width="85%"></center>')
Out[9]:

La display window e il canvas sono esattamente come uno schermo e le loro coordinate 2D e 3D sono espresse in pixel.

In [10]:
from IPython.display import HTML
HTML('<center><img src="media/coordinate.png" width="70%"></center>')
Out[10]:

Il punto in alto a sinistra alle coordinate (0, 0) è chiamato origine.

fullscreen(), displayWidth e displayHeight Indice

In Processing possiamo adattare allo schermo le dimensioni della diplay window (per uscire premere esc).

In [ ]:
void setup() {  
              fullScreen(); 

}


void draw() {      
}

In p5.js a causa delle restrizioni dei broswer è possibile attivare/disattivare la modalità fullscreen solo come conseguenza di un'azione da parte dell'utente.

  • Prima generiamo un canvas delle dimensioni della finestra del broswer.
  • Poi scriviamo una funzione che invochi fullscreen() quando clicchiamo col mouse in una zona dello schermo.
In [ ]:
function setup() {  
              createCanvas(displayWidth, displayHeight); // Crea un canvas delle dimensioni dello schermo
              //createCanvas(windowWidth, windowHeight); // Crea un canvas delle dimensioni della finestra broswer
              background(150);
              
}


function draw() {        
}

function mousePressed() {                                 // Se si clicca nell'angolo in alto a destra
                                                          // si attiva/disattiva la modalità fullscreen
  if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
    let fs = fullscreen();
    fullscreen(!fs);
  }
}

Coordinate relative e assolute Indice

Disegnamo ora quattro cerchi di un pixel sullo schermo (esempio solo in Processing valido anche per p5.js).

In [ ]:
void setup() {  
            size(300, 300); 
            background(255);         // Bianco
            
            ellipse(100,100,10,10);  // x, y, larg, alt (Alto a sx)
            ellipse(200,100,10,10);  // x, y, larg, alt (Alto a dx)
            ellipse(100,200,10,10);  // x, y, larg, alt (Basso a sx)
            ellipse(200,200,10,10);  // x, y, larg, alt (Basso a dx)
}


void draw() {      
}

Abbiamo definito le coordinate x e y in pixel assoluti.

Proviamo ora ad adattare il size alla schermo e osserviamo cosa cambia.

In [ ]:
void setup() {  
            fullScreen(); 
            background(255);         // Bianco
            
            ellipse(100,100,10,10);  // x, y, larg, alt (Alto a sx)
            ellipse(200,100,10,10);  // x, y, larg, alt (Alto a dx)
            ellipse(100,200,10,10);  // x, y, larg, alt (Basso a sx)
            ellipse(200,200,10,10);  // x, y, larg, alt (Basso a dx)
}


void draw() {      
}

Il rapporto tra la posizione dei cerchi è cambiato.

Questo perchè abbiamo utilizzato valori assoluti che sono sempre in relazione al punto d'origine (0,0) - ovvero l'angolo in alto a sinistra.

In questo caso possiamo definire le coordinate relative recuperando automaticamente le dimensioni dello schermo.

In [ ]:
void setup() {  
            size(300,300);
           // fullScreen(); 
            background(255);         // Bianco
            
            ellipse(width/4 * 1, height/4 * 1, 10,10); // x, y, larg, alt (Alto a sx)
            ellipse(width/4 * 3, height/4 * 1, 10,10); // x, y, larg, alt (Alto a dx)
            ellipse(width/4 * 1, height/4 * 3, 10,10); // x, y, larg, alt (Basso a sx)
            ellipse(width/4 * 3, height/4 * 3, 10,10); // x, y, larg, alt (Basso a dx)
}


void draw() {      
}

C'è un ulteriore problema: nel primo caso le dimensioni dello schermo sono quadrate mentre nel secondo rettangolari e anche i rapporti interni al disegno vengono modificati.

translate() e rotate() Indice

Possiamo allora traslare il punto d'origine al centro e fare riferimento ad esso con valori assoluti.

In [ ]:
void setup() {  
           // size(300,300);
            fullScreen(); 
            background(255);              // Bianco
            translate(width/2, height/2); // 0, 0 è al centro dello schermo
           
            ellipse(-100,  100, 10,10); // x, y, larg, alt (Alto a sx)
            ellipse( 100,  100, 10,10); // x, y, larg, alt (Alto a dx)
            ellipse(-100, -100, 10,10); // x, y, larg, alt (Basso a sx)
            ellipse( 100, -100, 10,10); // x, y, larg, alt (Basso a dx)
}


void draw() {       
}

Esiste anche un altro oggetto che ruota le coordinate dello schermo in radianti (da 0 a 2PI).

In [ ]:
void setup() {  
           // size(300,300);
            fullScreen(); 
            background(255);              // Bianco
            translate(width/2, height/2); // 0, 0 è al centro dello schermo
            rotate(PI/4);
           
            ellipse(-100,  100, 10,10); // x, y, larg, alt (Alto a sx)
            ellipse( 100,  100, 10,10); // x, y, larg, alt (Alto a dx)
            ellipse(-100, -100, 10,10); // x, y, larg, alt (Basso a sx)
            ellipse( 100, -100, 10,10); // x, y, larg, alt (Basso a dx)
}


void draw() {      
}

Per quanto riguarda p5.js possiamo utilizzare anche window.innerWidth e window.innerHeight.

File(s) di lavoro Indice

Processing

Quando salviamo un progetto viene creata una nuova cartella con all'interno un file.pde. La cartella e il file devono avere lo stesso nome.

Questa cartella deve contenere anche tutti i materiali (immagini, suoni, etc.) che saranno utilizzati nel progetto.

p5.js

Quando salviamo un progetto viene creata una nuova cartella con all'interno:

  • un file index.html ovvero la pagina web nella quale visualizzaremo il progetto.
  • una cartella libraries che contiene le librerie utilizzate nel progetto.
  • un file nome.js che contiene il codice su cui lavoriamo.
  • un file style.css che contiene istruzioni riguardo l'aspetto della pagina web (se utilizziamo l'IDE di Processing questo file si chiama sketch.properties).
In [11]:
from IPython.display import HTML
HTML('<center><img src="media/flusso.png" width="60%"></center>')
Out[11]:

Vediamo alcune caratteristiche basilari di questi files.

HTML

In [12]:
from IPython.display import HTML
HTML('<center><img src="media/html.png" width="60%"></center>')
Out[12]:

HTML + css

In [13]:
from IPython.display import HTML
HTML('<center><img src="media/duefile.png" width="60%"></center>')
Out[13]:

HTML + css + JavaScript

In [14]:
from IPython.display import HTML
HTML('<center><img src="media/trefile.png" width="60%"></center>')
Out[14]:

E' consuetudine separare le parti di codice in diversi files linkati.

In [15]:
from IPython.display import HTML
HTML('<center><img src="media/linka.png" width="60%"></center>')
Out[15]:

Eseguiamo in un editor la versione p5.js dell'ultimo progetto.

In [ ]:
function setup() {  
            createCanvas(windowWidth, windowHeight);
            background(255);              
            translate(width/2, height/2); 
            rotate(PI/4);
           
            ellipse(-50,  50, 10,10); 
            ellipse( 50,  50, 10,10); 
            ellipse(-50, -50, 10,10); 
            ellipse( 50, -50, 10,10); 
}


function draw() {      
}

Apri la pagina html

Il file index.html che è stato generato è il seguente.

In [ ]:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
    # Titolo della pagina
    <title>Test</title>
    
    <link rel="stylesheet" type="text/css" href="style.css">
        
    # Richiama p5 e le librerie o font o audio o video o altro...che sono online
    <script src="https://cdn.jsdelivr.net/npm/p5@1.7.0/lib/p5.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.min.js"></script>
    <script src="https://unpkg.com/tone-rhythm@0.0.2/dist/tone-rhythm.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/webmidi@latest/dist/iife/webmidi.iife.js"></script>
    <link rel="stylesheet" media="screen" href="https://fontlibrary.org//face/tt2020-style-b" type="text/css"/>
    
    # Richiama lo script
    <script src="a0_Test.js"></script>
    
  </head>
  <body>
  </body>
</html>

Tone.js Indice

A questo link possiamo scaricare i files con i codici seguenti.

Come specificato nell'Introduzione per quanto riguarda la parte audio i casi di studio che seguono e gli esercizi saranno svolti sia con SuperCollider (del quale è richieta la conoscenza come prerequisito a questo scritto) che con una libreria di JavaScript che si chiama Tone.js.

Vediamone le caratteristiche di base che sono simili a quelle di SuperCollider e di molti altri software dedicati all'audio.

Nel menù a sinistra della documentazione le Classi sono suddivise per tipologia.

  • Segnali - corrispondono più o meno alle Ugens di SuperCollider (generano o modificano segnali audio).

    • Source.
    • Effect.
    • Component.
    • Signal
  • Instrument - sono dei contenitori di algoritmi di sintesi o elaborazione del suono ottimizzati per la voice allocation.
  • Event - gestiscono diverse tecniche di sequencing per il controllo nel tempo dei parametri (proprietà) dei segnali e degli instrument.

Nei prossimi paragrafi sono affrontati gli argomenti principali che dovrebbero accompagnare all'autonomia per ulteriori approfondimenti personali.

A questo link troviamo numerose informazioni.

Operazioni preliminari.

  • Creiamo un nuovo progetto di p5.js come descritto in precedenza.

  • Andiamo a questo link.

  • Digitiamo 'tone.js'.

  • Copiamo il link minimizzato.

In [ ]:
https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.min.js
  • Apriamo il file index.html e sostituiamo il link che rimanda alla libreria p5.Sound con quello copiato.</br> N.B. Questa operazione va effettuata ogni volta che creiamo un nuovo progetto nel file 'index.html' corrispondente.

  • Salviamo il file index.html.

Segnali Indice

Come esempio significativo creiamo un algoritmo di sintesi molto semplice.

Oscillatore --> Inviluppo_ampiezza --> panner

Osserviamo che la struttura del file è la stessa di p5.js alla quale abbiamo aggiunto due nuove funzioni per poter controllare l'algoritmo di sintesi.

Una è valutata quando schiacciamo il pulsante del mouse (noteon) l'altra quando lo rilasciamo (noteoff).

La sintassi è intuitiva e commentata direttamente nello sketch.

In [ ]:
let myOsc;     // Variabili globali
let ampEnv;
let panner;

function setup() {
                  createCanvas(windowWidth, windowHeight);
                  background(220);

                  textAlign(CENTER, CENTER);
                  textSize(24);           
                  text("Clicca qua!",windowWidth/2,windowHeight/2);

// ----------------------------- Algoritmo di sintesi (definizione strumenti)
    
                  panner = new Tone.Panner().toDestination()  // Agli altoparlanti (sempre per primo)
    
                  ampEnv = new Tone.AmplitudeEnvelope({
                                                       attack:  0.02,  // da 0 a 2
                                                       decay:   0.05,  // da 0 a 2
                                                       sustain: 0.5,   // < 1
                                                       release: 0.8    // da 0 a 5
                  }).connect(panner);                                          // Invia l'output al panner

                  myOsc    = new Tone.Oscillator(440, "sine").connect(ampEnv); // Invia l'output all'inviluppo
                  myOsc.start();                                               // Accende l'oscillatore
}                

function draw() {
                     // Non mettiamo segnali audio in queta funzione!
}

// ----------------------------- Controllo parametri (interazione o sequencing)

let toneStart = 0;

function mousePressed(){   
                        if (toneStart = 0){
                                            Tone.start();
                                            toneStart = 1;
                                           }
    // Variabili locali                          
                        let freq = random(300,800);            // Frequenza in Hz oppure...
                        let note = ["A4","C#4","E4","G#4"];    // Array con nome note
                            note = random(note);               // Sceglie random dall'Array
                        let amp  = random(1.0);                // Ampiezza

                        //myOsc.type = "square";               // Modifica dinamicamente la forma d'onda
                        //myOsc.type = "triangle";
                        //myOsc.type = "sawtooth";
                        myOsc.type = "sine"; 
                        myOsc.partials = [0.011, 0.5,0.2,0.8]; // Ampiezze dei parziali (additiva)
                        
                        myOsc.frequency.value = freq;          // Setta la frequenza
                        
                                                               // Trigger per inviluppi CON fase di sostegno
                        //ampEnv.triggerAttack("+0", amp);     // (ritardo di attacco, ampiezza)
                        
                                                               // Trigger per inviluppi SENZA fase di sostegno
                                                               // (tempo di sostegno, ritardo di attacco, ampiezza)
                        ampEnv.triggerAttackRelease(0.5,"+0", amp); 
                        
                        panner.pan.value = random(-1,1);       // Setta il pan
}

function mouseReleased(){  
                         //ampEnv.triggerRelease() // Per inviluppi con fase di sostegno
}

Apri la pagina html

Settare parametri Indice

Gli oggetti di Tone.js hanno delle proprietà (Properties) che sono decritte negli Help files delle API.

Alcune di queste possono essere modificate dinamicamente.

Nel codice precedente ad esempio abbiamo modificato la forma d'onda dell'oscillatore mentre per i valori di frequenza e pan abbiamo utilizzato la seguente sintassi.

In [ ]:
nome.parametro.value = valore;

Il valore viene inviato immediatamente come quando in SuperCollider utilizziamo il messaggio '.set(\arg, val)'.

Esattamente come in SuperCollider possiamo trasformare (.lag() e affini) il valore singolo in una rampa (segnale di controllo).

Nel codice seguente alcune possibilità offerte (decommentare le singole linee).

In [ ]:
let myOsc;
let ampEnv;
let panner;

function setup() {
                  createCanvas(windowWidth, windowHeight);
                  background(220);

                  textAlign(CENTER, CENTER);
                  textSize(24);           
                  text("Clicca qua!",windowWidth/2,windowHeight/2);
    
// ----------------------------- Algoritmo di sintesi (definizione strumenti)
    
                  panner = new Tone.Panner().toDestination()  
                  ampEnv = new Tone.AmplitudeEnvelope({
                                                       attack:  0.02,    
                                                       decay:   0.05,     
                                                       sustain: 0.5,    
                                                       release: 0.8    
                                                       }).connect(panner);                                  
                  myOsc    = new Tone.Oscillator(440, "sine").connect(ampEnv);
                  myOsc.start()
}                

function draw() {

}

// ----------------------------- Controllo parametri (interazione o sequencing)

let toneStart = 0;

function mousePressed(){   
                        if (toneStart = 0){
                                            Tone.start();
                                            toneStart = 1;
                                           }

                        //myOsc.frequency.value = random(300,1600);
                        //myOsc.frequency.setValueAtTime(random(300,1600), "+1");              // Target, delay
                        //myOsc.frequency.rampTo(random(300,1600), 0.5);                       // Target, ramptime
                        //myOsc.frequency.linearRampToValueAtTime(random(300,1600), "+2");     
                        //myOsc.frequency.exponentialRampToValueAtTime(random(300,1600), "+2") 
                        //myOsc.frequency.setTargetAtTime(random(300,1600), "+2", 0.1); // Target, delay, ramptime
                        //myOsc.frequency.linearRampTo(random(300,1600), 0.8);
    
                        myOsc.frequency.exponentialRampTo(random(300,1600), 0.8);
                        panner.pan.rampTo(random(-1,1), 0.25); 
                        ampEnv.triggerAttack("+0", 1);   // CON fase di sostegno (noteon)
}

function mouseReleased(){  
                         ampEnv.triggerRelease();        // (noteoff)
}

Apri la pagina html

Instrument Indice

Tone.js fornisce una serie di strumenti con dei modelli di algoritmi di sintesi classici già programmati al loro interno.

Possiamo pensarli come delle SynthDef di SuperCollider già definite.

Come esempio utilizziamo Synth che è simile alla prima parte dell'algoritmo illustrato nel primo codice ovvero un oscillatore il cui segnale in output entra in un inviluppo d'ampiezza (per maggiori info consultiamo l'help file).

Tutti gli instrument sono monofonici.

In [ ]:
let mySynth;
let panner;

function setup() {
                  createCanvas(windowWidth, windowHeight);
                  background(220);
                  
                  textAlign(CENTER, CENTER);
                  textSize(24);           
                  text("Clicca qua!",windowWidth/2,windowHeight/2);

// ----------------------------- Algoritmo di sintesi (definizione strumenti)
    
                  panner = new Tone.Panner().toDestination()  // Panner ESTERNO
    
                  mySynth = new Tone.Synth({
                                            oscillator: {     // Se non specifichiamo prende default
                                                         type: "triangle" 
                                                         },
                                            envelope: { 
                                                       attack:  0.02, 
                                                       decay:   0.05, 
                                                       sustain: 0.5,  
                                                       release: 0.8   
                                                       }
                                            }).connect(panner);   
}

function draw() {
    
}

// ----------------------------- Controllo parametri (interazione o sequencing)

let toneStart = 0;

function mousePressed(){   
                        if (toneStart = 0){
                                            Tone.start();
                                            toneStart = 1;
                                           }
    
// ----------------------------- Oscillatore 
                        let freq = random(900,1400);          // Frequenza in Hz oppure
                        let note = ["A4","C#4","E4","G#4"];   // Stringa con nome note
                            note = random(note);
                        let amp  = random(1.0);
    
                        //mySynth.oscillator.type = "square";      // Eventuale modifica dinamica della 
                                                                   // forma d'onda dell'oscillatore
// ----------------------------- Inviluppo
                        mySynth.envelope.attack  = random(0.01,2); // Eventuale modifiche dinamiche di uno 
                        mySynth.envelope.decay   = random(0.01,2); // o più parametri dell'inviluppo
                        mySynth.envelope.sustain = random(0.01,1);
                        mySynth.envelope.release = random(0.01,5);
    
                        panner.pan.rampTo(random(-1,1), 0.25);
                        mySynth.triggerAttack(freq,"+0", amp);     // (noteon)
}

function mouseReleased(){  
                         mySynth.triggerRelease();                 // (noteoff)
}

Apri la pagina html

PoliSynth Indice

Per rendere polifonico un qualsiasi instrument possiamo utilizzare PoliSynth che è semplicemente un contenitore di instrument.

In [ ]:
let myPoly;
let panner;

function setup() {
                  createCanvas(windowWidth, windowHeight);
                  background(220);

                  textAlign(CENTER, CENTER);
                  textSize(24);           
                  text("Clicca qua!",windowWidth/2,windowHeight/2);

// ----------------------------- Algoritmo di sintesi (definizione strumenti)
    
                  panner  = new Tone.Panner().toDestination();
                                            // tipo,       n_voci                 
                  myPoly  = new Tone.PolySynth(Tone.Synth, 56).connect(panner); 
    
                  myPoly.set({              // Per settare i valori '.set({ })'       
                              oscillator: {                                  
                                        type: "sine",
                                        partials: [1, 0.5,0.2,0.8]
                                          },
                              envelope: { 
                                        attack:  0.02, 
                                        decay:   0.05,  
                                        sustain: 0.5, 
                                        release: 5    
                                        }
                            });
}

function draw() {
  
}

// ----------------------------- Controllo parametri (interazione o sequencing)

let freq;   // Variabili globali (devono valere sia in mousePressed() che in mouseReleased)
let note;   
let amp;
 
let toneStart = 0;

function mousePressed(){   
                        if (toneStart = 0){
                                            Tone.start();
                                            toneStart = 1;
                                           }
                        freq = [random(300,1600),random(300,1600),random(300,1600),random(300,1600)];
                        note = ["A4","C#4","E4","G#4"];   
                        amp  = 0.2;
                        let durs  = [0.1,  0.5,    1,    2];

                        //myPoly.triggerAttack(freq, "+0", amp);   // Prova a modificare il delay  
                        myPoly.triggerAttackRelease(freq, durs, "+0", amp);                        
                        panner.pan.rampTo(random(-1,1), 0.25);
}

function mouseReleased(){  
                         //myPoly.triggerRelease(freq[3], "+0.2"); // Bisogna indicare quali note 
                         //myPoly.triggerRelease(freq[2], "+0.6");
                         //myPoly.triggerRelease(freq[1], "+0.9");
                         //myPoly.triggerRelease(freq[0], "+0.9")
}

Apri la pagina html

Sequenze mono Indice

Possiamo definire sequenze di eventi nel tempo in diversi modi.

Una per instrument monofonici può essere definita con l'oggetto Sequence.

Accetta come primo argomento una funzione che viene valutata a tempi delta regolari (beat).

Questa funzione accetta due parametri:

  • time - il tempo delta tra i beat che può essere specificato sia in secondi che in forma simbolica (la sintassi è specificata come commento nel codice).
  • note - la sequenza delle altezze (in Hertz oppure in forma simbolica) sotto forma di Array. Ogni altezza corrisponde a un beat e se utilizziamo un Array 2D le altezze definita nei subArray definiscono la suddivisione del beat. Se vogliamo specificare una pausa scriviamo null.

Questi parametri sono passati come secondo e terzo argomento di Sequence.

In [ ]:
let mySynth;
let panner;
let mySeq;

//  Beats      1     2                          3           4                    5
let notes = ["C4", ["E4", "F4", "F#4", "G4"], ["E4", "E4"],["E4", null, "F#4"], ["G4","G4"]];
let amp;

function setup() {
                  createCanvas(windowWidth, windowHeight);
                  background(220);
                  
                  textAlign(CENTER, CENTER);
                  textSize(24);           
                  text("Clicca qua!",windowWidth/2,windowHeight/2); 

// ----------------------------- Algoritmo di sintesi (definizione strumenti)
    
                  panner  = new Tone.Panner().toDestination();
                  mySynth = new Tone.Synth({
                                            oscillator: {
                                                         type: "square" 
                                                         },
                                            envelope: { 
                                                       attack:  0.02, 
                                                       decay:   0.05,  
                                                       sustain: 0.5,
                                                       release: 0.8  
                                                       }
                                            }).connect(panner);

// ----------------------------- Definizione sequencing
    
                                                  
                  mySeq = new Tone.Sequence(      // beat  arg   valuta la funzione ad ogni beat 
                                            function(time, note) { 
                                                                  amp  = random(1.0);
                                                                  panner.pan.rampTo(random(-1,1), 0.25);
                                                                  mySynth.triggerAttackRelease(note, 0.1, time, amp)
                                                                  }, 
                                                                  notes, '4n'); 
                                                  //               arg    beat  (arg della funzione)

                  Tone.Transport.bpm.value = 80;  // Setta i BPM
                  Tone.Transport.start();         // Accende il Clock 
}

// 16n: sedicesimi (4 per beat)
// 8n: ottavi (2 per beat)
// 4n: quarti (1 per beat)
// 2n: metà (2 beats per nota)
// 1n: intero (4 beats per nota)
// Pause: null

function draw() {
  
}

let toneStart = 0;

function mousePressed(){   
                        if (toneStart = 0){
                                           Tone.start();
                                           toneStart = 1;
                                           }
                        mySeq.start();
}

function mouseReleased(){  
                         mySeq.stop();
}

Apri la pagina html

Sequenza di eventi Indice

Possiamo definire sequenze di ToneEvent.

Un ToneEvent si presenta nella seguente forma sintattica dove time corrisponde al tempo di onset e può essere definito in diversi modi:

  • Numeri - secondi.
  • Valori musicali - stringhe.
    • il beat è relativo al valore in BPM di Tone.Transport.bpm
    • “4n” = quarto
    • “8t” = ottavo di terzina
    • “2m” = due misure
    • “8n.” = ottavo puntato
    • etc.
  • Battute - "2:1:2" = Battuta 2, primo quarto, secondo sedicesimo.
In [ ]:
{ time: "0:0:0", note: "C4", velocity: 0.9 }

Un Array di ToneEvent può essere letto dall'oggetto Part che accetta anche altri parametri (specificati nel codice seguente).

In [ ]:
let mySynth;
let panner;
let mySeq;

//                   onsets
let score = [
             { time: "0:0:0", note: "C4", velocity: 0.9 }, // 1 ToneEvent
             { time: "0:1:0", note: "d4", velocity: 0.2 },
             { time: "0:1:2", note: "E4", velocity: 0.3 },
             { time: "0:1:3", note: "F4", velocity: 0.4 },
             { time: "0:2:0", note: "E4", velocity: 0.7 }, // Bicordo...
             { time: "0:2:0", note: "G4", velocity: 0.5 }, // Bicordo...
             { time: "0:3:0", note: "A4", velocity: 0.8 },
            ];

function setup() {
                  createCanvas(windowWidth, windowHeight);
                  background(220);

                  textAlign(CENTER, CENTER);
                  textSize(24);           
                  text("Clicca qua!",windowWidth/2,windowHeight/2); 
    
// ----------------------------- Algoritmo di sintesi (definizione strumenti)
    
                  panner  = new Tone.Panner().toDestination();
                  myPoly  = new Tone.PolySynth(Tone.Synth, 8).connect(panner);
                  myPoly.set({                         
                              oscillator: {
                                          type: "triangle"
                                          },
                              envelope: { 
                                          attack:  0.02, 
                                          decay:   0.05,  
                                          sustain: 0.5, 
                                          release: 5  
                                          }
                            });

// ----------------------------- Definizione sequencing
    
                  mySeq = new Tone.Part(
                                      // La funzione viene valutata ad ogni 'time' (onset)
                                      // Notazione alternativa alla precedente per funzione
                                      // beat  args
                                        (time, value) => {  
                                                          
                                            myPoly.triggerAttackRelease(value.note, "8n", time, value.velocity);
                                            panner.pan.rampTo(random(-1,1), 0.25);
                                                          }, 
                                        score);  // Array di ToneEvent (value)

                  Tone.Transport.bpm.value = 80; // Setta i BPM
                  Tone.Transport.start();        // Accende il Clock 
}

function draw() {
  
}

let toneStart = 0;

function mousePressed(){   
                        if (toneStart = 0){
                                            Tone.start();
                                            toneStart = 1;
                                          }
                        mySeq.start();
                        mySeq.loop = 4;              // Ripete 4 volte la sequenza
                        mySeq.loopStart    = "0:0:0" // Inizio loop
                        mySeq.loopEnd      = '1m'    // Fine loop m = misura
                        mySeq.playbackRate = 1       // a questa velocità
                        mySeq.probability  = 1;      // Probabilità random tra 0 e 1
}

function mouseReleased(){  
                         mySeq.stop();
}

Apri la pagina html

Tone-Rhythm.js Indice

Musicalmente non è particolarmente intuitivo pensare il tempo per onsets.

Possiamo allora utilizzare un'altra libreria di JavaScript (Tone-Rhythm.js) che ci consente di definire altezze, durate ed intensità sotto forma di Arrays similmente a quanto avviene ad esempio in SuperCollider iterando gli elementi con Routines.

Per utilizzare la libreria dobbiamo compiere la stessa operazione effettuata per Tone.js ovvero copiare nel file index.html il link che la richiama (verifichiamo il link alla versione più recente su GitHub).

In [ ]:
<script src="https://unpkg.com/tone-rhythm@0.0.2/dist/tone-rhythm.min.js"></script>

L'oggetto toneRhythm.mergeMusicDataPart({ }) converte gli Array in formato JSON che può essere letto da Part.

In [ ]:
let mySynth;
let panner;
let mySeq;
let score;

//                    bicordo                 pausa
let freq = ["Bb4", ["Ab4","C5"], "G4",        null,  "Eb4"];
//                             somma le durate
let durs = ['8n',   '8n',        ["4n","8n"], '8n',  "4n" ];
let vels = [0.1,    0.2 ,        0.5,         0,     1    ];

function setup() {
                  createCanvas(windowWidth, windowHeight);
                  background(220);

                  textAlign(CENTER, CENTER);
                  textSize(24);           
                  text("Clicca qua!",windowWidth/2,windowHeight/2);   

// ----------------------------- Algoritmo di sintesi (definizione strumenti)
    
                  panner  = new Tone.Panner().toDestination();
                  myPoly  = new Tone.PolySynth(Tone.Synth, 8).connect(panner);
                  myPoly.set({                         
                              oscillator: {
                                          type: "triangle"
                                          },
                              envelope: { 
                                          attack:  0.02, 
                                          decay:   0.05, 
                                          sustain: 0.5, 
                                          release: 0.3  
                                          }
                            });

// ----------------------------- Definizione sequencing

                  score = toneRhythm.mergeMusicDataPart({       // Formatta gli Array in JSON
                                                         rhythms:    durs,
                                                         notes:      freq,
                                                         velocities: vels
                                                         });

                  mySeq = new Tone.Part(
                                        (time, value) => {

                           myPoly.triggerAttackRelease(value.note, value.duration, time, value.velocity);
                           panner.pan.rampTo(random(-1,1), 0.25);
                                                         }, 
                                        score);

                  Tone.Transport.bpm.value = 80; 
                  Tone.Transport.start();        
}

function draw() {

}

let toneStart = 0;

function mousePressed(){   
                        if (toneStart = 0){
                                            Tone.start();
                                            toneStart = 1;
                                          }
                        mySeq.start();
                        mySeq.loop = 4;              // Ripete 4 volte la sequenza
                        mySeq.loopStart    = "0:0:0" // Inizio loop
                        mySeq.loopEnd      = '1m'    // Fine loop m = misura
                        mySeq.playbackRate = 1       // a questa velocità
                        mySeq.probability  = 1;      // Probabilità random tra 0 e 1
}

function mouseReleased(){  
                         mySeq.stop();
}

Apri la pagina html

Unità di misura Indice

Per comodità riassumiamo le diverse unità di misura utilizzabili.

Tempo

  • Numeri - secondi.
  • Valori musicali - stringhe.
    • il beat è relativo al valore in BPM di Tone.Transport.bpm
    • “4n” = quarto
    • “8t” = ottavo di terzina
    • “2m” = due misure
    • “8n.” = ottavo puntato
    • etc.
  • Battute - "2:1:2" = Battuta 2, primo quarto, secondo sedicesimo.

Frequenze

  • Numeri - Hertz.
  • Simboli - Stringhe - "C4" --> Do centrale.
  • Pause - nil

Esistono anche librerie per definire scale, utilizzare microtoni ed altro, inoltre nei seguenti casi di studio utilizzeremo ulteriori funzionalità di Tone.js ma per gli approfondimenti viene richiesta curiosità e una ricerca personale.

Indice

Esercitazione n° 1

Realizzare un progetto audio (senza parte visiva) che impieghi anche elementi e oggetti di Tone.js non presentati nei paragrafi precedenti.

Primo caso di studio Indice

Partiamo con il verificare diverse possibilità di creare un sound desing per il progetto grafico appena illustrato leggermente modificato.

In [ ]:
void setup() {  
            size(300,300);
            background(255);              
            translate(width/2, height/2); 
           // rotate(PI/4);
            fill(0);
            
            ellipse(-75,  75, 10,10); 
            ellipse( 75,  75, 10,10); 
            ellipse(-75, -75, 10,10); 
            ellipse( 75, -75, 10,10); 
}


void draw() {       
}
In [16]:
from IPython.display import HTML
HTML('<center><img src="media/caso_1.png" width="30%"></center>')
Out[16]:

Analizziamo le caratteristiche visive.

  • Statico.
  • Semplice.
  • Simmetrico.
  • Caratterizzato da un pattern ritmico visivo di elementi morfologicamente simili e riconoscibili.

Un obbiettivo che ci diamo consiste nel realizzare due piani percettivi indipendenti (audio / video) che descrivono la stessa idea.

Processing e SuperCollider Indice

La prima cosa che possiamo fare è pensare un suono che possa rappresentare al meglio a livello uditivo il singolo elemento visivo.

In questo caso il punto nero potrebbe essere ben rappresentato da un impulso di rumore bianco o simile.

In [ ]:
s.boot;
s.scope;
s.plotTree;

(
SynthDef(\punto, {arg dur=1, t_gate=0, done=0, pos=0;
                  var sig, bpf, env, pan;
                      sig = ClipNoise.ar;
                      bpf = Env.perc(dur*0.1,dur*0.9);
                      env = EnvGen.ar(bpf,t_gate,doneAction:done);
                      pan = Pan2.ar(sig*env,pos);
                  Out.ar(0, pan)
                  }).add;
)

Synth(\punto,[\done,2,\dur,0.1,\pos,0,\t_gate,1]);

Il problema principale nel sonificare un'immagine statica consiste nel fatto che è priva della dimensione temporale.

Per ovviare, in questo caso possiamo sfruttare il fatto che è un pattern semplice e simmetrico, generando una pattern ritmico di quattro impulsi separati da una pausa più lunga.

In [ ]:
(
r = Routine.new({
            inf.do({
                    4.do({
                          Synth(\punto,[\done,2,\dur,0.1,\pos,0,\t_gate,1]);
                          1.wait;
                        });
                    2.wait;
                    })
                 }).reset.play;
)

r.stop;

La percezione del pattern a livello uditivo ora funziona ma manca la dimensione verticale (la sequenza rende l'idea di quattro punti ma tutti posizionati sulla stessa linea).

Possiamo allora sfruttare il panning posizionando i singoli impulsi in punti diversi del fronte stereofonico o, ancora meglio in altoparlanti diversi in un sistema di diffusione quadrifonico in cui gli altoparlanti stessi assumono idealmente la funzione dei punti.

In [ ]:
(
r = Routine.new({
            inf.do({
                    4.do({
                          Synth(\punto,[\done,2,\dur,0.1,\pos,[-1,-0.3,0.3,1].choose,\t_gate,1]);
                          1.wait;
                        });
                    2.wait;
                    })
                 }).reset.play;
)

r.stop;

La scelta randomica della posizione vuole evitare la meccanicità percettiva che potrebbe instaurarsi con patterns così elementari.

p5.js e Tone.js Indice

In questa versione relizziamo una piccola variazione visiva ruotando semplicemente di 90° il pattern.

In [ ]:
function setup() {
                createCanvas(windowWidth,displayHeight);
                background(255);              
                translate(width/2, height/2); 
                rotate(PI/4);
                fill(0);
            
                ellipse(-75,  75, 10,10); 
                ellipse( 75,  75, 10,10); 
                ellipse(-75, -75, 10,10); 
                ellipse( 75, -75, 10,10); 
}


function draw() {      
}
In [17]:
from IPython.display import HTML
HTML('<center><img src="media/caso_1_2.png" width="30%"></center>')
Out[17]:

In questo caso la sonificazione che abbiamo realizzato nell'esempio precedente potrebbe essere meno efficace in quanto la percezione del pattern visivo è leggermente diversa (la dimensione verticale assume maggiore importanza).

Possiamo sottolineare questa carateristica diversificando l'altezza dei suoni.

Per farlo dobbiamo scagliere un suono con una forma d'onda periodica e un timbro il più neutro possibile (da una semplice sinusoide a una sintesi additiva a spettro armonico fisso con pochi parziali).

Aggiungiamo un riverbero.

In [ ]:
let rev;
let panner;
let ampEnv;
let myOsc;

let mySeq;
let notes = ["G5", "D6", "G5", "C5"];  // Sequenza pitches - Array
let pan   = [-1,      0,    1,   0 ];  // panning
let amp   = [0.75,    1, 0.75, 0.25];  // amp

function setup() {
    
// ----------------------------- Video
    
                createCanvas(windowWidth, windowHeight);
                background(255);      
                
                textAlign(CENTER, CENTER);
                textSize(18);           
                text("Click Audio on/off",windowWidth/2,windowHeight/2);   

                translate(width/2, height/2); 
                rotate(PI/4);
                fill(0);
            
                ellipse(-75,  75, 10,10); 
                ellipse( 75,  75, 10,10); 
                ellipse(-75, -75, 10,10); 
                ellipse( 75, -75, 10,10); 
    
// ----------------------------- Algoritmo di sintesi (definizione strumenti)

                rev       = new Tone.Reverb().toDestination();  // Riverbero
                rev.decay = 1.7;                                // Secondi
                rev.wet   = 0.8;                                // 0 -> 1
                panner    = new Tone.Panner().connect(rev);
                ampEnv    = new Tone.AmplitudeEnvelope({attack:0.02,decay:0,Sustain:1,release:0.1}).connect(panner);
    
                ampEnv.attackCurve  = "ripple";                 // Curve non lineari                      
                ampEnv.releaseCurve = "exponential"; 
                myOsc     = new Tone.Oscillator(440, "sine").connect(ampEnv); 
                myOsc.partials = [0.011, 0.5,0.2,0.8]; 
                myOsc.start();   

// ----------------------------- Definizione sequencing

            let i     = 0;                                     // Inizializza il contatore
            let dur   = '4n';     
                mySeq = new Tone.Sequence(    
                                          function(time, note) {
                                                                let amps;
                                                                let pans;

                                                                console.log(i);    // Come print()
                                              
                                                                amps  = amp[i];    // Itera gli Arrays
                                                                pans  = pan[i];
                                                            
                                                                myOsc.frequency.value = note;
                                                                panner.pan.rampTo(pans, dur);
                                                                ampEnv.triggerAttackRelease(0.02, "+0", amps); 

                                                                i = (i+1) % notes.length;  // Aggiorna il counter
                                                                                           // Operatore modulo
                                                                }, 
                                                                notes, dur); 
                Tone.Transport.bpm.value = 76;  
                Tone.Transport.start();  
                 
}


function draw() {      

}

let toneStart = 0;
let i = 0;                             // Inizializza un contatore

function mousePressed(){   
                        if (toneStart = 0){
                                            Tone.start();
                                            toneStart = 1;
                                          }
                        i = (i+1) % 2; // Aggiorna un contatore che va da 0 a 1
                        if(i==0){
                                 mySeq.stop(); 
                                }
                            else{
                                 mySeq.start();
                                 }
}

Apri la pagina html

Nuovi argomenti:

Riverberi Indice

Reverb, FreeVerb e JCReverb.

Rampe non lineari Indice

Negli inviluppi possiamo utilizzare anche curve non lineari.

- "linear"
- "exponential"
- "sine"
- "cosine"
- "bounce"
- "ripple"
- "step"

Per visualizzarle - Plot - clicchiamo su run.

Sequencing con Arrays Indice

Possiamo iterare gli elementi di uno o più Array definendo un contatore per gli indici

In [ ]:
String note[] = {"G5", "D6", "G5", "C5"};  // Sintassi per gli Array in Processing
float  amp[]  = {0.75,    1, 0.75, 0.25};  

int id = 0;                                // Inizializza il counter FUORI dalla funzione

void draw() {                              // Necessario per campionare il mouse
  
}

void mousePressed() {
                     println(id, note[id], amp[id]);  // Richiama gli indici
                     id = (id+1) % note.length;       // Aggiorna il contatore
}

Indice

Esercitazione n° 2

Realizzare un progetto audiovisivo focalizzato sulle correlazioni elementari tra pattern visivi e sonori statici.
Modificare la posizione dei punti e/o aggiungerne altri.
Decidere la sonificazione in base alle caratteristiche del pattern visivo scelto.
Le due partiture devono essere indipendenti.

Oggetti 2D Indice

Possiamo includere in un disegno quattro tipi di oggetti bidimensionali (i link sono al reference di p5js che vale anche per gli oggetti corrispondenti di di Processing).

  • Figure geometriche (Shapes).
  • Immagini.
  • Elementi tipografici.
  • GUI come sliders, buttoms, etc.

Figure geometriche Indice

Nel primo caso di studio abbiamo disegnato dei punti (che in realtà erano delle ellissi...).

Questo oggetto fa parte di una categoria che comprende figure geometriche a due o tre dimensioni.

Molti di questi hanno delle proprietà comuni.

  • Arrotondamento dei contorni - smooth, noSmooth().
  • Coordinate del punto d'origine per posizionare l'oggetto nel disegno - ellipseMode(), rectMode().
  • Colore interno all'oggetto - fill(), nofill, color().
  • Contorno dell'oggetto - noStroke(), strokeCap(). strokeJoin(), strokeWeight.
  • Colore del contorno stroke().

Per quanto riguarda i colori possiamo definire il modo attraverso il quale li componiamo con l'oggetto colorMode().

  • RGBA.
  • HSBA.

L'oggetto createColorPicker() genera una paletta dei colori che riporta i valori in entrambe i formati in modo più intuitivo.

Il codice seguente riassume tutte le principali funzionalità e vale sia per Processing che per p5.js.

In [ ]:
int sx = 50;                             // Processing
int sy = 50;

//let sx = 50;                           // p5js
//let sy = 50;

  void setup() {                         // Processing
//function setup() {                     // p5js
                    
                size(335,560);           // Processing
                //createCanvas(335,560); // p5js
                background(215); 
                colorMode(RGB, 255);
                smooth();
    
// ----------------------------- arc()
    
                ellipseMode(CENTER);   
                noFill();                            // Senza contenuto
                strokeWeight(5);                     // Altezza contorno
                                                     // x y largh alt inizio fine modo
                arc(sx + 25, sy + 0, 50, 50, HALF_PI, PI, OPEN); 

                fill(150);                           // Grigi (0-255)
                arc(sx +  100, sy + 0, 50, 50, PI*0, PI*1.0, CHORD);
                
                fill(80,60);                         // Grigio, Alpha
                arc(sx + 125, sy + 0, 50, 50, PI*0, PI*1.5, PIE);

// ----------------------------- ellipse()

                fill(123,234,145);                   // RGB
                stroke(234,16,165);                  // Colore contorno
                ellipse(sx + 200, sy + 0, 50, 50);   // x y largh alt 

                fill(132,134,245,200);               // RGBA
                noStroke();

// ----------------------------- circle()
    
                circle(sx + 215, sy + 0, 50);        // x y raggio

                rectMode(CENTER); 
                fill(0,0,255,160);  
                rect(sx + 25, sy + 100, 50, 30, 10); // x y largh alt curva1 curva2 etc

// ----------------------------- rect()
    
                noFill(); 
                stroke(100); 
                strokeWeight(2);
                rect(sx + 110, sy + 100, 50, 50, 10);   // Processing
                //rect(sx + 110, sy + 100, 50, 50, 10); // p5js
// ----------------------------- square()
    
                fill(143,219,198,189);
                strokeJoin(BEVEL);                   // Tipo arrotondamento
                square(sx + 210, sy + 100, 50);

// ----------------------------- line()
    
                stroke(0);
                strokeCap(ROUND);                    // Tipo arrotondamento
                line(sx + 0, sy + 175, sx + 50, sy + 175); // x1 y1 x2 y2

                strokeCap(SQUARE);
                strokeWeight(1);
                stroke(255);
                line(sx + 75, sy + 185, sx + 150, sy + 165);
               
                strokeCap(PROJECT);
                strokeWeight(5);
                stroke(0);
                line(sx + 175, sy + 175, sx + 225, sy + 175);

// ----------------------------- triangle()
    
                strokeWeight(1);
                fill(200,0,0);               // In senso orario
                triangle(sx +  0, sy + 225,  // x1 y1 
                         sx + 50, sy + 275,  //           x2 y2 
                         sx +  0, sy + 275); // x3 y3 

                stroke(20);
                strokeJoin(ROUND);
                strokeWeight(10);
                noFill();           
                triangle(sx + 125, sy + 225,  
                         sx + 150, sy + 250,   
                         sx +  75, sy + 275);   

                noStroke();
                fill(0,200,0, 50);           // Con Alpha...     
                triangle(sx + 215, sy + 225,  
                         sx + 250, sy + 275,  
                         sx + 175, sy + 275);  

// ----------------------------- quad()
    
                strokeWeight(1);
                fill(100,134,155);           // In senso orario
                quad(sx +  0, sy + 320,  
                     sx + 50, sy + 335,  
                     sx + 50, sy + 360,  
                     sx +  0, sy + 375); 
         
                stroke(20);
                strokeJoin(ROUND);
                strokeWeight(2);
                noFill();           
                quad(sx +  75, sy + 335, 
                     sx + 150, sy + 335,  
                     sx + 150, sy + 360,  
                     sx +  75, sy + 360); 
                 
                noStroke();
                fill(167,100,0, 50);         // Con Alpha...     
                quad(sx + 175, sy + 335,  
                     sx + 250, sy + 320,  
                     sx + 250, sy + 345,  
                     sx + 175, sy + 360); 

// ----------------------------- Shape()
    
                stroke(20); 
                fill(167,100,112);    
                beginShape();
                vertex(sx +   0, sy + 420);  // Singolo punto
                vertex(sx +  55, sy + 435);
                vertex(sx +  40, sy + 460);
                vertex(sx +  20, sy + 450);
                vertex(sx + 15, sy + 485);
                endShape(CLOSE);

// ----------------------------- Curved Shape()
    
                strokeWeight(5);
                point(sx +  80, sy + 420);
                point(sx + 120, sy + 440); 
                point(sx + 100, sy + 460);
                point(sx + 140, sy + 480);
                strokeWeight(1);
                noFill();
                beginShape();
                curveVertex(sx +  80, sy + 420);
                curveVertex(sx +  80, sy + 420);
                curveVertex(sx + 120, sy + 440);
                curveVertex(sx + 100, sy + 460);
                curveVertex(sx + 140, sy + 480);
                curveVertex(sx + 140, sy + 480);
                endShape();

                strokeWeight(5);
                point(       sx + 190, sy + 420);
                point(       sx + 240, sy + 430);
                point(       sx + 190, sy + 460);
                point(       sx + 240, sy + 480);

// ----------------------------- bezier()
    
                strokeWeight(1);
                beginShape();
                vertex(      sx + 190, sy + 420); // x1 y1
                bezierVertex(sx + 240, sy + 430,  // x2 y2
                             sx + 190, sy + 460,  // x3 y3
                             sx + 240, sy + 480); // ancora1 ancora2
                endShape();       
}


  void draw() {                         // Processing
//function draw() {                     // p5js    

}
In [18]:
from IPython.display import HTML
HTML('<center><img src="media/geom.png" width="30%"></center>')
Out[18]:

Apri la pagina html

Possiamo trovare ulteriori funzionalità negli Esempi.

Immagini Indice

Possiamo visualizzare immagini memorizzate in files.

I formati accettati sono: .gif, .jpg, .png e .svg.

Consiglio di mettere tutte le immagini in una cartella dedicata all'interno della cartella principale.

Carichiamo l'immagine con l'oggetto loadImage() possibilmente in una funzione preload() per essere sicuri di caricarlo prima della visualizzazione.

Quando caricato possiamo inserire nel canvas quante copie vogliamo definendone la posizione e le dimensioni.

Possiamo modificarne l'origine con l'oggetto imageMode(). Possiamo impiegare un'immagine come background.

Possiamo modificarne il colore e la trasparenza con l'oggetto tint().

Possiamo applicare dei filtri con l'oggetto filter() che agisce sugli oggetti che lo precedono nel codice (anche shapes).

Esempio Processing.

  • Dobbiamo assegnare le immagini a un particolare tipo di data: PImage.
  • Se vogliamo un'immagine come background il size deve corrispondere alle sue dimensioni.
In [ ]:
PImage img;
PImage bg; 

void setup() {
  
            img = loadImage("img/picasso.jpg"); // Carica l'immagine
            bg  = loadImage("img/boccioni.jpg"); 

            size(208, 209);            // Se usiamo un'immagine come background 
                                       // deve avere le stesse dimensioni
            background(bg);            // Immagine come background
            //background(210);

            imageMode(CORNER);         // Default
            //imageMode(CENTER);
            
            filter(BLUR,3);            // Solo su ciò che precede   
            image(img, 10, 10);        // x y
            
            tint(255, 150);            // RGBA   
            image(img, 10, 20+img.height, 
                  img.width  * 0.3,    // Larghezza
                  img.height * 0.3);   // Altezza
                  
            noTint();                  // Annulla tint()    
            image(img, 130, 30+img.height, 
                  img.width  * 0.17,           
                  img.height * 0.1);   // Deformata
}

void draw() {                  
}
In [19]:
from IPython.display import HTML
HTML('<center><img src="media/imma0.png" width="30%"></center>')
Out[19]:

Esempio p5.js.

  • Dobbiamo caricare le immagini prima della funzione setup().
  • Se vogliamo un'immagine come background possiamo utilizzare qualsiasi size del canvas.
In [ ]:
let img;
let bg;  

function preload() {
                    img = loadImage('img/picasso.jpg'); // Carica l'immagine
                    bg  = loadImage('img/boccioni.jpg'); 
}

function setup() {
                  createCanvas(windowWidth, windowHeight);
                  background(bg);            // Immagine come background
                  //background(210);

                  imageMode(CORNER);         // Default
                  //imageMode(CENTER);
    
                  filter(THRESHOLD,0.3);     // Solo su ciò che precede

                  image(img, 20, 20);        // x y

                  tint(255, 150);            // RGBA
    
                  image(img, 20+img.width, 20+img.height, 
                        img.width / 2,       // Larghezza
                        img.height / 2);     // Altezza
                  
                  noTint();                  // Annulla tint()    
                  image(img, 20, 215, 
                        img.width,           
                        img.height * 0.22);  // Deformata
}

function draw() {                  
}

Apri la pagina html

Possiamo trovare ulteriori funzionalità negli Esempi.

Elementi tipografici Indice

Per le caratteristiche proprie dei due diversi ambienti riguardo questo argomento dobbiamo separare le strategie di programmazione.

Processing

Possiamo utilizzare i fonts installati nel computer sul quale viene eseguito lo sketch (system fonts).

Possiamo anche includerli sotto forma di file (.ttf o .otf) in una cartella dedicata come abbiamo fatto per le immagini, in questo modo saremo sicuri che se condividiamo la cartella tutto ciò che è necessario per la corretta esecuzione dello sketch è presente.

Otteniamone una lista eseguendo il codice seguente (anche in static mode ovvero senza le funzioni setup e void).

In [ ]:
String[] fontList = PFont.list();
printArray(fontList);

Convertiamo i fonts dai formati TrueType (.ttf) o OpenType (.otf) in modo da poterli impiegare in uno sketch.

Li assegnamo a un particolare tipo di variabile: PFont.

Definiamo il punto del font che assume l'origine (x, y).

Definiamo il colore.

Assegnamo il font da utilizzare.

Eventualmente modifichiamo il size.

Visualizziamo il testo in una posizione (x, y).

In [ ]:
PFont trattarello;
PFont ptSerif;
PFont strambo;

String a = "Ciao";                       
String b = "Miao";
String c = "Uao!";

void setup() {                         
                    
            size(200,200);           
            background(235); 
            colorMode(RGB, 255);
            smooth();
             
            //trattarello = loadFont("AlexBrush-Regular",12); // Se salvati in locale
            trattarello = createFont("Trattatello",12);       // Converte i font
            ptSerif     = createFont("PTSerif-Regular",12);
            strambo     = createFont("Mshtakan",12);
                 
            textAlign(LEFT, CENTER); // Punto del font che specifica la posizione (x y)
                
            fill(150);
            textFont(trattarello);   // Assegna il font
            textSize(27);            // Modifica il size
            text(a, 10, 30);         // Visualizza

            fill(0);
            textFont(ptSerif);
            textSize(47);
            text(b, 50, 90);
                
            fill(123,165,254,150);
            textFont(strambo);    
            textSize(35);
            text(c, 100,170);
}

void draw() {                         
}
In [20]:
from IPython.display import HTML
HTML('<center><img src="media/textP.png" width="20%"></center>')
Out[20]:

p5.js

Possiamo utilizzare i fonts installati nel computer sul quale viene eseguito lo sketch (system fonts).

Teniamo però presente che a differenza di Processing ciò che disegnamo non sarà eseguito in locale ma su computers collegati attraverso il Web e non tutti potrebbero avere i font utilizzati.

I fonts più comuni che non dovrebbero dare problemi sono: “Arial”, “Courier” “Courier New”, “Georgia”, “Helvetica”, “Palatino”, “Times New Roman” “Trebuchet MS” e “Verdana”.

Per avere la sicurezza che siano disponibili per la corretta esecuzione dello sketch li dobbiamo includere sotto forma di file (.ttf o .otf) in una cartella dedicata come abbiamo fatto per le immagini.

In alternativa possiamo utilizzare Webfont come Googlefont o Open font library specificando il link di riferimento nel file index.html dello sketch.

In [ ]:
<link rel="stylesheet" media="screen" href="https://fontlibrary.org//face/tt2020-style-b" type="text/css"/>
In [ ]:
let tratterello; // System fonts
let alex;        // Font locale
let averia;      // Web font 

let a = "Ciao";                        
let b = "Miao";
let c = "Uao!";

function preload() {
                    alex  = loadFont('font/AlexBrush-Regular.ttf'); // Carica i fonts locali
}

function setup() {

      createCanvas(windowWidth, windowHeight);           
      background(235); 
      colorMode(RGB, 255);
      smooth();
                  
      textAlign(LEFT, CENTER); // Punto del font che specifica la posizione (x y)
       
      fill(150);
      textFont('trattarello');   // Assegna il font di sistema
      textSize(20);              // Modifica il size
      text(a, windowWidth/6, 30);           // Visualizza

      fill(0);
      textFont(alex);            // Font locale
      textSize(47);
      text(b, windowWidth/2.5, windowHeight/2.5);

      stroke(0);  
      strokeWeight(3);  
      fill(123,165,254,150);
      textFont('TT2020StyleBRegular'); // Web font    
      textSize(45);
      text(c, windowWidth/1.3,windowHeight/1.5);

}

function draw() {                  
}

Apri la pagina html

Possiamo trovare ulteriori funzionalità negli Esempi.

Secondo caso di studio Indice

Disegnamo una composizione visiva statica formata da quattro elementi.

  • un immagine posizionata al centro delleo schermo.
  • un numero a piacere di rettangoli con le seguenti caratteristiche:
    • grigi o colorati.
    • posizione distribuita linearmente nella window.
    • dimensioni variabili.
    • con o senza il bordo (di spessore variabile).
  • un numero a piacere di linee con le seguenti caratteristiche:
    • toni di grigio diversi.
    • orizzontali e verticali
    • larghezza fissa (1 pixel).
    • escono sempre dai bordi
  • un numero a piacere di punti con le seguenti caratteristiche:
    • neri.
    • dimensioni variabili.
    • posizionati in un area definita al centro dello schermo.

L'immagine è ri-composta randomicamente ogni volta che riceve un trigger.

Il trigger in questo caso è generato da un'inter-azione con la tastiera del computer (premere un tasto qualsiasi) oppure sul mouse (click) ma potrebbe essere generato da qualsiasi device esterno come una tastiera o controller MIDI, un sensore di Arduino, un interazione con smartphone, etc.

In [ ]:
PImage img;
int    pos;
float  scale = 0.8;  // Fattore di riscalaggio immagine

int nrect  = 10;     // Numero di elementi
int nline  = 4;      
int npoint = 25;

void setup() {
            img = loadImage("img/picasso.jpg"); 
            //fullScreen();
            size(600,600);
            noCursor();                    // Fa sparire il cursore
            background(255);
            smooth();
}; 
    
void draw() {                              // Per campionare gli istanti in cui
                                           // premiamo un tasto o clicchiamo col mouse
};

void disegno() {                           // Funzione che genera il disegno
                   
                println("Trigger");        // Monitor  
                background(255);           // Prima pulisce lo schermo...
                     
// ----------------------------- Immagine   
                     
                tint(random(150,255));
                imageMode(CENTER);
                image(img, width/2,height/2, img.width * scale, img.height * scale);        

// ----------------------------- Rettangoli
                     
                for (int i = 0; i < nrect; i = i+1) {
                                        if(random(-1, 1) > 0) {      
                                                               stroke(0);                     // Bordo
                                                               strokeWeight(random(1,3));
                                                               } else {                   
                                                                       noStroke();            // No bordo
                                                                       }
                                        if(random(-1, 1) > 0) {      
                                                               fill(random(255),random(255)); // Grigio
                                                               } else {                       
                                                               fill(random(255),random(255),  // Colori
                                                                    random(255),random(255));  
                                                                           }
                                        rect(random(width-100),random(height-100),
                                             random(100),random(100));
                                                       };
 
// ----------------------------- Linee
                     
                for (int i = 0; i < int(nline/2); i = i+1) {
                                         stroke(random(255));  // Grigi
                                         strokeWeight(1);
                                         line(random(width),   // Verticali
                                              0,
                                              random(width),
                                              height);
                                         line(0,              // Orizzontali
                                              random(height),
                                              width,
                                              random(height));
                                                            };
                     
// ----------------------------- Punti
                     
                for (int i = 0; i < npoint; i = i+1) {
                                         stroke(0);           // Nero
                                         strokeWeight(random(20));
                                         point(width /2 + random(-100,100),
                                               height/2 + random(-100,100));
                                                      }
}
                     
void keyPressed()   {                      // Tasto
                     disegno();            // Valuta la funzione...
                    }
void mousePressed() {                      // Click mouse
                     disegno();            // Valuta la funzione...
                    }
In [21]:
from IPython.display import HTML
HTML('<center><img src="media/caso2.png" width="60%"></center>')
Out[21]:

Nuovi argomenti:

Random Indice

Per la generazione di numeri pseudocasuali abbiamo a disposizione l'oggetto random() che restituisce sempre float.

In [ ]:
float a = random(100);      // Tra 0 e n
float b = random(-50,50);   // Tra min e max
int   c = int(random(100)); // Casting in int

println(a);
println(b);
println(c);

Trigger Indice

Possiamo definire un disegno all'interno di una funzione per poi farlo comparire o modificarlo alla ricezione di un trigger.

In [ ]:
void setup() {
              size(400,400);
              background(255);
};

void draw() {
};

void disegno() {
                background(255);             // Pulisce ad ogni click
                ellipse(mouseX,              // Recupera la posizione del mouse
                        mouseY,   
                        int(random(1,100)),
                        int(random(1,100))); 
}

void keyPressed() {
                   disegno();
}

void mousePressed() {
                   disegno();
}

for() e while() Indice

Come in molti linguaggi di programmazione possiamo definire iterazioni in due modi.

  • for
  • while
In [ ]:
size(400, 400);

for (int i = 0; i < 420; i = i+20) {                                    
                                    rect(i, i, random(20), 20);
                                    }

// -----------------------------

int i = 0;                                          // Fuori dal loop
while (i < 420) {          
                 int size = int(map(i,0,420,0,20)); // Riscala
                 ellipse(i, 
                         420 - i,                   // Inverso...
                         size, size);
                 i = i + 20;
                 }

if() e switch() Indice

Possiamo anche impiegare due comuni operatori condizionali.

  • if - tra due condizioni.
  • switch - tra diverse condizioni.
In [ ]:
void setup() {
              size(300,300);
              background(255);
              smooth();
};

void draw(){ 
 
};
 
void mousePressed(){                          // Quando clicchiamo
                    
                    if (mouseX > 150) {       // se il mouse è nella metà destra
                           fill(0);           // ...nero
                           } else {           // altrimenti
                                   fill(200); // Grigio
                                   };
                    
                    ellipse(width/2, height/2, 80, 80);          
};

void keyPressed() {
                   char lettera = key; // Recupera il char del tasto che abbiamo premuto

                   switch(lettera) {
                                    case 'r':      // Rosso
                                    case 'R': 
                                    fill(255,0,0); 
                                    break;
                                    case 'b':      // Blu
                                    case 'B': 
                                    fill(0,0,255); 
                                    break;
                                    }
                  ellipse(width/2, height/2, 80, 80);
}

Per quanto riguarda la sonorizzazione possiamo:

Mappare ognuno dei quattro elementi visivi con altrettante sequenze musicali che ne sottolineino le caratteristiche percettive.

Per poi scegliere tra due diverse impostazioni formali.

  • Rappresentazione uditiva - sfondo musicale indipendente dal trigger visivo che contiene e rappresenta tutti e quattro gli elementi sonori.
  • Mapping asincrono - pattern paralleli che rapprenentano il cambiamento morfologico degli stessi elementi ad un trigger anche per quanto riguarda il suono.

La prima la realizziamo con Processing e SuperCollider mentre la seconda con p5.js.

Processing e SuperCollider Indice

Scegliamo elementi minimi anche per quanto riguarda il suono.

  • Frammenti di voci femminili per la rappresentazione dell'immagine al centro.
  • Brevi pattern di suoni sinusoidali con inviluppo percussivo e panning randomico per la rappresentazione dei'punti al centro.
  • Brevi pattern di rumori brevissimi con panning randomico per la rappresentazione dele linee.
  • Impulsi di rumore filtrato a banda strettissima per la rappresentazione dei rettangoli (attacco dinamico per rappresentare il bordo più o meno spesso).

La patritura alterna questi elementi in modo continuamente cangiante ma non cambia al modificarsi del pattern visivo.

In [ ]:
s = Server.local;

s.waitForBoot{

s.meter;
s.scope;
s.plotTree;

// ----------------------------- Strumenti
    
~snd = Buffer.read(s, "voce.aif".resolveRelative);              // Carica il soundfile

SynthDef(\rev, {arg busIn=7,mix=0.5,room=0.5,damp=0.5;          // Riverbero
                var in, rev;
                    in  = In.ar(busIn, 2);
                    rev = FreeVerb2.ar(in[0],in[1],mix,room,damp);
                Out.ar(0,rev)
}).add;

SynthDef(\voce, {arg buf=0,pos=0,dur=0.2,amp=0,dir=1,pan=0,t_gate=0;
                 var sig,env;
                     sig = PlayBuf.ar(1, buf,
                                      BufRateScale.kr(buf) * dir, t_gate,                               
                                      BufSampleRate.kr(buf)* pos);
                     env = Env.linen(0.01,dur-0.02,0.01);
                     env = EnvGen.kr(env,t_gate,doneAction:2);
                     sig = Pan2.ar(sig*env*amp,pan);
                 Out.ar(7,sig)                               // Al riverbero
}).add;

SynthDef(\punto, {arg freq=400,amp=0,dur=1,atk=0.2,pan=0;
                  var sig, env;
                      sig = SinOsc.ar(freq);
                      env = Env.perc(atk, dur - atk);
                      env = EnvGen.ar(env, 1, doneAction:2);
                      sig = sig * env * amp;   
                      sig = Pan2.ar(sig, pan); 
                  Out.ar(7, sig)                                // Al riverbero
}).add;

SynthDef(\linea, {arg amp=0,dur=1,pan=0;
                  var sig, env;
                      sig = ClipNoise.ar;
                      env = Env.perc(dur*0.1,dur*0.9);
                      env = EnvGen.ar(env, 1, doneAction:2);
                      sig = sig * env * amp;
                      sig = Pan2.ar(sig, pan);
                  Out.ar(0, sig)
}).add;

SynthDef(\quad, {arg cutoff=400,qf=1,amp=0,dur=1,pan=0;
                 var sig,env,atk;
                     sig = WhiteNoise.ar;
                     sig = BPF.ar(sig, cutoff, qf);
                     atk = Decay.ar(Impulse.ar(0),              // Bordo
                                    dur * Rand(0.1,0.5)) * Rand(0.1,0.3); 
                     env = Env.perc(0.01, dur - 0.01);
                     env = EnvGen.ar(env, 1, doneAction:2);
                     sig = sig * env * amp + atk;  
                     sig = Pan2.ar(sig, pan); 
              Out.ar(0, sig)
}).add;

// ----------------------------- Partitura

{
Synth(\rev,[\mix,0.3,\room,0.8,\damp,0.5]); 

~voci = Routine.new({
                     inf.do({
                             rrand(1,4).do({
                                            Synth(\voce, [\buf,~snd, 
                                                          \pos, rand(~snd.duration),
                                                          \dur,rrand(0.1,0.3),
                                                          \amp,exprand(1.0,4),
                                                          \dir,[1,-1].choose,
                                                          \pan,rand2(1),
                                                          \t_gate,1]);
                                            rrand(0.01,0.2).wait;
                                            });
                             [0.5,2,6].choose.wait;
                             })
                     }).play;

~punti = Routine.new({
                      inf.do({
                              rrand(4,7).do({
                                             Synth(\punto, [\freq,rrand(600,2500),
                                                            \amp,[0.2,0.1,0.4,0.01].choose,
                                                            \dur,0.1,
                                                            \atk,0.01,
                                                            \pan,rand2(1.0)]);
                                             rand(0.1,3).wait;
                                             });
                              1.5.wait;
                              })
                      }).play;

~linee = Routine.new({
                      inf.do({
                              4.do({
                                    Synth(\linea,[\amp,rand(0.2),
                                                  \dur,rrand(0.01,0.05),
                                                  \pan,rand2(1.0)]);
                                    rrand(0.02,0.1).wait;
                                    });
                              2.5.wait;
                              })
                      }).play;

~rettangoli = Routine.new({
                           inf.do({
                                   9.do({
                                         Synth(\quad, [\amp,30,
                                                       \cutoff,rrand(1500,4000),
                                                       \qf, 0.001,
                                                       \dur,rrand(0.1,1)]);
                                         rrand(0.2,2.0).wait;
                                         });
                                   0.5.wait;
                                   })
                           }).play;
}.defer(3);                                 // Ritarda l'esecuzione di 3 secondi'
}

p5.js e Tone.js Indice

Prima di approcciare la seconda versione del secondo caso di studio con p5.js eploriamo le principali potenzialità di Tone.js per quanto riguarda la riproduzione e l'elaborazione di sound files.

Sound file player Indice

Per riprodurre sound files dobbiamo:

  • Caricare il sound file in un buffer all'interno di una funzione preload - esattamente come per le immagini il file può essere sia locale che on line. Per questo motivo meglio utilizzare formati compressi come mp3.
  • Definire un player che legga il buffer.
  • Triggerare la riproduzione attraverso il comando .start("now", offset, durata).

Possiamo:

  • definire inizio e fine in secondi.
  • definire un tempo di fadeIn e di fadeOut.
  • definire la velocità di lettura.
  • definire la direzione.
  • definire l'ampiezza in dB.
  • definire un loop.
  • Recuperare informazioni utili come:
    • durata del buffer in secondi.
    • lunghezza del buffer in campioni.
    • rata di campionamento.
    • numero di canali.

Esempio monofonico.

  • Prima creiamo il player nella funzione setup
  • poi lo facciamo partire con un trigger.
In [ ]:
let pth = "suoni/voce.mp3";                                          // Path al sound file
let buffer;
let player;

function preload() {   
                    buffer = new Tone.ToneAudioBuffer(pth);          // Buffer
};

function setup() {
                   createCanvas(windowWidth, windowHeight);
                   background(220);

                   textAlign(CENTER, CENTER);
                   textSize(24);           
                   text("Clicca qua!",windowWidth/2,windowHeight/2); 

                   player = new Tone.Player(buffer).toDestination(); // Player
                   player.autostart = false;
                   player.playbackRate = 1;
                   player.reverse = false;
                   player.volume.value = 0;     // in dB
                   player.fadeIn = 0.3;
                   player.fadeOut = 0.02;
                   player.loopStart = 0.5;
                   player.loopEnd = 0.8;
                   player.loop = true; 
}; 
    
function draw() {                              
};
                                         
let toneStart = 0;

function mousePressed(){   
                        if (toneStart = 0){
                                            Tone.start();
                                            toneStart = 1;
                                          }
               //                     delay  offset  dur   
                         player.start("+0",  0.6,    1.5);                // Trigger
                         //player.start();                                // Tutto il file...
                         console.log("dur: "     + buffer.duration);      // Info utili...
                         console.log("samples: " + buffer.length);
                         console.log("sr: "      + buffer.sampleRate);
                         console.log("canali: "  + buffer.numberOfChannels);
   }

function mouseReleased() {
                         //player.stop();
                         //player.loop = false;
}

Apri la pagina html

Esempio polifonico.

Ad ogni trigger creiamo un nuovo player e lo facciamo partire (eventualmente con un ritardo).

In [ ]:
let pth = "suoni/voce.mp3";                                          // Path al sound file
let buffer;
let player;

function preload() {   
                    buffer = new Tone.ToneAudioBuffer(pth);          // Buffer
};

function setup() {
                   createCanvas(windowWidth, windowHeight);
                   background(220);
                   
                   textAlign(CENTER, CENTER);
                   textSize(24);           
                   text("Clicca qua!",windowWidth/2,windowHeight/2); 
}; 
    
function draw() {                              
};
                                         
let toneStart = 0;

function mousePressed(){   
                        if (toneStart = 0){
                                            Tone.start();
                                            toneStart = 1;
                                          }
                         player = new Tone.Player(buffer).toDestination(); // Player
                         player.fadeIn  = 0.02;
                         player.fadeOut = 0.02;
                         player.start("+0",                                // Trigger
                                      random(0, buffer.duration-2.7),    
                                      random(0.05, 2.7));
                         }

Apri la pagina html

Sound files multipli

Se vogliamo invece triggerare più soundfiles diversi possiamo farlo con l'oggetto Players() che è semplicemente un contenitore di Player().

Possiamo accedere alle proprietà ed ai metodi di ogni singola istanza come specificato nel codice seguente.

Anche la gestione della polifonia (della singola istanza) segue la logica di Player().

In [ ]:
let pths = {voce: "suoni/voce.mp3",   
            bach: "suoni/bach.mp3",
            crot: "suoni/crotal.mp3"};     

let multiplayer;

function preload() {
                    multiplayer = new Tone.Players(pths).toDestination(); 
}; 

function setup() {
                  createCanvas(windowWidth, windowHeight);
                  background(220);

                  textAlign(CENTER, CENTER);
                  textSize(24);           
                  text("a = Voce, s = Bach, d = Crotali, spazio = Stop",windowWidth/2,windowHeight/2);
}; 
    
function draw() {                              
};

let toneStart = 0;

function keyTyped() {
                     if (toneStart = 0){
                                       Tone.start();
                                       toneStart = 1;   
                                       }
                     if (key === 'a') {
                           multiplayer.player("voce").reverse = true;
                           multiplayer.player("voce").start();
                        } else if (key === 's') {
                           multiplayer.player("bach").start("+0",0.5,0.5);
                        } else if (key === 'd') {
                           multiplayer.player("crot").playbackRate = 0.5;
                           multiplayer.player("crot").start();
                                                }
                     if (key === ' ') {                         // Barra spazio
                           multiplayer.stopAll()
                                      }
                     }

Apri la pagina html

Realizziamo un mapping asincrono impiegando le nuove conoscenze.

Generiamo dei pattern ritmici che richiamano il pattern ritmico visivo e che cambiano sfasati nel tempo rispetto a quest'ultimo.

Siccome le morfologie visive presenti sono quattro generiamo quattro istanze di un player di sounfiles (mapping).

In [ ]:
let img;
let pos;
let scale = 1.5;            // Fattore di riscalaggio immagine

let nrect  = 10;            // Numero di elementi
let nline  = 4;      
let npoint = 25;
                              
let buffer;
let rev;

function preload() {
                    img    = loadImage("img/picasso.jpg");               // Immagine
                    buffer = new Tone.ToneAudioBuffer("suoni/voce.mp3"); // Suono        
};

function setup() {
                  createCanvas(windowWidth, windowHeight);
                  noCursor();                       
                  background(220);
                  smooth();

                  rev       = new Tone.Reverb().toDestination();  // Un solo riverbero
                  rev.decay = 1.3;                                
                  rev.wet   = 0.4;                                
}; 
    
function draw() {};

function disegno() {                           // Funzione che genera il disegno
                   
                console.log("Trigger");        // Monitor  
                background(255);               // Prima pulisce lo schermo...
                     
// ----------------------------- Immagine   
                     
                tint(random(150,255));
                imageMode(CENTER);
                image(img, width/2,height/2, img.width * scale, img.height * scale);        

// ----------------------------- Rettangoli
                     
                for (let i = 0; i < nrect; i = i+1) {
                                        if(random(-1, 1) > 0) {      
                                                               stroke(0);                     // Bordo
                                                               strokeWeight(random(1,3));
                                                               } else {                   
                                                                       noStroke();            // No bordo
                                                                       }
                                        if(random(-1, 1) > 0) {      
                                                               fill(random(255),random(255)); // Grigio
                                                               } else {                       
                                                               fill(random(255),random(255),  // Colori
                                                                    random(255),random(255));  
                                                                           }
                                        rect(random(width-100),random(height-100),
                                             random(100),random(100));
                                                       };
 
// ----------------------------- Linee
                     
                for (let i = 0; i < int(nline/2); i = i+1) {
                                         stroke(random(255));  // Grigi
                                         strokeWeight(1);
                                         line(random(width),   // Verticali
                                              0,
                                              random(width),
                                              height);
                                         line(0,              // Orizzontali
                                              random(height),
                                              width,
                                              random(height));
                                                            };
                     
// ----------------------------- Punti
                     
                for (let i = 0; i < npoint; i = i+1) {
                                         stroke(0);           // Nero
                                         strokeWeight(random(20));
                                         point(width /2 + random(-200,200),
                                               height/2 + random(-200,200));
                                                      }
}
 
function suono() {                                       // Funzione che genera il suono
                  for (let i = 0; i < 3; i = i+1) {      // Quattro istanze 
                                                   
                                   let start = random(0, buffer.duration-1.3);
                                   let dur   = random(0.05, 1.3);
                                                   
                                   let panner;
                                   let player;
                                       panner = new Tone.Panner().connect(rev);          // 4 panners 
                                       panner.toDestination();
                                       panner.pan.rampTo(random(-1,1), dur);
                                                   
                                       player = new Tone.Player(buffer).connect(panner); // 4 players
                                       player.fadeIn    = random(0.2,5);
                                       player.fadeOut   = random(0.2,5);
                                       player.loopStart = start;
                                       player.loopEnd   = start + dur;
                                       player.loop = true;
                                       player.start("+0" + random(1, 5), start,random(5, 20));
                                                    }
}

let toneStart = 0;

function mousePressed() {                // Click mouse
                         if (toneStart = 0){
                                             Tone.start();
                                             toneStart = 1;   
                                             }
                         disegno();      // Valuta le funzioni
                         suono();
}

Apri la pagina html

Interazione in Processing e p5.js Indice

Nei codici precedenti abbiamo utilizzato alcune funzioni per triggerare eventi recuperando le inter-azioni dell'utente su devices esterni come mouse e tastiera del computer senza troppe spiegazioni.

In questo paragrafo le esploreremo in modo più approfondito.

Indipendentemente dal tipo di device e dal protocollo di comunicazione utilizzato possiamo generare tre tipi di data.

  • Triggers (evento singolo - bang).
  • Interruttori (evento doppio - on/off).
  • Valori continui (flusso di valori - fader).

Mouse Indice

Utilizzando il mouse abbiamo a disposizione tutte e tre le tipologie di interazione appena descritte.

Possiamo ottenere diverse informazioni (valori) in due modalità:

  • flussi - viene generato un nuovo valore ad ogni frame (il tempo dipende dalla frame rate). Come i segnali audio e di controllo.
  • eventi - viene generato un nuovo valore solo quando l'utente effettua una specifica operazione (click su uno dei bottoni). Come il sequencing.

In entrambe i casi i valori sono memorizzati in specifiche variabili di sistema.

Varibili di sistema Indice

Sia in Processing che in p5.js ci sono le seguenti variabili di sistema che restituiscono valori corrispondenti alle specifiche azioni.

  • mouseX - contiene le coordinate X del mouse all'interno della window.
  • mouseY - contiene le coordinate Y del mouse all'interno della window.

  • pmouseX - contiene le coordinate Y del mouse all'interno della window nel frame precedente.

  • pmouseY - contiene le coordinate Y del mouse all'interno della window nel frame precedente.

  • mousePressed (mouseIsPressed per p5.js) - restituisce true ogni volta che premiamo un bottone del mouse.

  • mouseButton - restituisce LEFT, RIGHT o CENTER a seconda del bottone che premiamo.

Se le utilizziamo all'interno di draw otteniamo valori continui ad una frame rate che possiamo definire con la funzione frameRate().

In [ ]:
void setup() {
              size(200,200);
              frameRate(10);  // Abbassa la framerate (60 fps di default)
}

void draw() {
             println(mouseX + " : " + mouseY);
}

Vediamo alcuni possibili utilizzi riguardo la posizione X Y.

Muovere un oggetto.

In [ ]:
void setup() {
              size(200,200);
              smooth();
              stroke(0,102);
}

void draw() {int x = mouseX; 
             int y = mouseY;
             
             background(255);      // Proviamo a commentare questa linea...
             ellipse(x,y,20,20);
}

Se vogliamo una latenza (easing - smoothing) tra l'azione del mouse e l'effetto.

In [ ]:
float x;
float y;
float px;
float py;
float easing = 0.05; // Fattore di smoothing

void setup() {
              size(200, 200);
              smooth();
              stroke(0, 102);
}

void draw() {
             x += (mouseX - x) * easing;
             y += (mouseY - y) * easing;
     
             float weight = dist(x, y, px, py);  // dist() calcola l'intervallo
             strokeWeight(weight);               // Mappato dullo spessore
    
             line(x, y, px, py);
             py = y;
             px = x;
}

Disegnare linee continue.

In [ ]:
void setup() {
              size(400,400);
              smooth();
              stroke(0,102);
}

void draw() {int x  = mouseX;
             int px = pmouseX;
             int y  = mouseY;
             int py = pmouseY;
             
             int largh = abs(px-x);  // + veloce + alto...
             //background(255);
             strokeWeight(largh);    // + largo...
             line(x,y,px,py);
}

Selezionare regioni della windows.

In [ ]:
void setup() {
              size(400,400);
              smooth();
              fill(0);
}

void draw() {
             background(255);
             if((mouseX <= 200) && (mouseY <= 200)) {
                                                     rect(0,0,200,200);
             }else if ((mouseX >= 200) && (mouseY <= 200)){
                                                     rect(200,0,200,200);
             }else if ((mouseX <= 200) && (mouseY >= 200)){
                                                     rect(0,200,200,200);
             }else if ((mouseX >= 200) && (mouseY >= 200)){
                                                     rect(200,200,200,200);   
             }
}

Modificare il range dei valori ottenuti (in pixels) per utilizzarli come controllo di altri parametri.

In [ ]:
void setup() {
              size(200, 200);
              strokeWeight(10);
              smooth();
}

void draw() {
             background(255);
             stroke(150);                               // linea grigia
             line(width/2, height/2, mouseX, mouseY);  
    
             stroke(0);                                 // linea nera 
             float mx = map(mouseX, 0, width, 60, 180); // minIn maxIn minOut maxOut  
             line(width/2, height/2, mx, mouseY);      
}

Vediamo alcuni possibili utilizzi riguardo il click.

Due stati (interruttore).

In [ ]:
void setup() {
              size(200, 200);
              smooth();
}

void draw() {
             background(204);
             if (mousePressed) {         // se è true == è sottointeso...
                                fill(0);         
             } else {           fill(255);       
                     }
             ellipse(width/2, height/2, 100, 100);
}

Tipo di tasto (LEFT, CENTER, RIGHT).

In [ ]:
void setup() {
              size(200, 200);
              smooth();
              noStroke();
}

void draw() {
             fill(126);              // Grigio
             if (mousePressed){
                if (mouseButton == LEFT) {      
                          fill(0);   // Nero         
                } else  {           
                          fill(255); // Bianco       
                } 
             };
             ellipse(width/2, height/2, 100, 100);
}

Eventi Indice

Possiamo impiegare le seguenti funzioni da scrivere dopo la funzione draw() che deve comunque essere sempre presente anche se vuota.

  • mousePressed - valuta la funzione quando clicchiamo un bottone del mouse.

  • mouseReleased - valuta la funzione quando rilasciamo un bottone del mouse.

  • mouseMoved - valuta la funzione quando muoviamo il mouse senza cliccare sul bottone.

  • mouseDragged - valuta la funzione quando muoviamo il mouse mentre teniamo premuto il bottone.

p5.js per le caratteristiche proprie al suo ambiente di fruizione ha più funzionalità rispetto a Processing per la cattura di dati provenienti dal mouse, possiamo trovare tutte le informazioni nel Reference.

Alcuni esempi basici.

Interruttore flip flop.

In [ ]:
int val = 0;

void setup() {
              size(200, 200);
              smooth();
}

void draw() {
             fill(val);
             ellipse(width/2, height/2, 50, 50);
}

void mousePressed() {                       // Interruttore flip flop
                     if (val == 0) {
                             val = 255;
                     } else {
                             val = 0;
                     }
}

Interruttore On / off.

In [ ]:
float val = 200;                 // Grigio

void setup() {
              size(200, 200);
              background(val);
              smooth();
}

void draw() {
             fill(val);
             ellipse(width/2, height/2, 50, 50);
}

void mousePressed() {                       
                     val = random(255); 
}
void mouseReleased() {
                      val = 255;
}

Cancellare lo schermo.

In [ ]:
void setup() {
              size(200, 200);
              background(255);
              smooth();
}

void draw() {
             ellipse(mouseX, mouseY, 10, 10);
}

void mousePressed() {
                     background(255);
}

Movimenti del mouse.

Le due funzioni si escludono vicendevolmente.

In [ ]:
int dragX, dragY, moveX, moveY;

void setup() {
              size(200, 200);
              smooth();
}

void draw() {
             background(204);
             fill(0);
             ellipse(dragX, dragY, 33, 33);   // Nero
             fill(153);
             ellipse(muoviX, muoviY, 33, 33); // Grigio
}

void mouseDragged() {
                     dragX = mouseX;
                     dragY = mouseY;
}

void mouseMoved() {
                   moveX = mouseX;
                   moveY = mouseY;
}

Tastiera Indice

Utilizzando la tastiera del computer abbiamo a disposizione le stesse funzionalità dei bottoni del mouse ovvero:

  • trigger (recuperiamo informazioni dall'azione solo quando premiamo il tasto).
  • interruttori (recuperiamo informazioni dall'azione sia quando premiamo che quando rilasciamo il tasto).

Anche in questo caso i valori sono memorizzati in variabili di sistema.

Varibili di sistema Indice

Sia in Processing che in p5.js ci sono le seguenti variabili di sistema che restituiscono valori corrispondenti alle specifiche azioni.

  • keyPressed - restituisce true ogni volta che premiamo un tasto.

  • key - restituisce il valore alfanumerico corrispondente all'ultimo tasto premuto o rilasciato per quanto riguarda i valori ASCII inclusi BACKSPACE, TAB, ENTER, RETURN, ESC e DELETE.

  • keyCode - restituisce i tasti speciali come BACKSPACE, TAB, ENTER, RETURN, ESC, and DELETE.

Se le utilizziamo all'interno di draw otteniamo valori continui ad una frame rate che possiamo definire con la funzione frameRate().

In [ ]:
float dim = 20;

void setup() {
              size(200, 200);
              rectMode(CENTER);
              textSize(60);
              textAlign(CENTER, CENTER);
              smooth();
}

void draw() {
             background(255);
             if(keyPressed == true) {                   // Qualsiasi tasto
                    dim = random(-50,50);
                    line(0+dim, 0, width+dim, height);
                    line(0+dim, height, width+dim, 0);
                    fill(150);
                    text(key, width/2,height/2);
             } else {
                     dim = random(10,50);
                     fill(255);
                     rect(width/2,height/2,50,50);
                     }          
}

Mapping.

In [ ]:
void setup() {
              size(200, 200);
              smooth();
}

void draw() {
             background(255);
//                                  AND              OR
             if((keyPressed == true) && ((key == 'a' || key == 'A'))) {    
                 rect(width/2,height/2,50,50);
             } else {
                ellipse(random(width)-25,random(height)-25,50,50);
                     }          
}

Tasti speciali.

In [ ]:
int x = 0;
int y = 0;

void setup() {
              size(200, 200);
              rectMode(CENTER);
              smooth();
}

void draw() {
             background(200);
             if (keyPressed && (key == CODED)) { // se è un tasto codificato
               if (keyCode == LEFT) {            // se è la freccia dx
                                     x--;
                                     } 
               else if (keyCode == RIGHT) {      // se è la freccia sx
                                           x++;
                                           }
               else if (keyCode == UP) {         // se è la freccia su
                                           y--;
                                           }
               else if (keyCode == DOWN) {       // se è la freccia giù
                                           y++;
                                           }
             }
             rect(x, y, random(1, 20),random(1,20));
}

Eventi Indice

Possiamo impiegare le seguenti funzioni da scrivere dopo la funzione draw() che deve comunque essere sempre presente anche se vuota.

  • keyPressed - valuta la funzione quando clicchiamo un bottone del mouse.

  • keyReleased - valuta la funzione quando rilasciamo un bottone del mouse.

p5.js per le caratteristiche proprie al suo ambiente di fruizione ha più funzionalità rispetto a Processing per la cattura di dati provenienti dal mouse, possiamo trovare tutte le informazioni nel Reference.

Un esempio basico.

In [ ]:
boolean scriviT = false;

void setup() {
              size(200, 200);
              noStroke();
}

void draw() {
             background(200);
             if (scriviT == true) { 
                 rect(40,40,120,40);
                 rect(78,80,44,90);
             }
}

void keyPressed() {
    if ((key == 't') || (key == 'T')) {
        scriviT = true;
    }
}

void keyReleased() {
    scriviT = false;
}

OSC Indice

Possiamo utilizzare il protocollo OSC per fare comunicare Processing e SuperCollider tra loro e con software e devices che lo accettano.

Per le sue caratteristiche (fruizione sia dell'audio che della grafica principalmente on line e all'interno di un broswer) non affronteremo l'integrazione di OSC in p5.js ma per chi fosse interessato esiste la libreria p5js-osc.

In Processing dobbiamo importare la libreria oscP5.

Le librerie aggiungono funzionalità specifiche al software.

Per importare librerie: --> Sketch --> Importa libreria... --> Manage Libraries.. compare la schermata seguente.

In [22]:
from IPython.display import HTML
HTML('<center><img src="media/oscP5.png" width="80%"></center>')
Out[22]:

Cercarla tra quelle in elenco.

Cliccare su Install.

Ecco uno script di SuperCollider che riassume le principali possibilità per inviare e ricevere diversi tipi di messaggi da e verso Processing.

In [ ]:
// ----------------------------- Inviare 

c = NetAddr("127.0.0.1",  12000);           // IP Porta

//        etichetta, valore
c.sendMsg("/x",      "trigger");            // String
c.sendMsg("/x",      rand(100));            // Intero
c.sendMsg("/x",      rand(1.0));            // Float

c.sendMsg("/x", rand(1.0), 234, "ciao");    // Più valori sincroni

c.sendMsg("/x", rand(1.0), rand(10));       // Più valori a-sincroni - Cambia il primo
c.sendMsg("/y", rand(1.0), rand(10));       // Cambia il secondo
c.sendMsg("/z", rand(1.0), rand(10));       // Cambia entramb

(
r = Routine({                                 // Sequencing
             inf.do({
                     c.sendMsg("/ellisse", rand(350), rand(350), rand(255));
                     rrand(0.1,3).wait;
                     })
             }).play
)
r.stop;
r.reset.play;

// ----------------------------- Ricevere 

//            nome,    funzione,            etichetta  porta
OSCdef.new(\criccio, {arg msg; msg.postln}, '/test',   recvPort:32000);

OSCdef(\criccio).free; // Cancella una specifica OSCdef
OSCdef.freeAll;        // Cancella tutte le OSCdef

Inviare e ricevere un solo valore (dobbiamo obbligatoriamente specificare il tipo di data).

In [ ]:
import oscP5.*;
import netP5.*;

OscP5      locale; // Dichiara variabile per indirizzo locale (questo patch)
NetAddress remoto; // Dichiara variabile per indirizzo remoto al quale inviare

void setup() {  
              locale = new OscP5(this, 12000);            // Assegnazione delle variabili (indirizzi)
              remoto = new NetAddress("127.0.0.1", 32000); 
}                                            

void draw() {      // Necessaria per monitorare gli eventi del mouse                         
}

// ----------------------------- Inviare 

void mousePressed() {
                     OscMessage ciao = new OscMessage("/test"); // Crea un nuovo messaggio osc con un'etichetta
                     ciao.add(random(100));                     // Aggiunge un valore
                     locale.send(ciao, remoto);                 // Lo invia all'indirizzo remoto
}

// ----------------------------- Ricevere 

void oscEvent(OscMessage messaggio) {
                                     if(messaggio.checkAddrPattern("/x") == true){      // se etichetta è /x
                                           int    val = messaggio.get(0).intValue();    // intero
                                           //float  val = messaggio.get(0).floatValue();  // float
                                           //String val = messaggio.get(0).stringValue(); // Stringa
                                           println(val);
                                           }
}

Inviare e ricevere una collezione di valori anche di tipi differenti.

In [ ]:
import oscP5.*;
import netP5.*;

OscP5      locale; 
NetAddress remoto;

float  x;      // Variabili che saranno riempite dai valori in ingresso
int    y;
String val;


void setup() {  
              locale = new OscP5(this, 12000);           
              remoto = new NetAddress("127.0.0.1", 32000); 
}  

void draw() {                 
}


void mousePressed() {  
                     OscMessage ciao = new OscMessage("/test");
                     ciao.add(123);                            // aggiunge un int
                     ciao.add(random(14));                     // aggiunge un float 
                     ciao.add("acciderboli!");                 // aggiunge una string
                     locale.send(ciao, remoto); 
}

void oscEvent(OscMessage messaggio) {
                                     if(messaggio.checkAddrPattern("/x")){  // ==true è sottinteso...
                                                    x   = messaggio.get(0).floatValue();  
                                                    y   = messaggio.get(1).intValue();  
                                                    val = messaggio.get(2).stringValue(); 
                                                    println(x, y, val);
                                                                          }  
}

Routing di messaggi in ingresso con etichette diverse.

In [ ]:
import oscP5.*;
import netP5.*;

OscP5      locale; 
NetAddress remoto;

float x;              
float y;             
float z;

String addr; // etichetta in ingresso
float val1;  // Primo valore della collezione in ingresso
int   val2;  // Secondo valore della collezione in ingresso

void setup() {  
              locale = new OscP5(this, 12000);           
              remoto = new NetAddress("127.0.0.1", 32000); 
}  

void draw() {                 
}

void oscEvent(OscMessage msg) {
                               addr = msg.addrPattern();       // Recupera etichetta
                               val1 = msg.get(0).floatValue(); // Recupera valori
                               val2 = msg.get(1).intValue();
  
                               if(addr.equals("/x"))      { x = val1; }
                               else if(addr.equals("/y")) { y = val2; }
                               else if(addr.equals("/z")) { x = val1; y = val2; }
                               println(x,y);
}

Esempio riassuntivo dove SuperCollider gestisce la 'partitura' di Processing.

In [ ]:
// ----------------------------- SuperCollider 

(
c = NetAddr("127.0.0.1",  12000);           

~xy = Routine.new({
                   inf.do({
                           c.sendMsg("/xy", rand(350),rand(350));
                           0.1.wait;
                           })
                   }).play;
    
~colore = Routine.new({
                       inf.do({
                               c.sendMsg("/colore", rand(250),rand(250),rand(250));
                               2.wait;
                               })
                       }).play;
    
~spessore = Routine.new({
                         inf.do({
                                 c.sendMsg("/spessore", rand(10));
                                 [0.1,2.3,1.7,4].choose.wait;
                                 })
                         }).play;
)
In [ ]:
// ----------------------------- Processing

import oscP5.*;
import netP5.*;

int x;
int y;
int r;
int g;
int b;
int bordo;

String addr;

OscP5 locale;

void setup() {
              size(400,400);
              noCursor();
              frameRate(30); 
              background(255);
              stroke(255);
              smooth();              
              locale = new OscP5(this, 12000); 
}

void draw() {
             background(0);
             fill(r,g,b);
             strokeWeight(spessore);
             stroke(255);
             ellipse(x,y, random(50),random(50));           
}; 

void oscEvent(OscMessage msg) {
                               addr = msg.addrPattern();
                               if(addr.equals("/xy"))      {x = msg.get(0).intValue();
                                                            y = msg.get(1).intValue();}
                          else if(addr.equals("/colore"))  {r = msg.get(0).intValue();
                                                            g = msg.get(1).intValue();
                                                            b = msg.get(2).intValue();}
                          else if(addr.equals("/spessore")){bordo = msg.get(0).intValue();}                                      
}

Nei casi di studio vedremo quali possono essere le migliori strategie di programmazione nella comunicazione tra due o più software o hardware.

MIDI Indice

Controllare uno o più parametri sia visivi che sonori interagendo con devices collegati al computer attraverso il protocollo MIDI.

Processing e SuperCollider

In questa configurazione abbiamo tre possibilità.

  • Recuperare i dati MIDI sia in SuperCollider che in Processing effettuando il routing dei parametri inviandoli su diversi canali Midi (ad esempio quelli visivi sul canale 1 e quelli sonori sul canale 2).

Processing e SuperCollider Indice

In questa configurazione abbiamo tre possibilità.

Routing canali MIDI - recuperiamo i dati MIDI sia in SuperCollider che in Processing inviandoli su diversi canali (ad esempio quelli visivi sul canale 1 e quelli sonori sul canale 2).

In [ ]:
// ----------------------------- SuperCollider 

MIDIClient.init;                  // Lista dei devices MIDI disponibili
MIDIIn.disconnectAll;             // Disconnette tutti quelli eventualmente connessi
MIDIIn.connectAll;                // Connette tutti 

(
MIDIdef.noteOn( \noteOn, {arg vel, nota, chan, uid;  
                          if(chan == 2, {postln(["note on", nota, vel])});
                }); 
MIDIdef.noteOff(\noteOff,{arg vel, nota, chan, uid;  
                          if(chan == 2, {postln(["note off",nota, vel])});
                }); 

MIDIdef.cc(     \control,{arg val, ccn, chan, uid; 
                          if(chan == 2, {postln([ccn, val])});
                }); 
)   

Per recuperare i dati MIDI in Processing possiamo utilizzare la libreria The midibus.

Installiamola come abbiamo fatto per oscP5.

Allo stato attuale (2024) per risolvere un bug con Processing 4 dobbiamo:

  • Scaricare themidibus.jar.
  • Andare nella cartella di Processing (in Mac --> ../nomeUtente/Documenti/Processing/libraries/themidibus/library
  • Sostituire il file presenta con quello scaricato.
In [ ]:
// ----------------------------- Processing

import themidibus.*; 

MidiBus    myBus;  // Dichiara variabile che contiene il bus Midi

void setup() {
  size(400, 400);
  background(0);

  MidiBus.list(); // Stampa elenco di device MIDI disponibili [ID 'nome'] (STDOUT). 
                  //   Parent In  Out
  myBus  = new MidiBus(this,   1, -1);        // Assegnazione delle variabili
}

void draw() {
}

void noteOn(int chan, int nota, int vel) {
                                          if(chan == 1)    // Se arriva dal canale 1
                                          {
                                          println("noteon", nota, vel);
                                          }
}

void noteOff(int chan, int nota, int vel) {
                                          if(chan == 1) 
                                          {
                                          println("noteoff", nota, vel);
                                          }
}

void controllerChange(int chan, int ccn, int val) {
                                                   if(chan == 1) 
                                                   {
                                                   println("cc", val, ccn);
                                                   }                    
}

N.B. Se testiamo gli script con un software come Max verifichiamo se il numero di canale parte da 0 o da 1...

Da SuperCollider a Processing - recuperiamo i dati MIDI in SuperCollider considerandolo una sorta di Hub MIDI e, se necessario inviamo a Processing quelli che controllano parametri visivi via OSC.

In [ ]:
// ----------------------------- SuperCollider 

MIDIClient.init;                  // Lista dei devices MIDI disponibili
MIDIIn.disconnectAll;             // Disconnette tutti quelli eventualmente connessi
MIDIIn.connectAll;                // Connette tutti 

c = NetAddr("127.0.0.1",  12000); // IP Porta (OSC)

(
MIDIdef.noteOn( \noteOn, {arg vel, nota, chan, uid;  
                          c.sendMsg("/on",  nota, vel, chan); // Invia
                }); 
MIDIdef.noteOff(\noteOff,{arg vel, nota, chan, uid;  
                          c.sendMsg("/off",  nota, vel, chan); // Invia
                }); 

MIDIdef.cc(     \control,{arg val, ccn, chan, uid; 
                          c.sendMsg("/cc",  val, ccn, chan);   // Invia
                }); 
)
In [ ]:
// ----------------------------- Processing

import oscP5.*;
import netP5.*;

int val;
int ccn;
int chan;
int nota;
int vel;

OscP5 locale; 

void setup() {  
              locale = new OscP5(this, 12000);            
}                                            

void draw() {                          
}

void oscEvent(OscMessage messaggio) {
                                     if(messaggio.checkAddrPattern("/cc")){      
                                           val  = messaggio.get(0).intValue();    
                                           ccn  = messaggio.get(1).intValue();    
                                           chan = messaggio.get(2).intValue();    
                                           println(val, ccn, chan);
                                     } else if (messaggio.checkAddrPattern("/on")){
                                           nota  = messaggio.get(0).intValue();    
                                           vel  = messaggio.get(1).intValue();    
                                           chan = messaggio.get(2).intValue();
                                           println(nota, vel, chan);
                                     }else if (messaggio.checkAddrPattern("/off")){
                                           nota  = messaggio.get(0).intValue();    
                                           vel  = messaggio.get(1).intValue();    
                                           chan = messaggio.get(2).intValue();
                                           println(nota, vel, chan);                                             
                                           }
}

Da Proessing a SuperCollider - recuperiamo i dati MIDI in Proessing considerandolo una sorta di Hub MIDI e, se necessario inviamo a SuperCollider quelli che controllano parametri visivi via OSC.

In [ ]:
// ----------------------------- SuperCollider 

OSCdef.new(\notOn, {arg msg; msg.postln}, '/on',   recvPort:32000);
OSCdef.new(\notOn, {arg msg; msg.postln}, '/off',  recvPort:32000);
OSCdef.new(\notOn, {arg msg; msg.postln}, '/ccn',  recvPort:32000);

OSCdef.freeAll;       
In [ ]:
// ----------------------------- Processing

import themidibus.*; 
import oscP5.*;
import netP5.*;


MidiBus    myBus;  // Dichiara variabile che contiene il bus Midi
OscP5      locale; // Dichiara variabile per indirizzo locale (questo patch)
NetAddress remoto; // Dichiara variabile per indirizzo remoto al quale inviare (OSC)

void setup() {
  size(400, 400);
  background(0);

  MidiBus.list(); // Stampa elenco di device MIDI disponibili [ID 'nome'] (STDOUT). 
                  //   Parent In  Out
  myBus  = new MidiBus(this,   2, -1);        // Assegnazione delle variabili
  locale = new OscP5(this, 12000);           
  remoto = new NetAddress("127.0.0.1", 32000); 
}

void draw() {
}

void noteOn(int chan, int nota, int vel) {
                                          println(nota, vel, chan);
                                          OscMessage on = new OscMessage("/on"); // Invia OSC
                                          on.add(nota);   
                                          on.add(vel);     
                                          on.add(chan);     
                                          locale.send(on, remoto);                 
}

void noteOff(int chan, int nota, int vel) {
                                           println(nota, vel, chan);
                                           OscMessage off = new OscMessage("/off");
                                           off.add(nota);   
                                           off.add(vel);     
                                           off.add(chan);     
                                           locale.send(off, remoto);     
}

void controllerChange(int chan, int ccn, int val) {
                                                   println(val, ccn, chan);
                                                   OscMessage cc = new OscMessage("/ccn"); 
                                                   cc.add(val);   
                                                   cc.add(ccn);     
                                                   cc.add(chan);     
                                                   locale.send(cc, remoto);                      
}

p5.js Indice

Se vogliamo controllare audio e grafica tramite devices midi in p5.js possiamo utilizzare la libreria webmidi.

Come abbiamo fatto per Tone.js dobbiamo includere nel flie index.html il link alla libreria.

In [ ]:
<script src="https://cdn.jsdelivr.net/npm/webmidi@latest/dist/iife/webmidi.iife.js"></script>

Nel codice seguente un esempio dei controlli di base.

In [ ]:
function setup() {
                  createCanvas(windowWidth, windowHeight);
                  noStroke();
                  smooth();

  // Attiva WebMidi.js e valuta la funzione onWebMidiEnabled() quando è pronto.
                  WebMidi.enable()
                                  .then(onWebMidiEnabled)
                                  .catch(err => alert(err));
}

function draw() {
}

function onWebMidiEnabled() {
                             let lar = 50;                             // Variabile larghezza dei cerchi
                             if (WebMidi.inputs.length < 1) {          // Verifica device collegati 
                                                  alert("Nessun MIDI Input!");
                                                  return;
                             } else{                                   // Stampa lista devices disponibili
                                    WebMidi.inputs.forEach((device, index) => console.log(index, device.name))
                                    };

  //      Device id              tipo    canale
  WebMidi.inputs[0].addListener("noteon", "all", e => {
                                                       console.log("noteon", e.note.number, e.rawValue);
                                                       cerchio(e.note.number/100*height, lar); // disegna cerchio
  });  
    
  WebMidi.inputs[0].addListener("noteoff", "all", e => {
                                                        console.log("noteoff", e.note.number, e.rawValue);
  });
    
  WebMidi.inputs[0].addListener("controlchange", "all", e => {
                                                        console.log("cc", e.controller.number, e.rawValue);
                                                        lar = e.rawValue;                    // modifica larghezza
  }); 
}

function cerchio(y, sp) {
                         fill(random(255), random(255), random(255));
                         circle(random(windowWidth),y, sp)
} 

Terzo caso di studio Indice

Nel terzo caso di studio focalizziamo la nostra attenzione sulle seguenti problematiche:

  • invio di uno o più trigger tra diverse applicazioni via OSC.
  • struttura formale a pannelli.
  • possibili relazioni dirette (mapping) tra pattern visivi e pattern musicali.

Processing e SuperCollider Indice

Nello sketch di Processing sono applicati alcuni argomenti esposti nei paragrafi precedenti.

L'unico elemento di novità informatica consiste nel come triggerare un'azione in draw (lo sfondo bianco) attraverso un messaggio OSC.

Caratterizziamo lo sketch con un mapping formale a pannelli (o scene) ovvero ad ogni trigger cambia immediatamente sia la parte visiva che quella sonora.

Quando premiamo un tasto qualsiasi (trigger) viene generata la grafica e inviato un trigger da Processing a Supercollider via OSC (una specie di noteon).

Quando l'evento sonoro termina viene inviato automaticamente un secondo trigger da SuperCollider a Processing che pulisce lo schermo.

In [ ]:
// ----------------------------- Processing

import oscP5.*;
import netP5.*;

OscP5      locale; 
NetAddress remoto;

PFont  miofont;
String testo = "Wendy? Darling? Light, of my life";  
int stop;

void setup() {
             // fullScreen();
              size(800,800);
              noCursor();
              frameRate(10); 
              background(255);
              smooth();
    
              miofont = createFont("Zepfino", 12); 
              textAlign(LEFT, CENTER);
              
              locale = new OscP5(this, 12000);
              remoto = new NetAddress("127.0.0.1", 32000); 
}

void draw() {
             if(stop==1){background(255);}                           // Sfondo bianco
}; 

void oscEvent(OscMessage msg) {                                      // Riceve trigger OSC
                               if(msg.checkAddrPattern("/ciccio") == true){                
                                                    stop = msg.get(0).intValue(); 
                                                    println("stop!");
                                }
}

void keyPressed() {  
                   stop = 0;
                   background(255);  

                   for(int i = 0; i < 14; i++){
                                               fill(random(255));
                                               textFont(miofont);      
                                               textSize(random(2,27));  
                                               text(testo, random(width), random(height)); 
                                               }
    
                   OscMessage cliCk = new OscMessage("/schiacciato");  // Invia trigger OSC
                   cliCk.add(1);                         
                   locale.send(cliCk, remoto);                 
}
In [ ]:
// ----------------------------- SuperCollider

(
s.waitForBoot{
SynthDef(\noise, {arg amp = 0, dur = 1, atk = 0.2;
                  var sig, filt, del, env, pan;
                      sig = WhiteNoise.ar;
                      filt = BPF.ar(sig, Rand(200,18000), Rand(0.01,0.2));
                      env = Env.perc(atk, dur - atk);
                      env = EnvGen.ar(env, 1, doneAction:2);
                      sig = filt * env * amp;
                      sig = Pan2.ar(sig, LFNoise2.kr(3));
                  Out.ar(9, sig)
}).add;

SynthDef(\del, {arg inBus=9;
                var sig, delT;
                    sig  = In.ar(inBus);
                    delT = LFNoise1.kr(8).range(0.1,1.5);
                    sig  = DelayL.ar(sig, 2, delT, 1, sig);
                    sig  = Pan2.ar(sig, LFNoise1.ar(5));
                Out.ar(0, sig)
}).add;

SynthDef(\bop, {var sig, den, amp;
                    den = LFNoise1.ar(0.2).range(1,10);
                    amp = LFNoise1.ar(0.3).unipolar;
                    sig = Dust.ar(den) * amp;
                    sig = Pan2.ar(sig,LFNoise1.ar(5));
                Out.ar(0, sig)
}).add;

SynthDef(\blim, {arg freq=#[200,300,400], t_gate=0;
                 var sig,env;
                     sig = SinOsc.ar(freq)*0.2;
                     env = Env.perc(0.01,3);
                     env = EnvGen.ar(env,t_gate, doneAction:2);
                     sig = Pan2.ar(sig*env, 0);
                 Out.ar(0, sig)
}).add;

{3.do({Synth(\del)});             // 3 Delays
 Synth(\bop)}.defer(0.5);         // Sfondo dinamico


c = NetAddr("127.0.0.1",  12000); // IP Porta

~dur = [0.01, 0.02, 0.1,  0.07,   0.3];
~dt  = [0.21, 0.35, 0.14, 0.049, 0.07];
~atk = [0.01,  0.1, 0.02, 0.08,   0.1];
~amp = [0.6,   0.2, 0.7,  0.5,    0.8];

OSCdef(\ricevi, {arg msg;         // Riceve il Trigger da Processing
                     msg.postln;
                     ~dt   = ~dt.scramble;
                     ~dur  = ~dur.scramble;
                     ~freq = rrand(1,5).collect({rrand(84,96)});
                     ~freq.postln;
                 if(msg.at(1) == 1, {r.reset.play})
                 },
                 '/schiacciato',
                 recvPort:32000);

r = Routine.new({
                 ~dt.do({arg i, id;
                         i.postln;
                         Synth(\noise, [\amp, ~amp[id],
                                        \dur, ~dur[id],
                                        \atk, ~atk[id]
                                        ]);
                         i.wait;
                         });
                 1.wait;

                 c.sendMsg("/ciccio", 1);   // Invia il trigger a Processing

                 Synth(\blim,[\freq, ~freq.midicps, \t_gate,1])
                 });
}
)

Cues (scene) Indice

Nello sketch precedente le modifiche tra un pannello e il successivo sono randomiche e realizzate sui parametri di un insieme finito di elementi.

Possiamo invece realizzare una partitura a cues dove richiamare attraverso trigger scene differenti sia audio che video.

In questo caso ogni singola scena sarà programmata all'interno di una funzione richiamata mediante l'iterazione degli indici effettuata con un contatore.

Lo sketch seguente illustra la gestione di parametri sia statici (che cambiano solo al trigger) che dinamici cha cambiano ad ogni frame.

Non è sviluppata la parte audio in quanto possiamo mappare il trigger (messaggi OSC) degli eventi sonori direttamente all'interno delle singole funzioni.

In [ ]:
int count = -1;  // Inizializzazione del contatore

void setup() {
             // fullScreen();
              size(400,400);
              noCursor();
              background(255);
              smooth();
}

void draw() {  
             if(count == 1) { 
                             sec_2();   // Ad ogni frame
                             }
} 

void sec_1() {
              background(255);
              fill(random(255),random(255),random(255)); 
              noStroke();
              ellipse(width/2,height/2,100,100);           
}

void sec_2() {
              background(255);
              rect(random(width-50),random(height-50),50,50);
}

void sec_3() {
              background(255);
              stroke(0);
              for(int i = 0; i < 15; i++){
                                          strokeWeight(random(1,5));
                                          line(random(width), 0, random(width), height);
                                          }
}

void keyPressed() {
                   count = (count + 1) % 3; // Aggiorna il contatore
                   switch(count) {
                                  case 0: sec_1();
                                  break;
                                  case 1: fill(200);       // Una sola volta
                                          strokeWeight(3);
                                          stroke(126);
                                  break;
                                  case 2: sec_3();
                                  break;
                                  }
                   println(count);             
}

p5.js e Tone.js Indice

In questa versione il trigger iniziale modifica dinamicamente due pattern che si svolgono entrambi nel tempo in quanto la parte grafica è un loop video.

A differenza dalla realizzazione precedente i pannelli che costituiscono l'ossatura delle due partiture (qualla visiva e quella sonora) alcune volte sono sincroni mentre altre sono a-sincroni grazie ad uno switch randomico.

Viene introdotta attraverso il fermo immagine ed i silenzi anche una riflessione estetica riguardo dinamismo e staticità.

In [ ]:
let img;
let pos;
let scale = 1.5;            // Fattore di riscalaggio immagine

let nrect  = 10;            // Numero di elementi
let nline  = 4;      
let npoint = 25;
                              
let buffer;
let rev;

function preload() {
                    img    = loadImage("img/picasso.jpg");               // Immagine
                    buffer = new Tone.ToneAudioBuffer("suoni/voce.mp3"); // Suono        
};

function setup() {
                  createCanvas(windowWidth, windowHeight);
                  noCursor();                       
                  background(220);
                  smooth();

                  rev       = new Tone.Reverb().toDestination();  // Un solo riverbero
                  rev.decay = 1.3;                                
                  rev.wet   = 0.4;                                
}; 
    
function draw() {};

function disegno() {                           // Funzione che genera il disegno
                   
                console.log("Trigger");        // Monitor  
                background(255);               // Prima pulisce lo schermo...
                     
// ----------------------------- Immagine   
                     
                tint(random(150,255));
                imageMode(CENTER);
                image(img, width/2,height/2, img.width * scale, img.height * scale);        

// ----------------------------- Rettangoli
                     
                for (let i = 0; i < nrect; i = i+1) {
                                        if(random(-1, 1) > 0) {      
                                                               stroke(0);                     // Bordo
                                                               strokeWeight(random(1,3));
                                                               } else {                   
                                                                       noStroke();            // No bordo
                                                                       }
                                        if(random(-1, 1) > 0) {      
                                                               fill(random(255),random(255)); // Grigio
                                                               } else {                       
                                                               fill(random(255),random(255),  // Colori
                                                                    random(255),random(255));  
                                                                           }
                                        rect(random(width-100),random(height-100),
                                             random(100),random(100));
                                                       };
 
// ----------------------------- Linee
                     
                for (let i = 0; i < int(nline/2); i = i+1) {
                                         stroke(random(255));  // Grigi
                                         strokeWeight(1);
                                         line(random(width),   // Verticali
                                              0,
                                              random(width),
                                              height);
                                         line(0,              // Orizzontali
                                              random(height),
                                              width,
                                              random(height));
                                                            };
                     
// ----------------------------- Punti
                     
                for (let i = 0; i < npoint; i = i+1) {
                                         stroke(0);           // Nero
                                         strokeWeight(random(20));
                                         point(width /2 + random(-200,200),
                                               height/2 + random(-200,200));
                                                      }
}
 
function suono() {                                       // Funzione che genera il suono
                  for (let i = 0; i < 3; i = i+1) {      // Quattro istanze 
                                                   
                                   let start = random(0, buffer.duration-1.3);
                                   let dur   = random(0.05, 1.3);
                                                   
                                   let panner;
                                   let player;
                                       panner = new Tone.Panner().connect(rev);          // 4 panners 
                                       panner.toDestination();
                                       panner.pan.rampTo(random(-1,1), dur);
                                                   
                                       player = new Tone.Player(buffer).connect(panner); // 4 players
                                       player.fadeIn    = random(0.2,5);
                                       player.fadeOut   = random(0.2,5);
                                       player.loopStart = start;
                                       player.loopEnd   = start + dur;
                                       player.loop = true;
                                       player.start("+0" + random(1, 5), start,random(5, 20));
                                                    }
}

let toneStart = 0;

function mousePressed() {                // Click mouse
                         if (toneStart = 0){
                                             Tone.start();
                                             toneStart = 1;   
                                             }
                         disegno();      // Valuta le funzioni
                         suono();

}

Apri la pagina html

Nuovi argomenti:

Riproduzione video Indice

createVideo(), p5.MediaElement e image().

Nell'ultimo sketch di p5.js è illustrato come possiamo inserire in un canvas la riproduzione di un viseo e quali sono i principali controlli alltraverso l'oggetto p5.MediaElement.

Prestiamo attenzione solamente nell'utilizzare più copie del video in formati diversi per allargare il più possibile la compatibilità con i principali broswer.

Sequencing polifonico Indice

Nello sketch generiamo una nuova istanza di panner, di gain e di player ad ogni evento di Part.

Questo ci permette una gestione polifonica dei suoni.

Abbiamo invece una sola istanza di riverbero creata nella funzione setup() nella quale confluiscono tutti i segnali.

Indice

Esercitazione n° 3 - Realizzare un progetto audiovisivo focalizzato su strutture a pannelli dinamici triggerate dall’utente investigando le possibili correlazioni tra pannelli sincroni, asincroni e/o indipendenti.

Animazioni e GUI Indice

Quarto caso di studio Indice

Esercitazione n° 4 - Realizzare un progetto audiovisivo focalizzato su tecniche di mapping diretto e/o misto tra suono ed elementi visivi.
Indice