Summary: Introduzione alla rappresentazione di suoni e immagini (colori, coordinate, ecc.) in Processing.
Sistema di coordinate![]() Figura 1: Sistema di coordinate 3D in uso in Processing |
size() definisce la dimensione della
finestra di display e stabilisce quale motore di
rendering sarà utilizzato per disegnare su tale
finestra. Il motore di default è JAVA2D, cioè la libreria di
grafica bidimensionale di Java. Se si desidera operare sulle
tre dimensioni è necessario impostare il rendering P3D
(Processing 3D), particolarmente efficiente per la grafica
su web, oppure OPENGL, che consente di delegare alla scheda
grafica molte operazioni tipiche della grafica 3D.
PImage. La funzione
loadImage("myImage") preleva il file (gif o jpg)
myImage, contenente la codifica di una immagine,
e restituisce in uscita il contenuto in pixel della immagine
stessa, il quale può essere assegnato ad una variabile di tipo
PImage. Il file myImage deve essere
caricato nella cartella data della directory
avente lo stesso nome dello sketch Processing al quale si sta
lavorando. New, Processing apre una cartella di nome
sketch_???????, all'interno di una directory
Processing, corrispondente al nome
assegnato dal sistema al file al quale si comincia a
lavorare. Tale cartella è accessibile tramite i comandi del
menu Sketch/Add File di Processing.PImage rende accessibili, mediante i campi
width e height, rispettivamente la
larghezza e la altezza della immagine caricata. Il contenuto è
invece accessibile mediante il campo pixels[].
size(400,300);
PImage b;
b = loadImage("gondoliers.jpg");
println("width=" + b.width + " height=" + b.height);
image(b, 0, 0, 400, 300); // position (0,0); width=400; height=300;
image(b, 20, 10, 100, 80); // position (20,10); width=100; height=80;
color è un tipo primitivo usato per
specificare il colore. E' realizzato mediante un numero di 32
bit, in cui il primo byte specifica il valore alpha, e gli
altri byte successivi specificano una terna nel modello RGB o
in quello HSB. La scelta di un modello piuttosto che
dell'altro è fatta mediante la funzione
colorMode(). I colori rappresentabili mediante
tre byte sono in numero di
colorMode(). Il
modello RGB è di tipo additivo. Gimp color chooser![]() Figura 2: Color chooser del software Gimp |
color, con il metodo alpha(). La
manipolazione dell'alpha channel si svolge mediante il metodo
blend() della classe PImage.
|
size(400,300);
PImage b = loadImage("gondoliers.jpg");
PImage a = loadImage("gondoliers.jpg");
float ramp = 0;
for (int j = 0; j < b.height; j++)
for (int i = 0; i < b.width; i++) {
b.set(i, j, b.get(i,j) +
color(0,0,0, 255 - (int)((1-ramp)*255)) );
ramp = ramp + 1/(float)(b.width * b.height);
}
a.blend(b, 0, 0, b.width, b.height,
80, 10, 450, 250, BLEND);
image(a, 0, 0, 400, 300);
|
color mediante la funzione
color(), essendo il modello precedentemente stato
definito mediante colorMode(). Le funzioni
red(), green(), blue(),
hue(), saturation(), e
brightness() consentono di passare da un modello
all'altro.
colorMode(RGB); color c1 = color(102, 30,29); colorMode(HSB); color c2 = color(hue(c1), saturation(c1), brightness(c1)); colorMode(RGB); color c3 = color(red(c2), green(c2), blue(c2)); // le variabili c1, c2, c3 contengono codifica dello stesso colore
tint(). Ad esempio, per dare un
tono blu all'immagine incastonata nel Esempio 1, è sufficiente far precedere il
secondo comando image() da tint(0, 153, 204,
126) .
translate() consente di spostare
oggetti sulla finestra di immagine. Ammette due o tre
argomenti, che sono rispettivamente gli spostamenti lungo le
direzioni rotate() consente di ruotare oggetti sulla
finestra di immagine. Ciò avviene mediante una moltiplicazione
(a sinistra) delle coordinate di ciascun pixel dell'oggetto
non ruotato per una matrice di rotazione. La rotazione avviene
sempre rispetto all'angolo superiore sinistro (coordinate
rotate(PI/3) prima del secondo comando
image() in Esempio 1. In tre
dimensioni, si possono usare le rotazioni elementari rispetto agli
assi coordinati rotateX(), rotateY(), e
rotateZ().
scale() consente di espandere o
contrarre un oggetto mediante moltiplicazione per una costante
delle coordinate dei punti che lo compongono. Se invocata con
due o tre parametri, gli scalamenti possono anche essere
diversi lungo i diversi assi cartesiani.
PFont ed i metodi loadFont()
(per caricare un font ed assegnarlo ad un oggetto di classe
PFont) e textFont() (per attivare un
font con una specifica dimensione). Per poter essere caricato,
il font deve essere stato precedentemente inserito nella
directory data dello sketch corrente. Il tool
Create Font, accessibile dal menu
Tools di Processing, consente di creare le bitmap
dei caratteri che si andranno ad usare e ne colloca il file
relativo nella directory data. In seguito a
queste operazioni preliminari, il font può essere usato per
scrivere del testo, usando la funzione
text(). Questa consente di collocare una stringa
di caratteri nello spazio bidimensionale o tridimensionale,
eventualmente inserendola all'interno di una box
rettangolare. L'allineamento dei caratteri all'interno della
box è governato dalla textAlign(). Nella
configurazione di default, il testo scritto può essere
sottoposto a trasformazioni spaziali come ogni altro oggetto.
Per quanto riguarda il colore dei caratteri, esso può essere
impresso con la funzione fill(), come per ogni
altro oggetto grafico.
|
PFont fonte;
/*The font have been previously created
in the data folder*/
fonte = loadFont("HoeflerText-Black-48.vlw");
textFont(fonte, 12);
fill(10, 20, 250, 80);
textAlign(RIGHT);
text("pippo pippo non lo sa", 10, 14, 35, 70);
textFont(fonte, 24);
fill(200, 0, 0, 100);
text("ppnls", 25, 5, 50, 90);
|
textWidth() calcola l'estensione orizzontale di
una carattere o di una stringa. Essa può essere usata, insieme
alle coordinate esatte di posizionamento passate alla
text(), per controllare il kerning ed
il tracking
tra caratteri. La textSize() consente di
ridefinire la dimensione dei caratteri. La
textLeading() ridefinisce la distanza in pixel
tra linee contigue di testo. Questa distanza si misura tra le
baseline su cui si adagiano le stringhe
di caratteri. Lettere quali "p" o "q" si estendono al di sotto
della baseline per una quantità di pixel calcolabile con
textDescent(). Invece, la
textAscent() restituisce l'estensione massima al
di sopra della baseline (tipicamente, l'altezza della lettera
"d").Metriche di tipo![]() Figura 3: Metriche di tipo |
.wav. Nelle versioni più recenti, Processing
affida la gestione del suono a librerie
esterne. Le più usate librerie audio sono Ess, Minim e
Sonia.
Così come per le immagini, anche per poter elaborare o riprodurre
suoni i relativi file audio devono essere archiviati nella
directory data dello sketch corrente.
La libreria Sonia è la
più complessa da utilizzare, ma consente una elaborazione molto
dettagliata ed efficiente. Tale libreria contiene, per esempio,
delle funzioni che permettono di riprodurre campioni audio
(sample playback), di eseguire un'analisi di
Fourier in tempo reale (ovvero un'analisi del suono che viene
captato da un microfono collegato al computer: realtime
FFT), nonchè di salvare dei file .wav
sul disco. Per poter utilizzare la libreria Sonia è
necessario scaricare il file .zip dal sito Sonia. Bisogna poi
decomprimere il tutto e copiare la directory
Sonia_?_? dentro la directory
Processing/libraries. Infine, dopo aver riavviato
Processing, si deve aggiungere un comando import,
selezionandolo direttamente dal menu Sketch / Import Library
/ Sonia_?_?. Sonia.start(this) è
indispensabile per attivare il motore audio di Sonia. La linea
Sample mySample1 dichiara una variabile atta a
contenere campioni audio. A tale variabile possono essere
applicati vari metodi, tra i quali il metodo play
per riprodurre il campione. Nel draw() viene
definito l'aspetto grafico della applet. Infine, tramite la
funzione mouseReleased() viene rilevato quando il
mouse viene rilasciato dopo essere stato premuto e in quale
area del cerchiello. A quel punto una successione di
condizioni if stabilisce quale strumento/timbro
debba essere riprodotto a seconda del punto di
interazione. Inoltre, all'interno della funzione
mouseReleased() viene invocata un'altra funzione:
filtra(). Questa funzione, che viene implementata alla fine
del listato di questa applet, esegue un filtraggio sul suono
che viene riprodotto. In particolare si tratta di un
filtraggio passa-basso, vale a dire che lascia passare le basse
frequenze ma non le alte. A seconda se il mouse viene
rilasciato più o meno vicino al centro, l'effetto del
filtraggio cambia. Più in particolare il filtraggio sarà più
drastico (le alte frequenze sono maggiormente attenuate, con
l'effetto di un maggior incupimento del suono) se il mouse
viene rilasciato in prossimità del centro e il suono risulterà
più povero (scolorito).
La realizzazione del Sound Chooser mediante la libreria Minim è proposta nel problema 4.
|
import pitaru.sonia_v2_9.*;
Sample mySample1, mySample2, mySample3, mySample4;
Sample mySample1F, mySample2F, mySample3F, mySample4F;
float[] data1, data2, data3, data4;
float[] data1F, data2F, data3F, data4F;
int sr = 11025; // sampling rate
void setup()
{
size(200, 200);
colorMode(HSB, 360, height, height);
Sonia.start(this);
mySample1 = new Sample("flauto.aif");
mySample2 = new Sample("oboe.wav");
mySample3 = new Sample("tromba.wav");
mySample4 = new Sample("violino.wav");
mySample1F = new Sample("flauto.aif");
// ... OMISSIS ...
data1 = new float[mySample1.getNumFrames()];
// creates new arrays the length of the sample
// for the original sound
// ... OMISSIS ...
data1F = new float[mySample1.getNumFrames()];
// creates new arrays the length of the sample
// for the filtered sound
// ... OMISSIS ...
mySample1.read(data1);
// ... OMISSIS ...
}
void draw()
{
// ... OMISSIS ...
}
void mouseReleased()
{
float ro;
float roLin;
float wc;
// FLAUTO
if ((mouseX > 95) && (mouseX < 105)&& (mouseY > 50)&& (mouseY < 90)) {
roLin = (mouseY-49.99)/41;
ro = pow(roLin,.33);
wc = 298*(TWO_PI/sr);
filtra(data1F, data1, wc, ro);
mySample1F.write(data1F);
mySample1F.play();
}
// ... OMISSIS ...
}
//filtra = new function
void filtra(float[] DATAF, float[] DATA, float WC, float RO) {
float G;
float RO2;
RO2 = pow(RO, 2);
G = (1-RO)*sqrt(1-2*RO*cos(2*WC)+RO2)*4; // (*4) is for having it louder
for(int i = 3; i < DATA.length; i++){
DATAF[i] = G*DATA[i]+2*RO*cos(WC)*DATAF[i-1]-RO2*DATAF[i-2];
//filtraggio ricorsivo
}
}
// safely stop the Sonia engine upon shutdown.
public void stop(){
Sonia.stop();
super.stop();
}
|
PImage è
accessibile mediante il suo campo pixels[]. I
pixel della immagine, corrispondenti ad una lettura riga
per riga, sono contenuti in questo array di dimensione
width*height. Modificare il codice di Esempio 2 in modo da utilizzare il campo
pixels[] invece del metodo
get(), senza cambiare il funzionamento del
programma.
b.set() con la
b.set(i,j,b.pixels[j*b.width+i]+ color(0,0,0, 255 - (int)((1-ramp)*255)) );
line con dei rect
e colorando le barrette ottenute con una variazione di
brillantezza, crescente dal centro verso l'esterno.
import ddf.minim.*;
import ddf.minim.effects.*;
AudioPlayer mySample1, mySample2, mySample3, mySample4;
LowPassSP lpf1, lpf2, lpf3, lpf4;
float cutoff1, cutoff2, cutoff3, cutoff4;
void setup()
{
size(200, 200);
colorMode(HSB, 360, height, height);
Minim.start(this);
mySample1 = Minim.loadFile("flauto.aif");
mySample2 = Minim.loadFile("oboe.wav");
mySample3 = Minim.loadFile("tromba.wav");
mySample4 = Minim.loadFile("violino.wav");
lpf1 = new LowPassSP(4000, mySample1.sampleRate());
lpf2 = new LowPassSP(4000, mySample2.sampleRate());
lpf3 = new LowPassSP(4000, mySample3.sampleRate());
lpf4 = new LowPassSP(4000, mySample4.sampleRate());
mySample1.addEffect(lpf1);
mySample2.addEffect(lpf2);
mySample3.addEffect(lpf3);
mySample4.addEffect(lpf4);
}
void draw()
{
stroke(255);
strokeWeight(1);
fill(0, 88, 88);
ellipseMode(CORNER);
ellipse(50,50,100,100);
beginShape(LINES);
vertex(50, 100);
vertex(90, 100);
vertex(110, 100);
vertex(150, 100);
vertex(100, 50);
vertex(100, 90);
vertex(100, 110);
vertex(100, 150);
endShape();
}
void mouseReleased()
{
// FLAUTO
if ((mouseX > 95) && (mouseX < 105)&& (mouseY > 50)&& (mouseY < 90)) {
cutoff1 = map(mouseY, 50, 90, 1000, 30);
lpf1.setFreq(cutoff1);
println(mouseY + " + " +cutoff1);
mySample1.rewind();
mySample1.play();
}
// OBOE
if ((mouseX > 110) && (mouseX < 149)&& (mouseY > 95)&& (mouseY < 105)) {
cutoff2 = map(mouseX, 110, 149, 30, 1000);
lpf2.setFreq(cutoff2);
println(mouseX + " + " +cutoff2);
mySample2.rewind();
mySample2.play();
}
// TROMBA
if ((mouseX > 95) && (mouseX < 105)&& (mouseY > 110)&& (mouseY < 149)) {
cutoff3 = map(mouseY, 110, 149, 30, 1000);
lpf3.setFreq(cutoff3);
println(mouseY + " + " +cutoff3);
mySample3.rewind();
mySample3.play();
}
// VIOLINO
if ((mouseX > 50) && (mouseX < 90)&& (mouseY > 95)&& (mouseY < 105)) {
cutoff4 = map(mouseX, 50, 90, 1000, 30);
lpf4.setFreq(cutoff4);
println(mouseX + " + " +cutoff4);
mySample4.rewind();
mySample4.play();
}
}
// safely stop the Minim engine upon shutdown.
public void stop(){
mySample1.close();
mySample2.close();
mySample3.close();
mySample4.close();
Minim.stop();
super.stop();
}
Comments, questions, feedback, criticisms?