Summary: La produzione di fenomeni oscillatori è un aspetto frequente di molte realizzazioni interattive. Vengono descritte tecniche di riproduzione di oscillazioni di varia forma e frequenza. Inoltre, si presenta il buffer circolare come struttura di supporto alla manipolazione continua del tempo.
buf[] il buffer (è un array)
contenente la forma d'onda da generare, l'oscillatore
tabellare funziona per lettura ciclica della tabella, con un
passo di avanzamento
f.
int B = 256; // lunghezza di tabella (risoluzione)
int R = 20; // rate
float f = 0.8; // frequenza in cicli al secondo
float[] buf = new float[B];
int HEIGHT = 128; // altezza della finestra
float amp; // ampiezza della oscillazione
int readPoint; // posizione orizzontale della cresta
float decayFactor = 0.95; // fattore di decadimento dell'onda
void setup() {
size(B,HEIGHT);
stroke(255); strokeWeight(2);
for (int i = 0; i < buf.length; i++) buf[i] = sin(2*PI*(float)i/buf.length);
}
void draw() {
background(0);
if ((mouseX >= 0) && (mouseX < B) && (mousePressed)) {
amp = 2*(HEIGHT/2 - mouseY)/float(HEIGHT);
readPoint = mouseX;
}
for (int i = 0; i < buf.length; i++) {
point(i, HEIGHT - HEIGHT/2 - HEIGHT/2*amp*buf[(i - readPoint + B/4 + B)%B]);
}
amp = decayFactor*amp;
readPoint = (readPoint + round(f*B/R))%B; // incremento dell'oscillatore
}
int B = 256; // lunghezza di tabella (risoluzione)
int R = 20; // rate
float f = 1.1; // frequenza in cicli al secondo
float[] buf = new float[B];
int HEIGHT = 128; // altezza della finestra
float amp; // ampiezza della oscillazione
float readPoint; // posizione orizzontale della cresta
float decayFactor = 0.92; // fattore di decadimento dell'onda
float tempo;
void setup() {
size(B,HEIGHT);
stroke(255); strokeWeight(2);
for (int i = 0; i < buf.length; i++) buf[i] = sin(2*PI*(float)i/buf.length);
}
void mousePressed() {
tempo = millis();
}
void mouseReleased() {
tempo = millis() - tempo; println(tempo);
f = 500/tempo;
}
void draw() {
background(0);
if ((mouseX >= 0) && (mouseX < B) && (mousePressed)) {
amp = 2*(HEIGHT/2 - mouseY)/float(HEIGHT);
readPoint = mouseX;
}
for (int i = 0; i < buf.length; i++) {
point(i, HEIGHT - HEIGHT/2 - HEIGHT/2*amp*buf[(i - round(readPoint) + B/4 + B)%B]);
}
amp = decayFactor*amp;
readPoint = (readPoint + (f*B/R))%B; // incremento dell'oscillatore
}
int B = 256; // lunghezza di tabella (risoluzione)
int R = 20; // rate
float f = 1.1; // frequenza in cicli al secondo
float[] buf = new float[B];
int HEIGHT = 128; // altezza della finestra
float amp; // ampiezza della oscillazione
float readPoint; // posizione orizzontale della cresta
float decayFactor = 0.92; // fattore di decadimento dell'onda
float tempo;
float ampli=0;
void setup() {
size(B,HEIGHT);
stroke(255); strokeWeight(2);
for (int i = 0; i < buf.length; i++) buf[i] = sin(2*PI*(float)i/buf.length);
}
void mousePressed() {
tempo = millis();
}
void mouseReleased() {
tempo = millis() - tempo; println(tempo);
f = 500/tempo;
}
void draw() {
background(0);
if ((mouseX >= 0) && (mouseX < B) && (mousePressed)) {
amp = 2*(HEIGHT/2 - mouseY)/float(HEIGHT);
readPoint = mouseX;
}
for (int i = 0; i < buf.length; i++) {
if (i < readPoint) ampli += 1/(float)readPoint;
else ampli -= 1/(float)(B - readPoint);
point(i, HEIGHT - HEIGHT/2 - HEIGHT/2*amp*ampli*buf[(i - round(readPoint) + B/4 + B)%B]);
}
ampli = 0;
amp = decayFactor*amp;
readPoint = (readPoint + (f*B/R))%B; // incremento dell'oscillatore
}
round()) con
l'interpolazione lineare.
import processing.video.*;
Capture myCapture;
int captureRate = 16;
float f=1;
int B = 64;
int R = 20;
PImage[] sequenza = new PImage[B];
int i;
float j=0;
float inc = f*B/R;
boolean via = false;
void setup() {
for (int i=0; i<B; i++) sequenza[i]=loadImage("vetro.jpg"); // immagine dummy, per inizializzare
size(200, 200);
String s = "IIDC FireWire Video";
myCapture = new Capture(this, width, height, s, 30);
myCapture.frameRate(captureRate);
frameRate(R);
}
void mousePressed(){
f = float(mouseX)/width; inc = f*B/R;
}
void draw() {
if (!via) {
if(myCapture.available()) {
// Reads the new frame
myCapture.read();
sequenza[i].copy(myCapture,0,0,myCapture.width,myCapture.height,0,0,sequenza[i].width,sequenza[i].height);
i = (i+1)%B;
println(i);
if (i==0) via = true;
}
}
if (via) { image(sequenza[floor(j)], 0, 0,200,200);
j = (j+inc); if (j>=B) j=j-B;
}
}
![]() Figura 1 |
onda1 e
onda2 di 64 elementi ciascuno. Il segnale a dente
di sega viene generato ad una frequenza di 139 cicli al
secondo. Il suo range viene moltiplicato per 64 in maniera da
occupare tutto il campo degli indici (da 0 a 63) dell'array
onda1. Ogni volta che si preme il bottone di
bang, 64 campioni del segnale prodotto dalla
lettura della tabella onda1 vengono scritti nella
tabella onda2. Il segnale prodotto dalla lettura
ciclica della tabella onad1 è udibile attraverso
conversione da digitale ad analogico (dac~). Si
noti che in Pd tutti gli operatori che hanno il simbolo di
tilde lavorano in maniera sincrona ad audio rate, e quindi la
lettura dalla tabella onda1 fornisce campioni al
sample rate di funzionamento (per default 44100 Hz). Si noti
anche la scalinatura del segnale scritto nella tabella
onda2, dovuto alla lettura non interpolata dei
campioni di onda1.
tabread~ con
tabread4~, in questo modo facendo una lettura
interpolata di ordine 3. In altri termini, l'interpolatore
fa passare un polinomio di terzo grado (una cubica) per tre
punti contigui della tabella.
IN, chiamata
puntatore di ingresso, che contiene l'indice dell'elemento
dell'array nel quale si va a scrivere. Una variabile
OUT, chiamata puntatore di uscita, contiene
l'indice dell'elemento dell'array dal quale si va a
leggere. Si tratta di incrementare i puntatori di accesso in
maniera circolare, mantenendo la loro distanza relativa pari
al numero di passi temporali di cui si vuole che sia fatto il
ritardo. Ad ogni passo temporale (o istante di campionamento)
il segnale di ingresso è scritto nella locazione puntata da
IN e letto dalla locazione puntata da
OUT, D passi indietro. Quindi, i due
puntatori sono aggiornati con le operazioni
IN = (IN + 1) % B; OUT = (OUT + 1) % B;dove
B è la lunghezza del buffer, scelta in
maniera da essere maggiore del più grande valore di ritardo
D che si intende usare. Il ritardo D
può variare dinamicamente, ad esempio oscillando tra un minimo
ed un massimo, ma è chiaro che se esso assume un valore non
intero bisognerà adottare una strategia di interpolazione per
la lettura del segnale ritardato. Ad esempio, si può ancora
una volta fare interpolazione lineare tra locazioni adiacenti,
oppure scegliere l'intero immediatamente inferiore a
D.
PFont font;
int B = 1000;
int INTERLINEA = 30;
int xpos=0, ypos=INTERLINEA;
char[] buffer = new char[B];
int OUT = 0;
int IN = 0;
int D = 0;
char carattere;
char inchar = '\0';
void setup() {
size(800,800);
font = loadFont("Courier-48.vlw");
textFont(font, 32);
frameRate(30);
}
void keyReleased() {
inchar = key;
}
void draw() {
if (inchar=='\n') {
D+=10;
IN = (OUT + D)%B;
xpos = 0;
ypos += INTERLINEA;
inchar='\0';
}
else {
buffer[IN] = inchar;
carattere = buffer[OUT];
text(carattere, xpos, ypos);
xpos += textWidth(carattere);
IN = (IN + 1)%B; OUT = (OUT + 1)%B;
}
inchar = '\0';
}
Comments, questions, feedback, criticisms?