P5 und P5.sound - Einfache Klangsynthese



Zunächst werden im <head> der Seite die beiden Scripte für P5 und P5.sound eingebunden:

<script src="header/p5.js"></script>
<script src="header/p5.sound.js"></script>

Dann wird im <body> der Seite ein Container mit einer eindeutigen id mithilfe von DIV-Tags angelegt, in dem alles angezeigt werden soll. Dies geschieht via

<DIV id="p5container"></DIV>

über Style-Eigenschaften kann man diesen noch weiter im Aussehen verändern, z.B.

<DIV id="p5container" style="width:800;border: 1px solid #333;box-shadow: 8px 8px 5px #444;padding: 8px 12px;background-color:ffffff"></DIV>

Darunter wird mit Hilfe von <select>...</select> eine Auswahl mit der id "auswahl" angelegt, in der man zwischen verschiedenen Wellenformen für die Klangsynthese wählen kann. Über den Befehl onchange kann die Funktion wechselwelle(); aufgerufen werden (s.u.), der mit Hilfe von this.value die jeweils ausgewählte Wellenform übergeben wird:

<select name="auswahl" id="auswahl" onchange="wechselwelle(this.value);">
<option value="sine">Sinus</option>
<option value="triangle">Triangle</option>
<option value="sawtooth">Sawtooth</option>
<option value="square">Square</option>
</select>

Bevor im <script>...</script>-Bereich mit dem eigentlichen Sript angefangen wird, werden zwei Arrays (Listen) mit MIDI-Notenwerten und den dazugehörenden Tonhöhennamen definiert:

var midinoten = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72]; // Liste mit den MIDI-Notenwerten
var tonhoehen = ['C3','C#3', 'D3', 'D#3','E3','F3','F#3', 'G3', 'G#3', 'A3', 'Bb3', 'B3','C4','C#4', 'D4', 'D#4','E4','F4','F#4', 'G4', 'G#4', 'B4', 'Bb4', 'B4','C5']; // Liste mit den dazugehörenden Tonhöhennamen

Dann werden in der Funktion setup() die Grundkomponenten festgelegt:

function setup(){
var container = createCanvas(800,400); // Canvas erstellen
container.parent('p5container'); // an DIV-Container anhängen
container.mousePressed(playSound); // rufe bei einer gedrückten Maus die Funktion
// playSound() auf (s.u.)

container.mouseReleased(stopSound); // rufe bei einer losgelassenen Maus die Funktion
// stopSound() auf (s.u.)

osc = new p5.Oscillator('sine'); // Erstelle einen Oszillator mit einer Sinus-Welle als Wellenform und weise ihn der Variablen osc zu.
env = new p5.Envelope(); // erstelle eine Hüllkurve für den Pegel des Oszillators und weise sie der Variablen env zu.
env.ramp(osc, 0, 0.5, 0); // gib der Hüllkurve env eine Rampenfunktion, die sich auf den Oszillator bezieht und gib dieser eine auf- (0 bis 0,5) und eine absteigende Kurve (0,5 bis 0).
osc.freq(440); // stelle die Frequenz des Oszillators auf 440 Hz ein
osc.amp(0) // stelle den Pegel des Oszillators auf 0 ein
fft = new p5.FFT();
// Fouriertransformation des Signals ermöglichen
}

Daraufhin wird in der Funktion draw() beschrieben, was im Canvas dargestellt werden soll:

