3.2 Klangsynthese

Synthesizer Moog 55. Quelle: Wikipedia Commons.
Neben Funktionen für das Abspielen von Klangdateien stellt die Sound-Library von Processing auch Funktionen für die Synthese von Klängen bereit. Weiterführende Informationen finden sich im Netz unter https://processing.org/tutorials/sound/ sowie in Roederer und Raffaseder (S. 223-247)
1 Oszillatoren
Die Grundlegende Art, Klänge zu erzeugen, sind Oszillatoren. Ein Oszillator erzeugt eine Schwingung, z.B. eine Sinus-Schwingung. Daneben gibt es etwa Sägezahn- Rechteck-, Dreieckschwingungen, die jeweils unterschiedliche Klangeigenschaften aufweisen.
Zunächst wird die sound-Library importiert und ein Oszillator-Objekt erzeugt:
import processing.sound.*;
SinOsc sino = new SinOsc(this);
Die Amplitude (Lautstärke) eines Oszillators wird durch amp() festgelegt. Die maximale Lautstärke wird durch einen Wert von 1 eingestellt, 0 bedeutet Stille.
sino.amp(0.5);
Die Frequenz, mit welche der Oszillator schwingt, wird mit freq() definiert und in Hertz angegeben:
sino.freq(220);
Ein Oszillator lässt sich mit play() starten (solange dies nicht geschieht ist er nicht zu hören):
sino.play();
Analog zum Sinus-Oszillator gibt es die Objekte SawOsc (Sägezahnschwingung) SqrOsc (Rechteckschwingung) TriOsc (Dreickschwingung) und Puls (Pulsgenerator) weitere für die anderen Schwingungstypen.
Im folgenden Beispiel SoundDrawwird das bereits bekannte Zeichen-Beispiel um KLanglichkeit erweitert. So entsteht eine audiovisuelle Verbindung, in diesem Fall eine Kongruenz: je weiter sich eine zeichnerische Geste in der y-Position nach oben bewegt, desto höher die Frequenz (und damit die Tonhöhe) des Klangs. So entstehen kurze audiovisuelle Gesten.
Beispiel 3-3: SoundDraw

import processing.sound.*;
SinOsc sino;
void setup(){
size(800,600);
background(255);
sino = new SinOsc(this);
fill(255,150);
}
void draw(){
}
void mousePressed(){
sino.freq(height-mouseY);
sino.play();
}
void mouseDragged(){
stroke(0,150);
line(mouseX, mouseY, pmouseX, pmouseY);
sino.freq(height-mouseY+100);
}
void mouseReleased(){
sino.stop();
noStroke();
rect(0,0,width, height);
}
Befehl | Funktion |
---|---|
play() | Generator starten |
set() | Mehrere Parameter gleichzeitig einstellen |
freq() | Schwingungsfrequenz einstellen |
amp() | Lautstärke des Generators einstellen (0 bis 1.0) |
add() | um andere Audiosignale zu modulieren |
pan() | Ausgabe im Stereopanorama bewegen (-1.0 bis 1.0) |
stop() | Generator stoppen |
2. Rauschen
Weißes Rauschen
WhiteNoise noise;
noise = new WhiteNoise(this);
noise.play();
Rosa Rauschen
Die Amplitude von Rosa Rauschen nimmmt im Vergleich zum gleichverteilten Weißen Rauschen um 3dB pro Oktave ab.
PinkNoise noise;
noise = new PinkNoise(this);
noise.play();
Brownsches Rauschen
Die Amplitude von Brownschem Rauschen nimmmt im Vergleich zum gleichverteilten Weißen Rauschen um 6dB pro Oktave ab.
BrownNoise noise;
noise = new BrownNoise(this);
noise.play();
Alle drei Rauschgeneratoren verfügen über die folgenden Funktionen:
Befehl | Funktion |
---|---|
play() | Generator starten |
set() | Mehrere Parameter gleichzeitig einstellen |
amp() | Lautstärke des Generators einstellen (0 bis 1.0) |
add() | um andere Audiosignale zu modulieren |
pan() | Ausgabe im Stereopanorama bewegen (-1.0 bis 1.0) |
stop() | Generator stoppen |
In unserem Beispiel ›SoundDraw‹ lässt sich nun die Verbindung von Bewegung etwas komplexer gestalten, indem die Lautstärke eines Rausgenerators mit der Bewegung der Maus auf der y-Achse gekoppelt wird. Je näher die Maus am oberen Bildschirmrand ist, desto mehr Rauschanteil wird dem Klang beigemengt. Gleichzeitig verändert sich die visuelle Repsäsentation: Die eindeutige Linie reisst analog zum Rauschanteil aus und stellt eine audiovisuelle Analogie her.
Beispiel 3-4: SoundDraw + Dimension Noise

