#attacktime
Explore tagged Tumblr posts
tinaturbin-blog1-blog · 8 years ago
Photo
Tumblr media
Organic Turkey Burgers with a little @stevespaleogoods Peach BBQ and Sriracha sauce mixed 1/2and1/2 and marinated lotus root from my farmers market topped with a nut cheese I made with using his homemade fermented cashews, spices with a little @primalkitchenfoods Ranch dressing. What an absolutely delicious meal for an after hike "attack" 😅. #postworkoutmeal #hungry #tuesdaydinner #nomnomnom #nongmo #suppertime #cleaneating #foodbloggers #foodie #thefeedfeed @thefeedfeed #chef #cook #foodiegram #letseat #healthyfood #healthyeating #healthylifestyle #active #attacktime #turkey #lotusroot #farmersmarket #bbq #stevespaleogoods #primalkitchen #marksisson
0 notes
reixperiment · 5 years ago
Text
Tumblr media Tumblr media Tumblr media
About to record a video on processing using my lamp to create visuals well as control an audio visual patch that through Arduino, generates a melodic rythm and affects the video and lamp visuals being recorded.
This is the code I used to enables the webcam to work, Arduino to input light data, and generate sound as well as visuals.
Initially, I attempted to learn how to utilise motion to generate the sound however, it turned out to be more difficult than expected.
-------------------------------
import processing.video.*;
import processing.sound.*;
//int circleX = 50;
Arduino arduino; // create a variable arduino of the Arduino data type
SawOsc saw;
Env env;
import processing.serial.*; // reference the serial library
import cc.arduino.*; // reference the arduino library
int time = millis();
//SinOsc sine;
Capture video;
// Times and levels for the ASR envelope
float attackTime = 0.001;
float sustainTime = 0.004;
float sustainLevel = 0.3;
float releaseTime = 0.2;
// This is an octave in MIDI notes.
int[] midiSequence = { 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72 };
// Play a new note every 200ms
int duration = 200;
// This variable stores the point in time when the next note should be triggered
int trigger = millis();
// An index to count up the notes
int note = 0;
void setup() {
size(600, 400);
// Make a new instance of a PImage by loading an image file
video = new Capture(this,640,480,30);
video.start();
arduino = new Arduino(this, Arduino.list()[0], 57600);
saw = new SawOsc(this);
//saw.play();
// Create the envelope
env = new Env(this);
}
void captureEvent(Capture video){
}
void draw() {
float (arduino.analogRead(0));
float m = map(arduino.analogRead(0), 0, 500, 0, 1000);
//fill(255);
//ellipse(500, 500, m, m);
//background(0);
println(arduino.analogRead(0));
float amplitude = map(mouseY, 0, height, 1.0, 0.0);
saw.amp(amplitude);
// Map mouseX from 20Hz to 1000Hz for frequency
float frequency = map(arduino.analogRead(0), 0, 500, 20.0, 1000.0);
saw.freq(frequency);
// Map mouseX from -1.0 to 1.0 for panning the audio to the left or right
float panning = map(mouseX, 0, width, -1.0, 1.0);
saw.pan(panning);
image(video,0,0,640,480);
video.read();
int passedMillis = millis() - time; // calculates passed milliseconds
if(passedMillis >= m){
time = millis();
tint(0,0,m); // if more than 215 milliseconds passed set fill color to red
} else {
tint(255,0,0,100);
fill(time % 255);
}
// If the determined trigger moment in time matches up with the computer clock and
// the sequence of notes hasn't been finished yet, the next note gets played.
if ((millis() > trigger) && (note<midiSequence.length)) {
// midiToFreq transforms the MIDI value into a frequency in Hz which we use to
// control the triangle oscillator with an amplitute of 0.5
saw.play(midiToFreq(midiSequence[note]), 0.5);
// The envelope gets triggered with the oscillator as input and the times and
// levels we defined earlier
env.play(saw, attackTime, sustainTime, sustainLevel, releaseTime);
// Create the new trigger according to predefined duration
trigger = millis() +arduino.analogRead(0);
// Advance by one note in the midiSequence;
note++;
// Loop the sequence, notice the jitter
if (note == 12) {
note = 0;
}
}
}
// This helper function calculates the respective frequency of a MIDI note
float midiToFreq(int note) {
return (pow(2, ((note-69)/12.0))) * 440;
}
0 notes
mbuoninfante · 7 years ago
Text
MonoSynth in ChucK - pt.7
Parte 7
SCOPO
Utilizzo di lookup table come generatore d’onda quadra limitata in banda al posto di un oscillatore sinusoidale.
Utilizzo della struttura di controllo “for”.
PREREQUISITI
Per poter utilizzare questo programma serve avere un controller MIDI connesso al computer, o una virtual MIDI keyboard. Questo va connesso (o aperto nel caso di una tastiera virtuale), prima di far partire il programma.
Conoscenza base del protocollo MIDI (https://it.wikipedia.org/wiki/Musical_Instrument_Digital_Interface - http://www.nyu.edu/classes/bello/FMT_files/8_MIDIcomms.pdf - https://www.nyu.edu/classes/bello/FMT_files/9_MIDI_code.pdf)
Conoscenza base della sintesi additiva e della serie di Fourier
LOOKUP TABLE E PHASOR
Nei precedenti tutorial sono stati affrontate principalmente problematiche riguardanti il controllo delle varie componenti base di un sintetizzatore (implementazione di una tastiera MIDI, controllo in frequenza di un oscillatore, portamento, etc.). Questo tutorial invece, sara’ incentrato sull’implementazione di un oscillatore ad onda quadra limitato in banda e si iniziera’ ad affrontare la parte riguardante il timbro dello strumento.
Generalmente I sintetizzatori hardware hanno una sezione chiamata VCO (voltage controlled oscillators - se analogici) o DCO (digital controlled oscillators - se digitali), la quale include vari oscillatori con forme d’onda differenti tra loro.
Le forme d’onde classiche molto spesso implementate nei synth hardware e software sono: sinusoidale, quadra, triangolare e dente di sega.
Esistono vari modi per implementare questi generatori in ChucK, il piu’ semplice prevede l’utilizzo di dedicati UGen come SqrOsc, TriOsc e SawOsc (onda quadra, triangolare e dente di sega), I quali hanno le stesse funzionalita’ dell’UGen SinOsc con cui si e’ lavorato fin’ora.
Purtroppo pero’, non e’ sempre possibile utilizzare questi UGen, in quanto e’ molto facile imbattersi in una problematica comune a tutti I sistemi digitali: l’aliasing.
Gli UGen presenti in ChucK, generano una versione perfetta delle forme d’onda classiche sopra elencate, la quale ha un alto contenuto armonico, che non varia in base alla frequenza (ie una forma d’onda a 50 Hz ha lo stesso numero di armoniche di una a 1000 Hz), e cio’ risulta essere un problema quando si lavora con strumenti musicali digitali.
Nel caso di SqrOsc si puo’ pensare ad un suono (onda quadra) con un numero infinito di armoniche. Cio’ significa che si avra’ un tot di armoniche con freqenza superiore alla frequenza di Nyquist (frequenza di campionamento / 2), al quale corrisponde il limite in banda di un sistema digitale.
Le frequenze che superano questo limite, non scompaiono dallo spettro ma vengono ribaltate.
Prendendo in considerazione un sistema con frequenza di campionamento pari a 48 kHz (48000 Hz), possono essere generate tutte le frequenze inferiori a 24 kHz (24000 Hz). Quando questa soglia viene suprata, le frequenze vengono ribaltate nel seguente modo:
y = x-SR
dove:
x >= SR/2 e rappresenta la frequenza in Hz
SR = sample rate (frequenza di campionamento)
Quindi nel caso di un sistema a 48 kHz, se si prova a generare una frequenza pari a 25000 Hz, si otterra’ come risultato una frequenza pari a -23000 Hz per la formula di cui sopra.
Il segno meno “-” suggerisce che la fase e’ stata invertita.
E’ chiaro a questo punto che questo comportamento e’ indesiderato nel caso di strumenti musicali digitali (ma non solo), in quanto il contenuto armonico e di consegueza il timbro dello strumento puo’ essere facilmente alterato. Allo stesso tempo pero’, non si vuole rinunciare all’utilizzo di forme d’onda diverse da quella sinusoidale.
Esistono varie tecniche utilizzate per aggirare questo problema. Quella presa in considerazione in questo esempio prevede l’utilizzo di lookup table, degli array contenenti forme d’onda (wavetable) utilizzati al posto dei generatori d’onda (oscillatori). L’idea e’ quella di avere piu’ lookup table contenenti la stessa forma d’onda ma con diverso numero di armonici (ie prima lookup table ha un’onda quadra con 15 armonici, la seconda lookup table una forma d’onda quadra con 8 armonici, etc.), per poi poter scegliere quella da utilizzare in base alla frequenza fondamentale della nota che si intende riprodurre. Piu’ la nota e’ alta minore sara’ il contenuto armonico della forma d’onda.
Essendo le lookup table delle tabelle (file audio), necessitano di un ulteriore UGen utilizzato per leggere I valori da esse contenuti. Generalmente si utilizza un ramp generator, che in ChucK (ed in molti altri linguaggi di programmazione audio) e’ l’UGen Phasor.
Questa tecnica risulta essere uno dei compromessi piu’ utilizzati, e come tutti I compromessi ha ovviamente pro e contro (uno dei contro e’ che non e’ facile modificare il contenuto delle lookup table in tempo reale, mentre puo’ essere relativamente facile modificare un algoritmo che genera la forma d’onda in tempo reale. Uno dei pro e’ che dal punto di vista computazionale e’ meglio avere una lookup table che singoli oscillatori quando si ha che fare con forme d’onda complesse).
In ChucK esistono degli UGen che fungono da lookup table, e sono quelli appartenenti alla famiglia dei GenX. Nell’esempio illustrato a breve, verra’ utilizzato Gen10, il quale attraverso il metodo .coeff(), genera automaticamente una forma d’onda in base all’argomento ricevuto.
Nel caso specifico, l’argomento deve essere un’array di numeri in virgola mobile, il quale rappresenta l’ampiezza delle varie sinusoidi (parziali) che compongono la forma d’onda desiderata. Le celle dell’array corrispondono alle parziali della forma d’onda (cella nr 1 = fondamentale, cella 2 = prima armonica, cella 3 = seconda armonica, etc.), per un massimo di 100 argomenti e quindi 100 parziali.
L’immagine seguente illustra il risultato, in termini di contenuto armonico, ottenuto utilizzando l’array [1., 0., 0.75, 0., 0.5, 0., 1., 0., 0., 0.5]
Tumblr media
Si puo’ intuire dunque che Gen10 non fa altro che effettuare una sintesi additiva in base all’argomento ricevuto, e ne salva il risultato (forma d’onda) in un array.
Come accennato in precedenza il modo utilizzato per accedere ai dati contenuti in questo array, per leggere l’array, prevede l’utilizzo di una rampa collegata all’ingresso di Gen10.
L’UGen Phasor genera una rampa in un range da 0 ad 1, e puo’ essere collegato all’ingresso di un UGen Gen10 per far si che questo generi un segnale in uscita corrispondente alla forma d’onda salvata al suo interno.
Tumblr media
I valori generati da Phasor (qualcosa di molto simile a 0, 0.001, 0.002, 0.003, 0.004, …, 0.999, 1. - per poi ripartire da 0) vengono utilizzati come indice dell’array contenuto in Gen10.
Avendo come riferimento l’immagine precedente, l’asse y di phasor corrisponde all’asse x della lookup table.
Quindi quando l’output di phasor e’ 0.5 (meta’ rampa) si stara’ leggendo il valore al centro della lookup table, quando l’output di phasor e’ 0.75, si stara’ leggendo il valore salvato nel punto x = ¾ * (lunghezza dell’array), e cosi’ via.
In questo scenario la frequenza (Hz) di Phasor influenza la velocita’ con cui la lookup table viene letta e di conseguenza la frequenza del segnale in uscita.
ANALISI DEL PROGRAMMA
Il programma come tutti quelli precedenti inizia con la dichiarazione degli UGen e delle variabili globali:
Gen10 square_1; // note da 0 a 60 Gen10 square_2; // note da 61 a 80 Gen10 square_3; // note da 81 a 105 Gen10 square_4; // note da 106 a 127 Phasor phasor;  // utilizzato per controllare gli UGen Gen10 Step step => Envelope line => phasor; Envelope env => Gain master => dac; step.next(1); phasor.sync(0); float coefficients[0];  // coefficienti degli UGen Gen10 initSquareWave(); // connessioni MIDI MidiIn mIn; MidiMsg msgIn; mIn.open("Launchkey MK2 25 MIDI 1"); // variabili 144 => int NOTE_ON; 128 => int NOTE_OFF; 176 => int CTRL_CHANGE; 0 => int MIDI_CHANNEL;  // canale MIDI - range da 0 a 15 float fr; float amp; int midiNote; int lastNote; 0.007874016 => float dividedBy127; 0.05 => float freqInterpolation;  // in secondi [21,22,23,24,25,26,27,28] @=> int knobs[];  // control change corrispondenti agli 8 potenziometri presenti sul controller 0.1 => amp; master.gain(amp); // impostare l'ampiezza del Gain 'master' 10::ms => dur attackTime;  // tempo di attacco 500::ms => dur releaseTime; // tempo di rilascio line.time(freqInterpolation); // imposta il tempo di interpolatione della frequenza dell'oscillatore
Come prima cosa vengono creati 4 Gen10 (lookup table) ed un Phasor. Quest’ultimo viene controllato in frequenza da una catena Step => Envelope. Questo, come visto negli esempi precedenti permette di avere l’effetto portamento (interpolazione del segnale che controlla la frequenza).
Viene poi chiamata la funzione initSquareWave(), che e’ dichiarata in seguito nel programma dopo ciclo while. Questa permette di inizializzare le lookup table, creando le 4 forme d’onda quadra con differenti contenuti armonici.
Il codice successivo, fino all’inizio del ciclo while non presenta niente di diverso da quello affrontato nel tutorial precedente.
A seguire si ha:
while(true) {  // avanza ad ogni messaggio MIDI ricevuto  mIn => now;  while(mIn.recv(msgIn))  {    if(msgIn.data1 == (NOTE_ON + MIDI_CHANNEL) && msgIn.data3 != 0)    {      // resetta il volume prima di generare una nuova nota per evitare 'glitch'      env.duration(3::ms);      env.keyOff(1);      3::ms => now;      msgIn.data2 => midiNote;      setSquareWave(midiNote);    // seleziona la wavetable in base alla nota ricevuta      Std.mtof(midiNote) => fr;      line.target(fr);      msgIn.data2 => lastNote;      msgIn.data3 * dividedBy127 => float amplitude;      env.gain(amplitude);      env.duration(attackTime);      env.keyOn(1);    }    else if( (msgIn.data1 == (NOTE_ON + MIDI_CHANNEL) && msgIn.data3 == 0 && msgIn.data2 == lastNote)              || (msgIn.data1 == (NOTE_OFF + MIDI_CHANNEL) && msgIn.data2 == lastNote) )    {      env.duration(releaseTime);      env.keyOff(1);    }    else if(msgIn.data1 == (CTRL_CHANGE+MIDI_CHANNEL) && msgIn.data2 == knobs[0])    {      setIntep(msgIn.data3) => freqInterpolation;      line.time(freqInterpolation);    }  } }
Il main loop inizia con qualcosa di nuovo. Ogni qual volta un messaggio MIDI di tipo note on viene ricevuto il seguente codice, prima di suonare la nota ricevuta, resetta il volume dell’oscillatore (di Gen10 in questo caso) ed aspetta 3 millisecondi prima di procedere:
env.duration(3::ms);      env.keyOff(1);      3::ms => now;
Questo piccolo pezzo di codice serve ad eliminare eventuali glitch, rumori indesiderati che possono essere causati dal cambio repentino di frequenza dell’oscillatore (ie si passa da una nota a 146.83 Hz ad una a 8372.01). Una situazione del genere potrebbe altrimenti creare delle discontinuita’ nel segnale, che si manifesterebbero nel dominio audio sotto forma di glitch (clicks, pops, etc.).
A seguire la nota MIDI ricevuta viene passata come argomento alla funzione setSquareWave(), anche questa dichiarata alla fine del codice nella sezione FUNZIONI. Questa funzione seleziona la lookup table da utilizzare in base alla nota MIDI. Piu’ la nota e’ alta (frequenza in Hz maggiore) meno armoniche devo essere utilizzate, e dunque una lookup table con meno armoniche viene selezionata.
Il resto del codice presente nel ciclo while e’ lo stesso utilizzato nel tutorial precedente.
A questo punto si ha la sezione dove le funzioni vengono dichiarate. La prima e’ setInterp(), spiegata nel tutorial parte 6.
A seguire le funzioni setSquareWave() e initSquareWave():
function void setSquareWave(int x) {  // seleziona l'UGen Gen10 in base alla nota ricevuta.  // I diversi Gen10 hanno un diverso numero di armoniche,  // piu' e' alta la nota selezionata meno armoniche si avranno  // per evitare problemi di aliasing  if(x <= 70 && square_1.isConnectedTo(env) == 0)  {    phasor => square_1 => env;    phasor =< square_2 =< env;    phasor =< square_3 =< env;    phasor =< square_4 =< env;    <<< "SQUARE 1" >>>;  }  else if(x > 70 && x <= 90 && square_2.isConnectedTo(env) == 0)  {    phasor =< square_1 =< env;    phasor => square_2 => env;    phasor =< square_3 =< env;    phasor =< square_4 =< env;    <<< "SQUARE 2" >>>;  }  else if(x > 90 && x <= 108 && square_3.isConnectedTo(env) == 0)  {    phasor =< square_1 =< env;    phasor =< square_2 =< env;    phasor => square_3 => env;    phasor =< square_4 =< env;    <<< "SQUARE 3" >>>;  }  else if(x > 108 && x <= 127 && square_4.isConnectedTo(env) == 0)  {    phasor =< square_1 =< env;    phasor =< square_2 =< env;    phasor =< square_3 =< env;    phasor => square_4 => env;    <<< "SQUARE 4" >>>;  } } function void initSquareWave() {  // crea 4 diverse lookup table, con contenuti armonici different.  // onda quadra - formula: y = sum[(1/k)*sin(2PI*f*k*t)]; con k=1,3,5,7,9,...  // square_1: 14 parziali  for(1=>int c; c<15; c++)  {      ((c * 2) - 1) => int odd; // solo numeri dispari      coefficients.size(odd); // ridimensiona l'array      1. / odd => float amp;  // 1/n      amp => coefficients[odd-1];  }  square_1.coefs(coefficients);  // square_2: 7 parziali  for(1=>int c; c<8; c++)  {    ((c * 2) - 1) => int odd;    coefficients.size(odd);    1. / odd => float amp;    amp => coefficients[odd-1];  }  square_2.coefs(coefficients);  // square_3: 3 parziali  for(1=>int c; c<4; c++)  {    ((c * 2) - 1) => int odd;    coefficients.size(odd);    1. / odd => float amp;    amp => coefficients[odd-1];  }  square_3.coefs(coefficients);  // square_4: 1 parziale - sinusoide  for(1=>int c; c<2; c++)  {    ((c * 2) - 1) => int odd;    coefficients.size(odd);    1. / odd => float amp;    amp => coefficients[odd-1];  }  square_4.coefs(coefficients); }
setSquareWave(), come gia’ detto in precedenza, si occupa di scegliere la lookup table giusta in base alla nota MIDI ricevuta. Infatti quando quest’ultima viene passata come argomento, viene controllato il suo valore e che la lookup table da utilizzare (in base alla nota MIDI) non sia gia’ in uso.
Per fare cio’ viene utilizzato il metodo comune a tutti gli UGen, isConnectedTo(nomeDell’UGen):
square_1.isConnectedTo(env) == 0
Utilizzando questo metodo si puo’ capire se un UGen A e’ connesso ad un altro UGen B.
Il valore restituito da isConnectedTo() e’ 1 nel caso in cui I due UGen sono connessi, altrimenti e’ 0.
Nel caso in cui la nota MIDI e’ nel range desiderato (ie nota <= 70) ed il risultato di isConnectedTo() e’ 0, viene eseguito il codice nelle parentesi graffe {}.
I 4 if contenuti in setSquareWave, hanno tutti una struttura simile:
phasor => square_1 => env;   phasor =< square_2 =< env;     phasor =< square_3 =< env;     phasor =< square_4 =< env;
In termini pratici, questo codice non fa altro che connettere l’output di phasor esclusivamente alla lookup table desiderata, e di connettere solo l’output della lookup table desiderata all’inviluppo env.
Per disconnettere due UGen si utilizza l’operatore UnChucK (=<).
Il motivo per cui si effettuano tutte queste operazioni e’ semplicemente per evitare calcoli inutili. Solo un Gen10 alla volta e’ necessario per generare la forma d’onda desiderata, e lasciare phasor connesso a piu’ Gen10 sarebbe uno spreco, in quando ogni operazione ha un costo computazionale che influenza le performance del programma.
Lasciare phasor connesso a tutti i Gen10 equivale a lasciare tutte le luci accese in un appartamento, anche se si e’ da soli, chiusi in una stanza. Come risultato si avra’ un maggiore consumo di corrente.
Nelle ultime righe di codice viene dichiarata la funzione initSquareWave() che genera le 4 forme d’onda quadra, ognuna con un diverso numero di parziali.
Questa funzione non ha bisogno di argomenti e quando invocata come prima cosa crea un array di tipo float con lunghezza pari a 0, a cui successivamente aggiunge I valori corrispondenti all’ampiezza delle parziali della forma d’onda.
Questa operazione si ripete 4 volte (una volta per onda quadra, per Gen10).
Per fare cio’ viene utilizzato una nuova struttura di controllo, il ciclo for:
for(1=>int c; c<15; c++)   {       ((c * 2) - 1) => int odd; // solo numeri dispari       coefficients.size(odd); // ridimensiona l'array       1. / odd => float amp;  // 1/n       amp => coefficients[odd-1];   }
Questo ciclo esegue il codice presente tra le parentesi graffe {} un numero x di volte, in base alla dichiarazione presente tra le parentesi tonde ().
for necessita di una variabile con un valore noto (non nulla - il valore puo’ essere anche 0), di una condizione da valutare e di un’operazione da svolgere per far si che il codice tra le parentesi graffe {} venga eseguito.
for(variabile; condizione; operazione) { codice da eseguire un numero ‘n’ di volte } for(5 => int c; c > 0; c--) { <<< c >>>; }
Questo breve esempio puo’ essere interpretato nel seguente modo:
affinche ‘c’ (che ha valore 5) e’ maggiore di 0, decrementa ‘c’ di 1, e stampa il suo valore sulla console. Quando ‘c<=0’ interrompi il loop e prosegui nel programma.
Il risultato sara’:
5 :(int) 4 :(int) 3 :(int) 2 :(int) 1 :(int)
Quello che succede in pratica e’:
la variabile c viene creata e gli viene assegnato il valore 5
viene controllato che e’ maggiore di 0
viene eseguito il codice tra le parentesi graffe {}
a c viene assegnato il valore (c-1) – in ChucK ed altri linguaggi di programmazione il valore delle le variabili puo’ essere incrementato e/o diminuito di 1, semplicemente utilizzando nomeDellaVariabile++ oppure nomeDellaVariabile-- (http://chuck.cs.princeton.edu/doc/language/oper.html#incdec)
il codice tra le parentesi graffe {} viene ripetuto e a c viene sottratto 1, affinche’ c>0
Tornando al codice in esame, il ciclo for esegue le seguenti operazioni:
((c * 2) - 1) => int odd; // solo numeri dispari      coefficients.size(odd); // ridimensiona l'array      1. / odd => float amp;  // 1/n      amp => coefficients[odd-1];
La variabile c dichiarata nel costrutto for viene utilizzata per inizializzare la variabile odd con solo numeri dispari:
(1*2) -1 = 1
(2*2) -1 = 3
(3*2) -1 = 5
etc.
L’array dichiarato all’esterno del ciclo for viene ridimensionato (era di lunghezza 0 al momento della sua dichiarazione) utilizzando il metodo size(). La variabile amp viene creata e gli viene assegnato un valore pari a 1/odd. Infine amp viene salvata in una cella (odd-1) dell’array coefficients.
Queste 4 righe di codice non fanno altro che calcolare l’ampiezza delle parziali di una forma d’onda quadra in accordo con la seguente formula:
y = sum[(1/k)*sin(2PI*f*k*t)]; con k=1,3,5,7,9,...
la quale dice che per ottenere una forma d’onda quadra serve sommare solo sinusoidi dispari con ampiezza uguale a 1/n con n = numero della parziale.
Utilizzando Gen10 non e’ necessario pero’ effettuare nessuna operazione se non il calcolo dell’ampiezza delle armoniche. Le operazioni trigonometriche avvengono all’interno dell’UGen stesso.
Alla fine di ognuno dei 4 cicli for si ha un rigo di codice del tipo:
square_1.coefs(coefficients);
il quale passa l’array appena generato come argomento al metodo .coefs(), che fa si che la forma d’onda desiderata venga generata e salvata in uno dei 4 UGen Gen10.
I 4 cicli for sono identici a parte per il numero delle parziali che vengono create all’interno delle parentesi graffe {}, e dunque alla durata del loop for.
Questo e’ dovuto alla differenza nel codice contenuto nelle parentesi tonde () che seguono for:
for(1=>int c; c<15; c++)
for(1=>int c; c<8; c++)
for(1=>int c; c<4; c++)
for(1=>int c; c<2; c++)
Il primo ciclo for genera 14 parziali, il secondo 7, il terzo 3 e l’ultimo 1.
RIEPILOGO
utilizzo di lookup table - UGen Gen10
generatore d'onda quadra limitata in banda
connettere e disconnettere UGen
struttura di controllo "for"
Il codice ed il testo possono essere scaricati qui: https://bitbucket.org/mariobuoninfante/chuck_workshop/src
0 notes
lionsground · 8 years ago
Video
youtube
Learn how to start a Youtube channel in 2017 for beginners and create a successful channel and make money for free. Change your life today and make your hobby your full-time job. If you've got questions drop them below. What is success? Success can not be explained in digits but in freedom and happiness that it offers. This is why I made this video to motivate you to do something you enjoy. How to know the peak hour? (FB Page required) https://www.facebook.com/YOURFBPAGENAME/insights/?section=navPosts CAMCORDERS TOP 3 * US $42.69 - FULL HD 1080P 24MP HDMI 3" TFT LCD 16X ZOOM Digital Video Camera DV Camcorder : https://goo.gl/J0LJ4m * US $103.54 - 24MP 3.0'' HD 1080P Wifi LCD Screen Digital Video Camera Camcorder DV 16X Zoom : https://goo.gl/RhOVOT * US $127.59 - HD 1080P 16X Digital Zoom WiFi Video Camcorder Camera DV&LUX480 LED Video Light : https://goo.gl/vzSQju USB MICROPHONES TOP 3 * US $15.96 - Professional USB Podcast Condenser Microphone PC Recording MIC with Stand Tripod : https://goo.gl/QEeEFj * US $25.99 - USB Condenser Studio Sound Recording Microphone Mic + Shock Mount Tripod Stand : https://goo.gl/PN0M54 * US $159.99 - Blue Microphones Yeti USB Microphone - Blackout Edition Bundle : https://goo.gl/BoiBXI FREE EDITING TOOLS * Video editing 'Filmora' https://filmora.wondershare.com/ * Image editing 'Pixlr' https://pixlr.com/ * Audio editing 'Audacity' http://www.audacityteam.org/ For Audacity 'one click' vocal improvement you are required to create a 'CHAIN'. In order to do that you need to open a small audio file or record a short sample and select all(CTRL+A). Than go to: 1. File 2. Edit Chains 3. Add 4. Insert: Command: Compressor Parameters: Threshold=-15.000000 NoiseFloor=-40.000000 Ratio=2.000000 AttackTime=0.010000 ReleaseTime=1.000000 Normalize=yes UsePeak=no OK 5. Insert: Command: Equalization Parameters: FilterLength=4001 CurveName="Bass Boost" InterpolateLin=no interpolationMethod=B-spline OK 6. Insert: Command: Equalization Parameters: FilterLength=4001 CurveName="Treble Boost" InterpolateLin=no interpolationMethod=B-spline OK 7. Insert: Command: Normalize Parameters: ApplyGain=yes RemoveDcOffset=yes Level=-1.000000 StereoIndependent=no OK 8. Insert: Command: HardLimiter Parameters: dB_limit="-4" Residue_level="0" Wet_level="1" OK 9. Insert: Command: Normalize Parameters: ApplyGain=yes RemoveDcOffset=yes Level=-1.000000 StereoIndependent=no OK 10 OK In order to apply the chain go to file, apply chain. 💢 GET ACCESS TO EXCLUSIVE CONTENT ☷☷☷☷☷☷ ➡️ https://www.patreon.com/lionsground ➡️ Never miss a story again — sign up to our Telegram and we'll keep you up to speed on your mobile or desktop: https://telegram.me/lionsground ➡️ Get your Lions Ground T-Shirt: https://teespring.com/youtubelionsground 🚫 If I miss any advertising / disrespectful comments to anyone make sure to flag them, we're here to enjoy the videos!
0 notes
mbuoninfante · 7 years ago
Text
MonoSynth in ChucK - pt.6
Parte 6
SCOPO
Aggiungere il portamento al programma precedente
Utilizzo di funzioni, array e di messaggi MIDI control change.
PREREQUISITI
Per poter utilizzare questo programma serve avere un controller MIDI connesso al computer, o una virtual MIDI keyboard. Questo va connesso (o aperto nel caso di una tastiera virtuale), prima di far partire il programma.
Conoscenza base del protocollo MIDI (https://it.wikipedia.org/wiki/Musical_Instrument_Digital_Interface - http://www.nyu.edu/classes/bello/FMT_files/8_MIDIcomms.pdf - https://www.nyu.edu/classes/bello/FMT_files/9_MIDI_code.pdf)
ANALISI DEL PROGRAMMA
In questo esempio viene implementato un altro modulo presente in molti sintetizzatori commerciali, il portamento, che permette di effettuare un glissando tra note successive.
Di solito, quando due note differenti vengono generate in successione (ie C4 e dopo F#4), la frequenza dell’oscillatore cambia immediatamente. Con l’utilizzo del portamento e’ possibile rendere questo passaggio graduale. Al cambiare della nota, la frequenza dell’oscillatore non passa immediatamente dal punto A (frequenza della prima nota) al punto B (frequenza della seconda nota), ma “impiega del tempo” la cui durata corrisponde al valore del portamento.
Tumblr media
Nella figura precedente viene illustrato il comportamento della frequenza dell’oscillatore nel tempo,  nel caso in cui non e’ presente alcun
portamento (linea nera) e quando invece c’e’ portamento (linea rossa). In pratica il segnale che controlla frequenza viene interpolato.
In ChucK fino ad ora la frequenza dell’UGen SinOsc e’ stata sempre controllata attraverso l’utilizzo del metodo freq(), ma esistono altri modi per svolgere lo stesso compito.
In questo esempio SinOsc utilizza il segnale inviato al suo ingresso come controllo in frequenza.
Step step => Envelope line => SinOsc osc => Envelope env => dac; step.next(1); osc.sync(0);  
Per fare cio’ viene introdotto un nuovo UGen: Step.
Questo genera un valore costante che puo’ essere impostato utilizzando il metodo next().
Step viene collegato ad un Envelope il quale ne controlla l’ampiezza in modo lineare (interpolazione).
L’output dell’Envelope viene inviato in ingresso a SinOsc, il quale lo utilizza come controllo in frequenza.
Dopo la dichiarazione degli UGen, questi vengono inizializzati. Step genera un segnale costante pari a 1 e SinOsc attraverso l’utilizzo del metodo sync(), viene impostato in modo da utilizzare il segnale in ingresso come controllo in frequenza (vedi metodo sync in: ChucK FLOSS: SinOsc).
//connessioni MIDI MidiIn mIn; // crea un input MIDI MidiMsg msgIn;  // crea un contenitore per i messaggi MIDI ricevuti mIn.open("Launchkey MK2 25 MIDI 1"); // variabili 144 => int NOTE_ON; 128 => int NOTE_OFF; 176 => int CTRL_CHANGE; 0 => int MIDI_CHANNEL;  // MIDI channel da 0 a 15 float fr; float amp; int midiNote; int lastNote; 0.05 => float freqInterpolation;  // in secondi [21,22,23,24,25,26,27,28] @=> int knobs[]; 0.2 => amp; osc.gain(amp); 10::ms => dur attackTime; 500::ms => dur releaseTime; line.time(freqInterpolation);
A seguire gli oggetti MidiIn e MidiMsg vengono dichiarati e il device MIDI in uso viene connesso.
Come negli esempi precedenti anche le variabili in uso nel programma vengono dichiarate e inizializzate.
Un nuovo elemento viene pero’ introdotto, la variabile knobs[], che rappresenta un array di valori.
Un array puo’ essere immaginato come una scatola in grado di contenere un numero n di dati. Un semplice esempio di array puo’ essere quello dei cruciverba, dove si hanno delle singole caselle contenti dati atomici (in questo caso lettere) che sono parte di una struttura piu’ grande (parola).
In ChucK bisogna specificare il tipo di dato che si vuole salvare nell’array, che nel caso specifico e’ di tipo int.
Esistono vari modi per dichiarare ed inizializzare un array, nel caso in cui lo si voglia solo dichiarare bisogna utilizzare la seguente sintassi:
tipo nome[numeroDiCelle];
int numeriPrimi[12];
Il tipo di dato viene specificato, e successivamente il nome dell’array seguito da due parentesi quadre [] contenenti il numero di celle dell’array, che corrisponde al numero massimo di elementi che si possono salvare.
In questo caso l’array non essendo stato inizializzato, conterra’ tutti valori nulli, pari a 0.
Ovviamente esiste un modo per poter assegnare valori alle diverse celle di un array dopo la sua dichiarazione.
Va tenuto presente che in informatica (in ChucK e quasi tutti I linguaggi di programmazione) gli elementi di un array si contano a partire da 0. Dunque nel caso di un array di 10 elementi, si avra’ la prima cella pari a array[0], la seconda array[1] e cosi via fino all’ultima che sara’ array[9].
Per assegnare un valore alla prima cella di un array, dopo che questo e’ stato dichiarato, si utilizza la seguente sintassi:
valore => nome[numeroCella];
13 => numeriPrimi[0];
Cosi’ come illustrato nel codice precedente, gli array possono anche essere inizializzati nel momento in cui vengono dichiarati, utilizzando la seguente sintassi:
valori @=> tipo nome[];
[1,2,3,5,8,13] @=> int fibonacci[];
A differenza di una normale variabile, per inizializzare un intero array (non una singola cella) viene utilizzato il simbolo @=> (invece di => - per maggiori informazioni al riguardo: ChucK FLOSS: Arrays)
A questo punto l’array fibonacci contiene I seguenti 6 numeri: 1, 2, 3, 5, 8, 13.
In modo molto simile a come un singolo valore e’ stato assegnato ad una specifica cella, si possono ottenere I valori contenuti in un array. Nell’esempio dell’array fibonacci, si ha che:
fibonacci[0] = 1
fibonacci[1] = 2
fibonacci[2] = 3
fibonacci[3] = 5
fibonacci[4] = 8
fibonacci[5] = 13
Si puo’ dunque utilizzare fibonacci[4] per ottenere il valore intero 8. ln effetti fibonacci[4] verra’ trattato esattamente come un numero intero, si possono dunque effettuare operazioni matematiche, lo puo’ utilizzare in strutture di controllo, etc. (ie fibonacci[4] * 2 restituira’ 16).
Tornando al programma in questione, si ha:
[21,22,23,24,25,26,27,28] @=> int knobs[];
che corrisponde a un array di numeri interi, I quali rappresentano i control number (vedi: MIDI protocol) degli 8 potenziometri presenti sul controller Novation Launchkey MK2 (in uso al momento della realizzazione del testo).
I potenziometri su questo (e molti altri) controller inviano messaggi MIDI di tipo control change che come I messaggi Note On e Note Off sono composti da 3 byte:
byte 1: Tipo di Messaggio
byte 2: dato 1
byte 3: dato 2
Nel caso di messaggi control change il secondo byte corrisponde al control number, cioe’ un numero identificativo utilizzato dal controllo (knob, fader, bottone, etc.). Il terzo byte invece corrisponde al valore generato dal controllo, in un range da 0 a 127.
Nel caso di un knob, generalmente, si ha un valore 0 quando il knob e impostato sul minimo e un valore 127 quando e’ impostato al massimo.
Nel programma in questione verra’ utilizzato solo uno degli 8 potenziometri disponibili sul controller, ma nonostante cio’ si e’ deciso di utilizzare un array contenente anche le informazioni riguardanti I 7 potenziometri non in uso. Questo e’ stato fatto per facilitare l’aggiunta di altro codice al programma considerando che nei prossimi tutorial probabilmente altri potenziometri verranno utilizzati per diversi scopi.
Dopo la dichiarazione di knobs[] il programma prosegue con la dichiarazione di altre variabili e infine viene impostato il tempo di portamento utilizzando il metodo time() dell’UGen Envelope.
In questo modo viene specificato il tempo necessario per passare da un valore A ad un valore B, nel caso specifico si parla della frequenza dell’oscillatore.
while(true) {  //  avanza ad ogni messaggio MIDI ricevuto  mIn => now;  while(mIn.recv(msgIn))  {    if(msgIn.data1 == (NOTE_ON + MIDI_CHANNEL) && msgIn.data3 != 0)    {      msgIn.data2 => midiNote;      Std.mtof(midiNote) => fr;      line.target(fr);      msgIn.data2 => lastNote;      env.duration(attackTime);      env.keyOn(1);      <<< "KEY PRESSED" >>>;    }    else if( (msgIn.data1 == (NOTE_ON + MIDI_CHANNEL) && msgIn.data3 == 0 && msgIn.data2 == lastNote) || (msgIn.data1 == (NOTE_OFF + MIDI_CHANNEL) && msgIn.data2 == lastNote) )    {      // impostare il tempo impegato dall'inviluppo per      // passare da 1 a 0 (da massimo a minimo)      env.duration(releaseTime);      // triggerare l'inviluppo per farlo passare da ON a OFF      env.keyOff(1);      <<< "KEY RELEASED" >>>;    }    else if(msgIn.data1 == (CTRL_CHANGE+MIDI_CHANNEL) && msgIn.data2 == knobs[0])    {      setIntep(msgIn.data3) => freqInterpolation;      line.time(freqInterpolation);    }  } }
Il ciclo while risulta essere praticamente identico a quello mostrato nella Parte 5, con l’aggiunta pero’ di un ulteriore costrutto else if alla fine della struttura.
Quest’ultimo if viene utilizzato per controllare che il messagio MIDI inviato dal controller sia di tipo control change, che sia sul canale MIDI desiderato e che il control number corrisponda a quello specificato nel primo elemento salvato nell’array knobs (cioe’ il primo verso sinistra degli 8 potenziometri presenti sul controller in utilizzo).
A questo punto, se il messaggio MIDI soddisfa tutte queste condizioni il seguente codice viene eseguito:
setIntep(msgIn.data3) => freqInterpolation;      line.time(freqInterpolation);
Il terzo byte del messaggio MIDI (msgIn.data3) viene passato come argomento alla funzione setInterp, la quale genera un valore e lo assegna alla variabile freqInterpolation. Questa poi viene utilizzata come tempo di interpolazione per l’Envelope line (che controlla la frequenza dell’oscillatore). Dunque freqInterpolation rappresenta il portamento.
In queste due ultime linee di codice viene utilizzata una funzione che non appartiene a nessuna libreria di ChucK (Std e Math), che infatti e’ stata scritta ad hoc nelle ultime righe del programma, al di fuori del ciclo while.
function float setIntep(float x) {  x / 127. => x;  //  scala in un range da 0 a 1  x*x*x => x; //  rendi la funzione esponenziale  (x * 0.997) + 0.003 => x; //  scala in un range da 3 a 1000 millisecondi  <<< "Freq Interpolation: " + x + " ms" >>>;  return x; }
In ChucK, come in altri linguaggi di programmazione, e’ possibile scrivere le proprie funzioni dichiarandole nello script, all’inizio o alla fine del codice.
Una funzione non e’ altro che un pezzo di codice che puo’ essere utilizzato infinite volte nel programma, atto a svolgere un determinato compito con lo scopo di ottimizzare la leggibilita’, la manutenibilita’ e la qualita’ del codice.
Nel caso dell’esempio in questione, la funzione setInterp genera un numero utilizzato come valore di interpolazione per l’UGen Envelope.
Le funzioni in ChucK hanno la seguente struttura:
function tipoDiDato nomeDellaFunzione(tipoDiDato nomeDellArgomento)
{
codice da eseguire;
}
function float quadratoDiUnNumero(float x) { return x*x; }
La dichiarazione inizia con la parola chiave function (e’ anche possibile utilizzare la versione abbreviata fun), seguita dal tipo di dato che la funzione deve restituire, il nome della funzione, a seguire tra parentesi tonde () gli argomenti di cui la funzione necessita (possono anche non esserci argomenti ed in tal caso le parentesi tonde saranno vuote), ed infine il codice da eseguire quando la funzione viene utilizzata, tra parentesi graffe{}.
Va notato che anche per gli argomenti (nel caso in cui ce ne siano), va specificato il tipo di dato.
Come detto in precedenza, dopo la parola chiave function va specificato il tipo di dato che la funzione restituira’. Questo puo’ essere di qualsiasi tipo (int, float, etc.), ed anche di un tipo nuovo che non e’ mai stato incontrato fin’ora: void.
Quando una funzione e’ di tipo void significa che non restituisce alcun valore.
Di seguito due esempi che mostrano due diverse funzioni, la prima restituisce un dato di tipo string:
"myFile.wav" => string fName; function string addPrefix(string fileName) {    return(me.dir() + fileName); } addPrefix(fName) => string newFName; <<< newFName >>>;
In questo caso la funzione riceve un dato string e ne restituisce uno dello stesso tipo. Per restituire un dato, le funzioni utilizzano la parola chiave return seguita dal dato da restituire tra parentesi tonde ().
La stringa myFile.wav (variabile fileName) viene passata alla funzione, la quale aggiunge un’altra stringa contenente il nome del folder in cui lo script e’ contenuto (per fare cio’ viene utilizzato me.dir()) alla stringa ricevuta. L’output di questo programma, stampato sulla console (CTRL+0) e’:
"/home/myFile.wav" : (string)
Questo secondo esempio mostra l’utilizzo di una funzione che non restituisce alcun dato:
function void printTime() {    <<< "Time Elapsed in Minutes: ", now/minute >>>; } printTime();
printTime non necessita di alcun argomento, e quando utilizzata stampa sulla console la frase “Time Elapsed in Minutes:“ piu’ un numero che rappresenta il tempo (in minuti) passato da quando la Virtual Machine di ChucK e’ stata avviata a quando la funzione e’ stata chiamata:
Time Elapsed in Minutes: 5.392267
Essendo printTime una funzione che non restituisce alcun valore, non contiente la parola chiave return.
Per ulteriori inforamazioni sulle funzioni in ChucK: ChucK FLOSS: Functions.
RIEPILOGO
funzioni
array
utilizzo di UGen per controllare la frequenza dell'oscillatore
messaggi MIDI control change
Il codice ed il testo possono essere scaricati qui: https://bitbucket.org/mariobuoninfante/chuck_workshop/src
0 notes
mbuoninfante · 7 years ago
Text
MonoSynth in ChucK - pt.5
Parte 5
SCOPO
Migliorare il programma illustrato nella parte 4.
Utilizzo di un device MIDI (controller, keyboard, virtual keyboard, etc.) per controllare un oscillatore sinusoidale.
Utilizzo della struttura di controllo if/else e if/else if.
PREREQUISITI
Per poter utilizzare questo programma serve avere un controller MIDI connesso al computer, o una virtual MIDI keyboard. Bisogna connettere (o aprire nel caso di una tastiera virtuale) il controller prima di far partire il programma.
Conoscenza base del protocollo MIDI (https://it.wikipedia.org/wiki/Musical_Instrument_Digital_Interface - http://www.nyu.edu/classes/bello/FMT_files/8_MIDIcomms.pdf - https://www.nyu.edu/classes/bello/FMT_files/9_MIDI_code.pdf)
ANALISI DEL PROGRAMMA
Nella parte 4 si e’ realizzato un semplice programma che permette di controllare un oscillatore sinusoidale ed il suo inviluppo via MIDI.
Con l’intento di offrire una gentile introduzione all’argomento, non sono state fatte una serie di considerazioni importanti riguardo la gestione di messaggi MIDI, infatti il precedente programma, nonostante perfettamente funzionante, non considera alcuni possibili situazioni in cui l’utente si puo’ trovare e che possono causare comportamenti indesiderati.
In questa nuova versione vengono implementate delle restrizioni che rendono il programma piu’ efficiente.
La parte iniziale e’ praticamente identica a quella del programma spiegato nella parte 4, a parte per l’introduzione di 4 nuove variabili: NOTE_ON, NOTE_OFF, MIDI_CHANNEL e lastNote.
SinOsc sine => Envelope env => dac; //connessioni MIDI MidiIn mIn; // crea un input MIDI MidiMsg msgIn;  // crea un contenitore per i messaggi MIDI ricevuti mIn.open(1);   // selezionare la porta MIDI che corrisponde al controller utilizzato (CTRL+2 per controllare le porte MIDI) // variabili 144 => int NOTE_ON; 128 => int NOTE_OFF; 0 => int MIDI_CHANNEL;// canale MIDI in un range da 0 a 15 float fr; float amp; int midiNote; int lastNote; 0.2 => amp; sine.gain(amp); // impostare l'ampiezza di "sine" su "amp", cioe' 0.2 10::ms => dur attackTime;  // tempo di attacco 500::ms => dur releaseTime; // tempo di rilascio
Le prime 3 variabili rappresentano I due messaggi MIDI note on e note off ed il canale MIDI utilizzato dal device in uso. In pratica quando si preme un tasto su una tastiera MIDI, il messaggio e’ composto da 3 byte, di cui il primo specifica il tipo di messaggio (ie note on, cc, real time, etc.). Nel caso di note on il valore e’ appunto 144 + canale MIDI (in un range da 0 a 15). Quindi nel caso in cui il device invia messaggi sul canale MIDI 3, il byte sara’ 146.
Lo stesso discorso vale per il messaggio note off, con il valore del primo byte del messaggio uguale a 128 + canale MIDI.
La variabile lastNote verra’ utilizzata in seguito nel codice.
Il programma prosegue con il ciclo infinto while:
while(true) {      // quando un (qualsiasi) messaggio MIDI viene ricevuto    // avanza nel programma    mIn => now;        while(mIn.recv(msgIn))      {        // stampa sulla console il messaggio MIDI raw        <<< "Raw MIDI msg: " + msgIn.data1, msgIn.data2, msgIn.data3 >>>;                // controlla che il messaggio MIDI sia un NOTE ON con velocity maggiore di 0        if(msgIn.data1 == (NOTE_ON + MIDI_CHANNEL) && msgIn.data3 != 0)        {            // prendere il MIDI byte 2 (nel caso di messaggi NOTE ON/OFF e' il pitch)            // ed assegnarlo alla variabile di tipo int "midiNote"            msgIn.data2 => midiNote;            Std.mtof(midiNote) => fr;   // conversione da MIDI a Freq in Hz            sine.freq(fr);  // assegnare la frequenza all'oscillatore            msgIn.data2 => lastNote;            // impostare il tempo impegato dall'inviluppo per            // passare da 0 a 1 (da minimo a massimo)            env.duration(attackTime);            // triggerare l'inviluppo per farlo passare da OFF a ON            env.keyOn(1);                        <<< "KEY PRESSED" >>>;        }                // controlla che il messaggio MIDI sia un NOTE OFF o un NOTE ON con velocity 0        // in entrambi i casi la nota MIDI deve combaciare con l'ultima suonata (playedNote)        else if( (msgIn.data1 == (NOTE_ON + MIDI_CHANNEL) && msgIn.data3 == 0 && msgIn.data2 == lastNote) || (msgIn.data1 == (NOTE_OFF + MIDI_CHANNEL) && msgIn.data2 == lastNote) )        {            // impostare il tempo impegato dall'inviluppo per            // passare da 1 a 0 (da massimo a minimo)            env.duration(releaseTime);                        // triggerare l'inviluppo per farlo passare da ON a OFF            env.keyOff(1);                        <<< "KEY RELEASED" >>>;                  }              } }
Anche questa parte del programma e’ molto simile a quella mostrata nella nella parte 4. Detto cio’, ci sono pero’ delle piccole ma importanti differenze.
Come prima cosa ogni qual volta un messaggio MIDI viene generato, questo viene stampato sulla console (CTRL+0). Cio’ permette di visualizzare I byte che sono parte del messaggio.
In seguito il costrutto if/else risulta essere leggermente diverso da quello usato in precedenza.
In questo caso il messaggio MIDI viene utilizzato per controllare la frequenza dell’oscillatore ed il suo inviluppo solo se e’ del tipo note on, se e’ stato inviato sul canale MIDI specificato all’inizio del programma e se la sua velocity e’ diversa da zero.
if(msgIn.data1 == (NOTE_ON + MIDI_CHANNEL) && msgIn.data3 != 0)
La novita’ risiede nel fatto che si stanno valutando piu’ condizioni, utilizzando piu’ operatori logici (prima condizione && seconda condizione), che in ChucK sono:
&& : and
|| : or
== : uguale
!= : diverso
> : maggiore di
>= : maggiore uguale di
< : minore di
<= : minore uguale di
Per maggiori info a proposito degli operatori logici si possono trovare molte fonti in rete (https://www.c-programming-simple-steps.com/logical-operators.html).
Nel caso in cui il messaggio ricevuto rispetti tutti I criteri specificati precedentemente, la nota MIDI viene convertita in frequenza ed utilizzata per controllare la frequenza dell’oscillatore e l’inviluppo viene triggerato dopo aver impostato il suo tempo di attacco su attackTime. Infine la nota MIDI viene assegnata alla variabile lastNote, che tornera’ utile in seguito.
Nel caso in cui il messaggio ricevuto non rispetti tutti I criteri specificati nel costrutto if, il codice tra le parentesi graffe {} che seguono if verra’ ignorato.
A questo punto si ha il costrutto else che, a differenza del programma utilizzato nella parte 4, e’ seguito da un altro costrutto if:
else if( (msgIn.data1 == (NOTE_ON + MIDI_CHANNEL) && msgIn.data3 == 0 && msgIn.data2 == lastNote) || (msgIn.data1 == (NOTE_OFF + MIDI_CHANNEL) && msgIn.data2 == lastNote) )
Questo rigo di codice non fa altro che dire: nel caso in cui la condizione specificata nel precedente if non sia soddisfatta, valuta la seguente:
e’ il primo byte di tipo note on?
e’ stato inviato sul canale MIDI giusto?
se e’ di tipo note on ha la velocity uguale a 0?
e’ il secondo byte (nota) uguale alla variabile lastNote?
Nel caso nessuna delle precedenti condizioni sia soddisfatta verfica le seguenti (il simbolo || significa or)
e’ il primo byte di tipo note off?
e’ stato inviato sul canale MIDI giusto?
e’ il secondo byte (nota) uguale alla variabile lastNote?
In pratica il codice contenuto nelle seguenti parentesi graffe {} (che consiste nel rilasciare la nota) viene eseguito solo nel caso in cui si  riceve un messaggio note on con velocity pari a 0, o di tipo note off (la velocity non viene presa in considerazione), se questo e’ inviato sul canale MIDI giusto ma soprattutto se e’ uguale alla variabile lastNote.
Quest’ultima parte e’ di fondamentale importanza. Va ricordato che lo scopo del programma e’ quello di controllare un (semplicissimo al momento) synth monofonico, dove quindi si puo’ suonare una sola nota alla volta.
Bisogna dunque prendere in considerazione un importante possibile scenario. Nel caso in cui si preme un tasto della tastiera e successivamente, senza rilasciare il primo tasto se ne preme un altro, poi si rilascia il primo, I seguenti messaggi vengono generati:
note on (primo tasto premuto)
note on (secondo tasto premuto)
note off (primo tasto rilasciato)
Il fatto di aver assegnato il valore dell’ultima nota premuta alla varibile lastNote, permette di poter in seguito accettare solo messaggi note off provenienti dall’ultima nota, cioe’ la nota che corrisponde a lastNote.
Infatti, nel caso in cui questa condizione non esistesse, in una situazione come quella descritta nell’esempio precedente in cui due tasti vengono premuti e solo il primo viene rilasciato, al rilascio del primo tasto premuto, il messaggio di note off verrebbe preso in considerazione e la nota verrebbe rilasciata.
Dunque, l’accettare solo messaggi note off generati dall’ultima nota suonata, permette di premere e rilasciare piu’ tasti contemporaneamente, con la consapevolezza che solo l’ultima nota suonata verra’ presa in considerazione.
RIEPILOGO
 operatori logici
 struttura di controllo if/else e if/else if
Il codice ed il testo possono essere scaricati qui: https://bitbucket.org/mariobuoninfante/chuck_workshop/src
0 notes
mbuoninfante · 7 years ago
Text
MonoSynth in ChucK - pt.4
Parte 4
SCOPO
Utilizzo di un device MIDI (controller, keyboard, virtual keyboard, etc.) per controllare un oscillatore sinusoidale.
Utilizzo della struttura di controllo if/else.
PREREQUISITI
Per poter utilizzare questo programma serve avere un controller MIDI connesso al computer, o una virtual MIDI keyboard. Bisogna connettere (o aprire nel caso di una tastiera virtuale) il controller prima di far partire il programma.
Conoscenza base del protocollo MIDI (https://it.wikipedia.org/wiki/Musical_Instrument_Digital_Interface - http://www.nyu.edu/classes/bello/FMT_files/8_MIDIcomms.pdf - https://www.nyu.edu/classes/bello/FMT_files/9_MIDI_code.pdf)
ANALISI DEL PROGRAMMA
Anche in questa quarta parte, il programma inizia con la dichiarazione degli UGen, seguita pero’ da due oggetti nuovi: MidiIn e MidiMsg.
//oscillatore sinusoidale collegato ad un inviluppo SinOsc sine => Envelope env => dac; //connessioni MIDI MidiIn mIn; MidiMsg msgIn;
MidiIn rappresenta la porta MIDI utilizzata da ChucK (rappresenta il controller), mentre MidiMsg puo’ essere considerato come un contenitore in cui vengono contenuti I messaggi generati dal device MIDI.
Come tutti gli oggetti, questi necessitano di un nome, che in questo caso sono mIn per l’oggetto MidiIn e msgIn per MidiMsg.
Una volta creata la porta MIDI utilizzata dal programma, questa va collegata al device MIDI che si vuole utilizzare:
mIn.open("Virtual Keyboard");
Si utilizza il metodo open() il quale necessita di un argomento che puo’ essere o un numero intero o una stringa. L’argomento rappresenta il device MIDI a cui si vuole connettere la porta mIn.
In miniAudicle si possono visualizzare I device MIDI collegati al computer premendo CTRL+2, o  cliccando su Window→Device Browser.
A questo punto, nel tab MIDI, apparira’ una lista di input e output MIDI, che corrisponde alle porte MIDI disponibili.
Nel caso di questo programma, va considerata solo la lista riguardante gli input, in quanto non si vuole inviare nessun messaggio da ChucK ad un apparecchiatura esterna, ma solo ricevere messaggi da un device esterno.
Si puo’ notare che la lista contiente sia numeri (indice dei device disponibili) che stringhe di testo (nomi dei device). Come gia’ detto in precedenza, il metodo open() puo’ ricevere sia il numero che il nome del device a cui ci si vuole connettere. In questo caso e’ stato utilizzato il nome del device "Virtual Keyboard" il quale ovviamente cambia a seconda del device connesso, quindi questa riga di codice va cambiata in base al controller a disposizione.
Una volta aperta la porta MIDI, le variabili vengono dichiarate ed inizializzate:
// variabili float fr; float amp; int midiNote; 0.2 => amp; sine.gain(amp); // impostare l'ampiezza di "sine" su "amp", cioe' 0.2 10::ms => dur attackTime;  // tempo di attacco 500::ms => dur releaseTime; // tempo di rilascio
A seguire il ciclo while che rappresenta un loop infinito:
while(true) {      mIn => now;    while(mIn.recv(msgIn))      {        msgIn.data2 => midiNote;        Std.mtof(midiNote) => fr;   // conversione da MIDI a Freq in Hz        sine.freq(fr);  // assegnare la frequenza all'oscillatore        if(msgIn.data3 != 0)        {            env.duration(attackTime);            env.keyOn(1);                        <<< "KEY PRESSED" >>>;        }        else        {            env.duration(releaseTime);            env.keyOff(1);                        <<< "KEY RELEASED" >>>;                  }              } }
Questo programma risulta essere leggermente piu’ complesso dei precedenti e riserva alcune novita’.
Come prima cosa va notato il primo rigo di codice all’interno del costrutto while:
mIn => now;
Come gia’ detto in precedenza, now viene utilizzato come delay, una sorta di punto in cui il programma si ferma per un determinato lasso di tempo, prima di procedere ed eseguire il codice seguente.
La novita’ risiede nel fatto che qui now viene utilizzato in combinazione con la porta MIDI mIn, invece che con una variabile di tipo dur (ie 100::ms => now, 2::second => now).
In pratica il risultato e’ che il programma aspetta affinche’ l’oggetto mIn, che rappresenta il controller MIDI, non invia un messaggio MIDI. Nel momento in cui questo accade, il programma prosegue.
A seguire si ha un altro costrutto while, anche questo leggermente diverso da quello degli esempi precedenti.
while(mIn.recv(msgIn))  
In questo caso while valuta il valore restituito dal metodo .recv() a cui viene passato come argomento l’oggetto msgIn.
Nonostante questo passaggio possa sembrare al quanto complicato, quello che realmente succede e’ relativamente semplice: il codice contenuto tra le parentesi graffe {} che seguono while, viene eseguito ogni qual volta un messaggio MIDI viene generato dal controller MIDI (ie un tasto della tastiere viene premuto).
Il procedimento e’ il seguente:
Un tasto viene premuto sul controller
il controller genera ed invia un messaggio MIDI a ChucK
il messaggio raggiunge ChucK tramite la porta mIn
il messaggio viene salvato in msgIn utilizzando il costrutto mIn.recv(msgIn) – il quale indica che il messaggio ricevuto (da qui il nome del metodo recv) viene passato a msgIn.
Il ciclo while inizia ed al suo termine, aspetta un nuovo messaggio MIDI prima di ricominciare
A questo punto il secondo byte (msgIn.data2) del messaggio MIDI, che nel caso del messaggio note on/off rappresenta la nota suonata (vedi i link riguardanti il protocollo MIDI elencati nei PREREQUISITI), viene assegnato alla variabile midiNote.
Questa viene successivamente convertita in frequenza in hertz e utilizzata per controllare l’oscillatore.
msgIn.data2 => midiNote; Std.mtof(midiNote) => fr;   // conversione da MIDI a Freq in Hz sine.freq(fr);  // assegnare la frequenza all'oscillatore
Di seguito nel codice viene utilizzata una nuova struttura di controllo: if/else
if(msgIn.data3 != 0)        {            env.duration(attackTime);            env.keyOn(1);                        <<< "KEY PRESSED" >>>;        }         else        {            env.duration(releaseTime);            env.keyOff(1);                        <<< "KEY RELEASED" >>>;                  }
Questa struttura non fa altro che valutare il codice contenuto nelle parentesi tonde () che seguono if, nel caso specifico msgIn.data3 != 0 e nel caso questo sia vero eseguire il codice contenuto tra le parentesi graffe {}, altrimenti nel caso in cui sia falso, ignorare il codice tra le parentesi graffe {} e proseguire.
Il costrutto else, che puo’ essere solo utilizzato in combinazione con if, offre un’alternativa. Quando  msgIn.data3 != 0 e’ falso, viene eseguito il codice contenuto nelle parentesi graffe {} che seguono else.
Il codice msgIn.data3 != 0 non fa altro che valutare se il terzo byte (msgIn.data3) contenuto nel messaggio MIDI ricevuto e’ diverso da 0. Il terzo byte, nel caso di un messaggio MIDI note on/off, rappresenta la velocity.
Dunque questo pezzo di codice fa si che quando la velocity e’ diversa da zero (cioe’ quando si preme un tasto della tastiera), l’inviluppo che controlla il volume dell’oscillatore venga attivato in un tempo pari ad attackTime. Mentre quando la velocity e’ uguale a 0 (al rilascio della nota della tastiera), l’inviluppo viene rilasciato ed il volume dell’oscillatore torna ad essere 0 in un tempo pari a releaseTime.
Una volta eseguito questa parte di codice il loop riparte da
msgIn.data2 => midiNote;
e dunque il programma rimane in attesa di un nuovo evento, messaggio MIDI.
Per ascoltare il risultato di questo programma basta eseguirlo con un device MIDI connesso al computer, e generare messaggi MIDI note on/off (di solito generati da controller a tastiera, ma non solo).
Va ricordato che il nome del device in uso va specificato nella prima parte del programma:
mIn.open(”nome del device”)
oppure il suo numero del device, per esempio:
mIn.open(2)
RIEPILOGO
"MidiIn" e "MidiMsg" per controllare l'oscillatore con un device MIDI
 struttura di controllo "if/else"
Il codice ed il testo possono essere scaricati qui: https://bitbucket.org/mariobuoninfante/chuck_workshop/src
0 notes
mbuoninfante · 7 years ago
Text
MonoSynth in ChucK - pt.3
Parte 3
SCOPO
Aggiunta di un inviluppo alla catena audio utilizzata fino ad ora.
ANALISI DEL PROGRAMMA
Come di consueto il programma inizia con la dichiarazione degli UGen e delle variabili utilizzate:
// oscillatore sinusoidale collegato ad un inviluppo SinOsc sine => Envelope env => dac; // variabili float fr; float amp; int midiNote; dur t;
In questo caso si ha un oscillatore sinusoidale collegato ad un UGen Envelope la cui uscita e’ collegata al dac.
Envelope e’ un generatore di inviluppo (Wikipedia - Generatore di inviluppo) di tipo AR, cioe’ ha solo due “stati”, attack e release. In pratica si possono impostare indipendentemente il tempo di attacco (tempo che intercorre tra volume 0 e volume massimo) e di rilascio (tempo che intercorre tra volume massimo e volume 0) che influenzaranno il segnale generato dall’oscillatore sinusoidale (env controlla il volume dell’oscillatore).
A seguire vengono inizializzate le variabili ed alcune di esse vengono passate come argomenti alle apposite funzioni:
0.2 => amp; sine.gain(amp); // impostare l'ampiezza di "sine" su "amp", cioe' 0.2 100::ms => dur attackTime;  // tempo di attacco 990::ms => dur releaseTime; // tempo di rilascio
A questo punto il while loop che contiene il codice seguente:
while(true) {    Math.random2(48, 72) => midiNote;    Std.mtof(midiNote) => fr;      sine.freq(fr);        env.duration(attackTime);    env.keyOn(1);      <<< "KEY PRESSED" >>>;    attackTime => now;    env.duration(releaseTime);        env.keyOff(1);        <<< "KEY RELEASED" >>>;        releaseTime => now; }
Come prima cosa si genera una nota MIDI random utilizzando la funzione random2 parte della libreria Math, questa viene poi convertita in frequenza in hertz e passata all’oscillatore sinusoidale.
Viene poi impostato il parametro duration dell’inviluppo env. Questo permette di impostare la quantita’ di tempo che l’inviluppo impiega per passare da uno stato A ad uno stato B (ie. da 0 ad 1,  da 1 a 0).
Nel caso di questo programma, viene passata la variabile di tipo dur, attackTime che corrisponde a 100 millisecondi.
Successivamente l’inviluppo viene triggerato utilizzando il metodo keyOn che, come il nome suggerisce, equivale all’attivazione dell’inviluppo (passaggio da 0, volume minimo, ad 1, volume massimo – equivale a premere un tasto di una tastiera).
Questo metodo necessita di un argomento che sia un numero intero. Nel caso in cui l’argomento sia 1, l’inviluppo passa da 0 ad 1, mentre nel caso l’argomento sia 0, l’inviluppo passa da 1 a 0.
A seguire la variabile attackTime, che rappresenta il tempo di attacco, viene utilizzata come tempo di attesa prima di proseguire con il programma. Questo delay serve a dare all’inviluppo il tempo di raggiungere il volume massimo prima di eseguire una qualsiasi altra operazione.
Una volta che env completa il passaggio di stato da silenzio a volume massimo, viene in pratica ripetuto il codice precedente con l’unica differenza che al posto di attackTime, viene utilizzata la variabile releaseTime (990 millisecondi), che rappresenta il tempo di rilascio. In piu’ per far si che l’inviluppo env passi da 1 a 0, viene utilizzato il metodo keyOff (che equivale a rilasciare un tasto di una tastiera) che, come keyOn, necessita di un argomento che sia un numero intero.
keyOff e’ l’esatto opposto di keyOn, infatti necessita di un argomento pari ad 1 per passare dal volume massimo al volume minimo, e di un argomento pari a 0 per passare dal volume minimo al volume massimo.
Infine releaseTime viene utilizzata come delay, tempo di attesa, per permettere ad env di raggiungere il volume minimo prima di eseguire una qualsiasi altra operazione.
RIEPILOGO
UGen: Envelope (inviluppo)
triggerare l'inviluppo
utilizzare piu' volte "now" in un solo loop
Il codice ed il testo possono essere scaricati qui: https://bitbucket.org/mariobuoninfante/chuck_workshop/src
0 notes