function draw(){
background(255, 255, 255); //zeichne weißen Hintergrund

cursor(HAND); // zeige den Cursor als Hand

// zunächst werden Trennlinien für die einzelnen Tonhöhenbereiche gezeichnet:

faktor=round(width/midinoten.length); // Über die Weite des Canvas geteilt durch die
// Anzahl der Töne im Array midinoten wird der Abstand der Trennlinien ermittelt und der Variblen
// faktor zugeordnet.

for (var i = 0; i < midinoten.length; i++){ // hole in einer Schleife in der Länge
// des Arrays midinoten jeden einzelnen Wert i aus dem Array

line(i*faktor, 0, i*faktor, height); // zeichne für jeden Wert eine Linie von oben
// nach unten in einem Abstand von i multipliziert mit dem vorher ermittelten Faktor für den
// Abstand

stroke(225,225,225); // gib der Linie die Strichfarbe hellgrau.
}

// dann wird die Tonhöhe aus der Mausposition über den Trennlinien ermittelt:

ton = round(map(mouseX, width, 0, midinoten.length, 0)); // skaliere den Wert
// der X-Koordinaten der Maus, der zwischen der Weite des Canvas und 0 liegt, so, dass er zwischen
// der Anzahl der verfügbaren MIDI-Notenwerte und 0 liegt, runde diesen zu einer ganzen Zahl und
// weise ihn der Variablen ton zu.

note = round(midiToFreq(midinoten[ton])); // nimm die Variable ton als index
// (= Positionsanzeiger) für das Array midinoten und ermittle via midiToFreq() die Frequenz des
// gewählten Tons und gib den auf ganze Zahlen gerundeten Wert der Frequenz in die Variable note.

osc.freq(note); // stelle über die Variable note die Frequenz für den Oszillator ein.
notenname = tonhoehen[ton]; // nimm die Variable ton als index (= Positionsanzeiger) für
// das Array tonhoehen und gebe die darüber gefundene Tonhöhenbezeichnung in die Variable
// notenname.

dynamik = map(mouseY, height, 0, 0, 1); // skaliere den Wert der Y-Koordinaten der
// Maus, der zwischen der Höhe des Canvas und 0 liegt, so, dass er zwischen 0 und 1 liegt und weise
// ihn der Variablen dynamik zu.

osc.amp(dynamik); // stelle die Amplitude (amp) des Oszillators mit der Variablen dynamik
// ein.

fill(188, 48, 47, 255); // Dunkelrot als Füllfarbe
text('Tonhöhe: ' + notenname + ' ('+note+' Hz)', 20, 20); // gebe die Tonhöhe
// mit Notenname und Frequenz aus.

text('Dynamik: ' + dynamik, 20, 40); // gebe den Pegel aus.

// Schließlich wird der jeweilige Pegel mit der Größe der Kreisfläche ausgegeben

var waveform = fft.waveform(); // hole aus dem Klang alle Pegelwerte und schreibe sie
// ins Array waveform

for (var i = 0; i < waveform.length; i++){ // hole in einer Schleife in der Länge
// des Arrays waveform jeden einzelnen Wert i aus dem Array

var y = map( waveform[i], -1, 1, 0, height); // skaliere jeden Wert aus dem
// Array waveform, der zwischen -1 und 1 liegt, so, dass er zwischen 0 und der Höhe des Canvas
// dargestellt wird, und weise ihn dem Wert y zu (Durchmesser des Kreises)

noStroke(); // keine Strichfarbe = keinen Rand für den Kreis
fill(188, 48, 47); // Dunkelrot als Füllfarbe

ellipse(mouseX, mouseY, y-190, y-190);// Zeichne einen Kreis an der Mausposition
// (x,y) und lasse seinen Durchmesser vom ermittelten Pegel (y) bestimmen.

}
}

Schließlich wird mit der Funktion playSound() und stopSound() beschrieben, was passieren soll, wenn die Maus auf dem Canvas gedrückt oder losgelassen wird:

function playSound() {
osc.start(); // Starte den Oszillator
}
function stopSound() {
osc.stop(); // Stoppe den Oszillator
}

Mit den Audio-Bausteinen von P5.sound lässt sich ein kompletter Synthesizer auf Javascript-Basis erstellen.

Insgesamt sieht das Script dann folgendermaßen aus:

<script src="header/p5.js"></script>
<script src="header/p5.sound.js"></script>

<script>
var midinoten = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72];
var tonhoehen = ['C3','C#3', 'D3', 'D#3','E3','F3','F#3', 'G3', 'G#3', 'A3', 'Bb3', 'B3','C4','C#4', 'D4', 'D#4','E4','F4','F#4', 'G4', 'G#4', 'B4', 'Bb4', 'B4','C5'];
var osc;

function setup(){
var container = createCanvas(800,400);
container.parent('p5container');
container.mousePressed(playSound);
container.mouseReleased(stopSound);

osc = new p5.Oscillator('sine');
env = new p5.Envelope();
env.ramp(osc, 0, 0.5, 0);
osc.freq(440);
osc.amp(0)
fft = new p5.FFT();
}

function wechselwelle(id){
osc.setType(id);
}

function draw(){
background(255, 255, 255);
cursor(HAND);

faktor=round(width/midinoten.length);
for (var i = 0; i < midinoten.length; i++){
line(i*faktor, 0, i*faktor, height);
stroke(225,225,225);
}

ton = round(map(mouseX, width, 0, midinoten.length, 0));
note = round(midiToFreq(midinoten[ton]));
osc.freq(note);
notenname = tonhoehen[ton];
dynamik = map(mouseY, height, 0, 0, 1);
osc.amp(dynamik);
fill(188, 48, 47, 255);
text('Tonhöhe: ' + notenname + ' ('+note+' Hz)', 20, 20);
text('Dynamik: ' + dynamik, 20, 40);

var waveform = fft.waveform();
for (var i = 0; i < waveform.length; i++){
var y = map( waveform[i], -1, 1, 0, height);
noStroke();
fill(188, 48, 47);
ellipse(mouseX, mouseY, y-190, y-190);
}
}

function playSound() {
osc.start();
}
function stopSound() {
osc.stop();
}

</s
cript>

<DIV id="p5container" style="width:800;border: 1px solid #333;box-shadow: 8px 8px 5px #444;padding: 8px 12px;background-color:ffffff"></DIV>