import processing.sound.*;
SinOsc sino;
BrownNoise noise;
void setup(){
size(800,600);
frameRate(120);
background(255);
sino = new SinOsc(this);
noise = new BrownNoise(this);
fill(255,150);
}
void draw(){
}
void mousePressed(){
sino.freq(height-mouseY);
sino.play();
noise.play();
noise.amp(0);
}
void mouseDragged(){
stroke(0,150);
fill(0);
sino.freq(height-mouseY+200);
float noiseL = map(height-mouseY, height, 0, 0.1, 0);
noise.amp(noiseL);
strokeWeight(noiseL*40);
line(mouseX+(randomGaussian()*noiseL*400), mouseY+(randomGaussian()*noiseL*400), pmouseX, pmouseY);
}
void mouseReleased(){
sino.stop();
noise.stop();
noStroke();
fill(255);
rect(0,0,width, height);
}
3. Filter
LowPass
Zunächst muss wieder ein entsprechendes Objekt deklariert werden:
LowPass lowPass;
Dann wird dieses initialisiert:
lowPass = new LowPass(this);
Dann wird der Filter gestartet:
lowPass.process(noise); // "noise" stellt das Signal dar, auf das der Filter
Um die cut off - Frequenz einzustellen wird die Funktion freq() verwendet:
lowPass.freq(880); // cut off - Frequenz bei 880Hz
Neben dem LowPass-Filter bietet Processing einen BandPass und einen HighPass-Filter.
In unserem kleinen Beispielprogramm SoundDraw lässt sich nun eine weitere Klangdimension einführen, indem die x-Position der Maus mit der Steuerung eines Bandpass-Filters gekoppelt wird. Die durch den Filter gesteuerten höheren und tieferen Klanganteile werden visuell durch die Farbigkeit der visuellen Elemente aufgenommen.
Beispiel 3-5: SoundDraw + Dimension Noise + Dimension Bandpass

import processing.sound.*;
SinOsc sino;
WhiteNoise noise;
BandPass bandPass;
int cHue;
color c;
void setup(){
size(800,600);
frameRate(120);
background(0);
sino = new SinOsc(this);
noise = new WhiteNoise(this);
bandPass = new BandPass(this);
fill(255,150);
colorMode(HSB);
}
void draw(){
}
void mousePressed(){
sino.freq(height-mouseY);
sino.play();
noise.play();
sino.amp(0.2);
noise.amp(0);
bandPass.process(noise, 100, 50);
cHue = round(random(255));
c = color(cHue, 200, 100);
}
void mouseDragged(){
fill(c, 20);
noStroke();
rect(0,0,width, height);
sino.freq(height-mouseY+200);
float noiseL = map(height-mouseY, height, 0, 0.1, 0);
noise.amp(noiseL);
bandPass.set(mouseX*10, mouseX/2);
strokeWeight(noiseL*80);
strokeWeight(4);
//stroke(0,mouseX/2);
float bright = map(mouseX, 0, width, 100, 255);
stroke(random(cHue-mouseX/10, cHue+mouseX/10), 255, bright, 255);
line(mouseX+(randomGaussian()*noiseL*400), mouseY+(randomGaussian()*noiseL*400), pmouseX, pmouseY);
}
void mouseReleased(){
bandPass.stop();
sino.stop();
noise.stop();
noStroke();
fill(0);
rect(0,0,width, height);
}
Wie lässt sich das Beispiel SoundDraw mit Object-Tracking aus dem Kapitel Computer Vision kombinieren?
4. Hüllkurven

Hüllkurve mit den vier Phasen Attack, Decay, Sustain, Release. Quelle: Wikipedia Commons
Env hk;
env = new Env(this);
env.play(triOsc, attackTime, sustainTime, sustainLevel, releaseTime);
Pointilistisches Klangbeispiel, in dem kein Klangkontinuum, sondern Klangwolken entstehen. Diese Wolken sind durch Mausinteraktion und Zufallsfunktionen gesteuert.
Beispiel 3-6
coming

Steinberg Retrologue virtueller analog VST-Synthesizer
5. Einfacher Synthesizer
Mit dem erreichten Wissen lässt sich nun ein einfacher Synthesizer mit 3 Klangerzeugern und Hüllkurvengenerator programmieren.
Wie ließe sich die Lautstärke der einzelnen Oszillatoren zueinander einstellen? Wie ließen sich die drei Filtertypen im Display-Feld darstellen?
Beispiel 3-7

import controlP5.*;
import processing.sound.*;
ControlP5 cp5;
int count;
int Duration = 1000;
boolean sinoPlay = false;
boolean sawoPlay = false;
boolean squaroPlay = false;
boolean delayDo = false;
boolean envelopeDo = false;
float FreqSin, FreqSaw, FreqRect;
float attack, sustain, susLevel, release;
SinOsc sino;
SawOsc sawo;
SqrOsc squaro;
WhiteNoise noise;
BandPass bandPass;
Env envelope;
Reverb reverb;
void setup() {
size(500,200);
background(0);
sino = new SinOsc(this);
sawo = new SawOsc(this);
squaro = new SqrOsc(this);
noise = new WhiteNoise(this);
bandPass = new BandPass(this);
envelope = new Env(this);
reverb = new Reverb(this);
cp5 = new ControlP5(this);
cp5.addBang("bang")
.setPosition(10, 10)
.setSize(40, 20)
.setTriggerEvent(Bang.RELEASE)
.setLabel("Play")
;
cp5.addSlider("Duration")
.setPosition(100,10)
.setSize(100, 20)
.setRange(0, 2000) // values can range from big to small as well
.setValue(1000)
.setNumberOfTickMarks(5)
.setSliderMode(Slider.FLEXIBLE)
;
// Oszillatoren
cp5.addToggle("Sinus")
.setPosition(310,10)
.setSize(40,20)
.setValue(true)
;
cp5.addToggle("Saegezahn")
.setPosition(360,10)
.setSize(40,20)
.setValue(false)
;
cp5.addToggle("Rechteck")
.setPosition(410,10)
.setSize(40,20)
.setValue(false)
;
cp5.addSlider("FreqSin")
.setPosition(310,50)
.setSize(40, 20)
.setRange(16, 2000)
.setValue(440)
//.setSliderMode(Slider.FLEXIBLE)
;
cp5.getController("FreqSin").getCaptionLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
cp5.addSlider("FreqSaw")
.setPosition(360,50)
.setSize(40, 20)
.setRange(16, 2000)
.setValue(440)
.setSliderMode(Slider.FLEXIBLE)
;
cp5.getController("FreqSaw").getCaptionLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
cp5.addSlider("FreqRect")
.setPosition(410,50)
.setSize(40, 20)
.setRange(16, 2000)
.setValue(440)
.setSliderMode(Slider.FLEXIBLE)
;
cp5.getController("FreqRect").getCaptionLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
//Envelope
cp5.addToggle("envelopeOn")
.setPosition(10,160)
.setSize(40,20)
.setValue(false)
.setLabel("Env on");
;
cp5.addSlider("attack")
.setPosition(60,160)
.setSize(40, 20)
.setRange(0.01, 0.5)
.setValue(0.1)
.setSliderMode(Slider.FLEXIBLE)
;
cp5.getController("attack").getCaptionLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
cp5.addSlider("sustain")
.setPosition(110,160)
.setSize(40, 20)
.setRange(0.01, 0.5) // values can range from big to small as well
.setValue(0.2)
.setSliderMode(Slider.FLEXIBLE)
;
cp5.getController("sustain").getCaptionLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
cp5.addSlider("susLevel")
.setPosition(160,160)
.setSize(40, 20)
.setRange(0, 1) // values can range from big to small as well
.setValue(0.5)
.setSliderMode(Slider.FLEXIBLE)
;
cp5.getController("susLevel").getCaptionLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
cp5.addSlider("release")
.setPosition(210,160)
.setSize(40, 20)
.setRange(0.01, 0.5) // values can range from big to small as well
.setValue(0.1)
.setSliderMode(Slider.FLEXIBLE)
;
cp5.getController("release").getCaptionLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
//Delay
/*cp5.addToggle("delayOn")
.setPosition(10,410)
.setSize(40,20)
.setValue(false)
;*/
}
void draw() {
fill(20,20,50);
noStroke();
// Zeichnen des Hüllkurven-Diagramms
rect(10,50,280,100);
stroke(255);
float sp = 150-susLevel*100;
line(10,150, attack*200+10, sp);
line(attack*200+10,sp, attack*200+sustain*200+10,sp);
line(attack*200+sustain*200+10, sp, attack*200+sustain*200+release*200+10,149);
// Ton beenden
if (millis() > count+Duration && envelopeDo == false){
if (sinoPlay){ sino.stop(); }
if (sawoPlay){ sawo.stop(); }
if (squaroPlay){ squaro.stop(); }
}
}
void bang() {
if (sinoPlay){
sino.stop();
sino.play(FreqSin, 0.5);
if (envelopeDo){
envelope.play(sino, attack, sustain, susLevel, release);
}
if (delayDo){
reverb.process(sino);
reverb.set(5, 0.8, 1);
}
}
if(sawoPlay){
sawo.stop();
sawo.play(FreqSaw, 0.5);
if (envelopeDo){
envelope.play(sawo, attack, sustain, susLevel, release);
}
}
if(squaroPlay){
squaro.stop();
squaro.play(FreqRect, 0.5);
if (envelopeDo){
envelope.play(squaro, attack, sustain, susLevel, release);
}
}
count = millis();
}
void envelopeOn(boolean flag){
if(flag){
envelopeDo = true;
}else{
envelopeDo = false;
}
}
void delayOn(boolean flag){
if(flag){
delayDo = true;
}else{
delayDo = false;
}
}
void Sinus(boolean flag){
if(flag){
sinoPlay = true;
}else{
sinoPlay = false;
}
}
void Saegezahn(boolean flag){
if(flag){
sawoPlay = true;
}else{
sawoPlay = false;
}
}
void Rechteck(boolean flag){
if(flag){
squaroPlay = true;
}else{
squaroPlay = false;
}
}
6. Simple Reactable
Möchte man das grundlegende Prinzip des Reaktables nachvollziehen, so lässt sich das Beispiel zur Erkennung von Markern aus dem Kapitel `Computer Vision nun um Klanggenaratoren erweitern.
Beispiel 3-7-2
import processing.video.*;
import processing.sound.*; // Processing Sound importieren
import boofcv.processing.*; // BoofCV importieren
import java.util.*; // Die Java Library util importieren
Capture cam; // Cam-Objekt deklarieren
SimpleFiducial detector; // Objekt für den Detector erkennen
SinOsc sino = new SinOsc(this); // Sinus Oszillator deklarieren
SawOsc sawo = new SawOsc(this); // Sinus Oszillator deklarieren
boolean sinoPlay = false;
boolean sawoPlay = false;
boolean sinoIsPlaying = false;
boolean sawoIsPlaying = false;
PVector coord;
void setup() {
size(1280, 720);
coord = new PVector();
//cam = new Capture(this, 640, 480);
cam = new Capture(this, 1280, 720, "Live! Cam Sync HD VF0770");
cam.start();
surface.setSize(cam.width, cam.height);
detector = Boof.fiducialSquareBinaryRobust(0.1);
detector.guessCrappyIntrinsic(cam.width,cam.height);
}
void draw() {
if (cam.available() == true) {
cam.read();
List found = detector.detect(cam);
println(found.size());
if (found.size() == 0){
sino.stop();
sinoIsPlaying = false;
sawo.stop();
sawoIsPlaying = false;
}
image(cam, 0, 0);
//background(20);
for( FiducialFound f : found ) {
detector.render(this,f);
float xRaw = (float)f.getFiducialToCamera().getTranslation().getX();
float yRaw = (float)f.getFiducialToCamera().getTranslation().getY();
float zRaw = (float)f.getFiducialToCamera().getTranslation().getZ();
float xs = map(xRaw, -0.37*zRaw, 0.37*zRaw, 0, width);
float ys = map(yRaw, -0.27*zRaw, 0.27*zRaw, 0, height);
float zs = zRaw;
sinoPlay = false;
if (f.getId() == 284){
fill(255,0,0);
if (sinoIsPlaying == false){
sinoIsPlaying = true;
sino.freq(700);
sino.play();
}
}
if (f.getId() == 643){
fill(255);
if (sawoIsPlaying == false){
sawoIsPlaying = true;
sawo.freq(700);
sawo.play();
}
}
noStroke();
ellipse(xs, ys, 40,40);
stroke(255);
strokeWeight(3);
line(xs, ys, width/2, height/2);
//sino.amp(map(ys, 0, height, 0,1));
}
}
}