Programmiersprachen für Audio

Proseminar Medienverarbeitung I - Audio, WS.99/00, Stefan Reich, Oliver Leistert

 

0.1

Inhalt

Teil I: Audiobibliotheken für herkömmliche Programmiersprachen

Teil II: Spezielle Audio-Programmiersprachen

Anhang: URLs

 

0.2

Vorbemerkung

Das Thema "Programmiersprachen für Musikanwendungen" ist weit gefächert. Nahezu jede gängige Programmiersprachen erlaubt es unter Verwendung entsprechender Bibliotheken, Musikanwendungen zu erstellen. Je nachdem, welche Kriterien angesetzt werden - einfache Entwicklung, gute Ausnutzung der vorhandenen Rechenleistung, große Auswahl an vorgefertigte Klang-Algorithmen oder anderes - empfehlen sich verschiedene Sprachen und Bibliotheken.

Die auf Klanganalyse oder -erzeugung spezialisierten Sprachen sind meist ein integriertes System: Compiler/Interpreter, Bibliotheken und u.U. Entwicklungsumgebung gehören zusammen. Man besitzt mit einem solchen System theoretisch also alles, was nötig ist, um Musiksoftware zu entwickeln.

Es stellt sich die Frage, was der Vorteil einer weiteren neu entwickelten Programmiersprache sei. Schließlich gibt es viele weit verbreitete universelle Sprachen. Zudem zeichnen sich die Musik-Programmiersprachen, die uns untergekommen sind, kaum durch Innovation aus. Sie sind fast immer nahe Abkömmlinge gängiger Hochsprachen der 3. Generation. Teilweise kombinieren sie bestehende Sprachen. So enthält der Sprachkern von Supercollider C- und Smalltalk-Elemente - und nichts Substanzielles darüber hinaus. Uns scheint, dass der Entwickler eines Sound-Framework sich viel Arbeit sparen könnte, würde er bei dem Sprachkern auf Vorhandenes zurückgreifen. Wer den Framework nutzt, könnte sich mit geringerem Lernaufwand einarbeiten. Und der Anwender einer damit erstellten Software schliesslich profitierte von gründlich getesteten und oft hoch optimierten Ausführungsumgebungen.

Wir werden in diesem Referat auf einige Beispiele eingehen. Die Zusammenstellung ist in keiner Weise repräsentativ; eine positive Charakterisierung könnte lauten, dass es sich um besonders interessante und vielleicht weniger bekannte Technologien handelt.

I.0

Audiobibliotheken für herkömmliche Programmiersprachen

Unterscheidung Algorithmen/Hardware-Schnittstellen

Man kann eine grundsätzliche Unterscheidung vornehmen:
  • Algorithmen zur Klanganalyse, -synthese, -manipulation

    Beispiel: PVC (ein Vocoder-Toolkit, das auch viele Filteralgorithmen enthält)

  • Schnittstellen zur Audio-Hardware

    Beispiele: JavaSound, Linux/Windows/BeOS APIs

Bei der Ansteuerung von Audio-Hardware besteht heutzutage die unterste Ebene i.a. aus einem ins Betriebssystem eingebundenen Treiber. Dadurch können Anwendungen geräteunabhängig entwickelt werden. Teilweise werden noch APIs dazwischengeschaltet, die weitere Vorteile bieten, etwa betriebssystemunabhängig sind (Beispiel JavaSound).

I.1

JavaSound

JavaSound bietet ein relativ leicht verständliches, trotzdem aber mächtiges API. Es sind Implementierungen dieses JavaSound-API für alle Plattformen denkbar; momentan liegen nur Implementierungen der Firma Sun selbst vor; diese beschränken sich auf Windows und Solaris.

Die neuesten Releases tragen noch Versionsnummern, die mit "0." beginnen. Dies macht sich deutlich bemerkbar. Die Namen der Klassen und Methoden ändern sich zwischen zwei Releases teilweise dramatisch. Man gewinnt den Eindruck, dass die Konzeptionierungs-Phase übersprungen wurde. Die Entwickler scheinen drauflos zu programmieren, in der Hoffnung, dass ein Konzept nebenbei entsteht. Auch die aktuelle Implementierung ist alles andere als praxistauglich. Vor allem gravierende Timingprobleme verhindern einen produktiven Einsatz.

Wer JavaSound dennoch testen möchte, kann eine Vorversion des Java Development Kit 1.3 herunterladen, in das JavaSound integriert ist. Ausserdem ist JavaSound Teil des Java Media Framework, der sich auf das JDK 1.2 installieren lässt.

Die Idee ist durchaus vielversprechend: eine plattformunabhängige Sound-Bibliothek, flexibel und trotzdem unkompliziert einsetzbar. Soll Ton in Echtzeit ausgegeben werden, hält die Sprache Java selbst allerdings weitere Hindernisse bereit: Die Java-Spezifikation berücksichtigt keinerlei Echtzeit-Anforderungen. Vor allem der Garbage Collector, den jede Java Virtual Machine enthält, kann den Programmablauf teilweise für Sekunden einfrieren. Der Entwurf eines "Realtime Java" ist aber bereits unterwegs, vielleicht stellt dies eine Lösung dar.

I.2

BeOS Media API

Das alternative Betriebssystem BeOS wirbt mit besonderer Eignung für Audio- und Video-Verarbeitung. Es soll auf allen Ebenen für geringe Latenz und hohen Datendurchsatz ausgelegt sein.

BeOS läuft auf handelsüblichen PCs und Macs, wobei vieles daraufhindeutet, dass die Mac-Variante nicht mehr lange weiter entwickelt wird. Die neueste Version, BeOS 5, ist für den nicht-kommerziellen Einsatz kostenlos erhältlich.

Für unser Thema ist das BeOS Media API einen Blick wert. Es unterscheidet sich von den Sound-APIs anderer Betriebssystem dadurch, dass es größtenteils objektorientiert angelegt ist. Dies ist sicher ein konzeptioneller Vorteil; zugleich verkompliziert es die Verwendung nicht-objektorientierter Programmiersprachen. Die BeOS-"Haussprache" ist C++; für andere Sprachen ist unseres Wissens noch keine Schnittstelle definiert.

Das Verarbeitungsmodell des Media APIs basiert auf sogenannten Nodes, die miteinander verbunden werden. Es gibt dabei Nodes, die nur Ausgänge besitzen (z.B. Aufnahme über Line In oder ein Softwaresynthesizer), Nodes mit Ein- und Ausgängen (z.B. Filter oder Sampleraten-Konverter) und Nodes nur mit Eingängen (z.B. Ausgabe auf den systemweiten Audio-Mixer).

Möchte man einen eigenen Node-Typ definieren, bildet man eine Unterklasse der allgemeinen Audio-Node-Klasse. Man hat dann ca. 2 Dutzend abstrakte Methoden zu implementieren - keine leichte Aufgabe. Unter anderem muss der Node seine eigene maximale Latenz angeben können und Parameter wie Samplerate und Anzahl der Kanäle mit den mit ihm verbundenen Nodes aushandeln.

 

II Spezielle Audio-Programmiersprachen

II.1

Csound

Die Geschichte von Csound beginnt in den früher 60er Jahren: Die Bell Labs entwickelten eine Musiksoftware namens „Music 4“, aus der später Csound hervorging. Csound ist heute frei erhältlich und kompatibel mit sehr vielen Plattformen.

In der Grundversion arbeitet es batch-orientiert. Als Eingabe erwartet es ein sog. Orchestra-File und ein Score-File. Es berechnet aus diesen Dateien den Audio-Datenstrom und leitet ihn in eine Datei. Wie die Namen andeuten, werden Partitur des Stücks und Beschreibung der Instrumente voneinander getrennt, was eine Neu-Instrumentierung u.U. erleichert.

Die Instrumentenbeschreibungen können auf zwei Arten gelesen werden; dies war ein Punkt, der uns die Einordnung von Csound zunächst schwer machte - ist es eine Programmiersprache oder nicht?

  • Zum einen nämlich funktioniert Csound wie ein Modularsynthesizer. Man "verdrahtet" vorgegebene Module miteinander - Oszillatoren, Filter, Hüllkurven - und "zapft" an einem dieser Module das Ausgangssignal ab.

  • Teilweise finden sich in den Orchestra-Files jedoch Befehle wie if und goto - welche Bedeutung haben diese in der Beschreibung einer Modultopologie? Zudem waren es keine Metaanweisungen, die im voraus hätten ausgeführt werden können; die Bedingungen der if-Statements hingen vielmehr von über die Zeit veränderlichen Werten ab.
Schließlich erkannten wir, dass die Vorstellung von miteinander verbundenen Modulen nur ein Hilfsmittel ist, um mit Orchestra-Files umzugehen. In Wahrheit ist eine Instrumentenbeschreibung nichts anderes als eine Prodezurbeschreibung. Eine Zeile wie
a1 oscil 10000, 440, p6

ist keine Instanziierung eines Oszillators, sondern meint den Aufruf der Unterroutine "Berechne ein Oszillator-Sample und lege es in a1 ab". Csound iteriert über so viele Samples, wie die Ausgabedatei enthalten soll, und führt jedesmal wieder die Instrumentenbeschreibung aus.

Die Syntax von Orchestra- und Score-File ist teils sehr kryptisch. Die Architektur ist softwaretechnisch "von gestern" - bei Csounds respektablem Alter nicht verwunderlich.

Dafür gibt es sehr viele Erweiterungen des Csound-Kernsystems, beispielsweise GUIs zur bequemen Erstellung von Orchestra- und Score-Files. Mit einer speziellen MIDI-Erweiterung ist es sogar möglich, Csound als Softsynth (also in Echtzeit!) zu betreiben.

Hier noch ein Beispiel eines Orchestra-Files, generiert von einer Csound-Erweiterung namens "Matt's Shoddy Vocoder":

sr=44100
kr=441
ksmps=100
instr 1
ain1 soundin "C:\USR\VB\Vocoder\alpha1\vox.wav"
ain2 soundin "C:\USR\VB\Vocoder\alpha1\pad.wav"
aout = 0
abp1 butterbp ain1,  120,  23
abp2 butterbp ain2,  120,  23
avoc balance abp2, abp1
aout = aout + (avoc *  0.5 )
abp1 butterbp ain1,  696,  100
abp2 butterbp ain2,  696,  100
avoc balance abp2, abp1
aout = aout + (avoc *  0.5 )
abp1 butterbp ain1,  1245,  200
abp2 butterbp ain2,  1245,  200
avoc balance abp2, abp1
aout = aout + (avoc *  0.5 )
abp1 butterbp ain1,  1869,  200
abp2 butterbp ain2,  1869,  200
avoc balance abp2, abp1
aout = aout + (avoc *  0.5 )
abp1 butterbp ain1,  6261,  1058
abp2 butterbp ain2,  6261,  1058
avoc balance abp2, abp1
aout = aout + (avoc *  0.5 )
abp1 butterbp ain1,  9429,  1576
abp2 butterbp ain2,  9429,  1576
avoc balance abp2, abp1
aout = aout + (avoc *  0.3 )
out aout
endin

Das zugehörige Score-File:

t 0 60
i1 0 2.655

II.2

KeyKit

KeyKit ist eine Sprache für MIDI-Anwendungen, die keine Soundsynthese ermöglicht. Sie läuft auf diversen Plattformen (u.a. Atari St und Amiga), Grundlage unseres Referates ist die Version für Windows 95. Entwickelt wurde und wird sie von Tim Thompson.

Von Haus aus bringt KeyKit bereits eine umfangreiche Library mit, die wir hier nicht diskutieren wollen. Für den Einsteiger stellt sie aber eine relativ zugängliche Möglichkeit dar, sich mit der Sprache vertraut zu machen.

KeyKit ist eine multi-tasking-fähige, echtzeit-fähige, objekt-orientierte Programmiersprache, die teilweise an Awk angelehnt ist, d.h.: keine Deklaration von Variablen; Funktionen mit Argumenten und Rückgabewerten beliebigen Typs.

Über die Konsole können KeyKit-Befehle durch einen Interpreter ausgeführt werden, z. B. die Einstellungen der Midi-Ports des Systems. Ebenso können neue Klassen in eine laufende Instanz von KeyKit eingebunden werden. Der Rest des KeyKit-Fensters ist maus-sensitiv: Über ein ausgefuchstes Click&Drop-Modell werden die einzelnen Tools aufgezogen, verbunden, aktiviert und verändert. Das sind die in der Library mitgelieferten Tools, wie auch selbst geschriebene, die in das Hauptmenü der Maus miteingebunden werden können. Beliebig viele Seiten des KeyKit-Fensters können instanziiert, gesichert und z.B. zur thematischen Aufteilung benutzt werden, etwa die Sequenzerseite zur globalen Steuerung, die Klaviaturseite zur Darstellung diverser Metaphern und die Effekt-Seite. Beim Herumspielen entstand bei uns rasch ein ziemliches Chaos auf dem Bildschirm:

      KeyKit-Ansicht

Daten-Typen

Die Daten-Typen von KeyKit sind Character-Strings, Integer, Floating-Points, Arrays, Funktionen und Objekte. Besonderes Interesse verdienen die Musik-Ausdrücke (phrases). Diese geordneten Noten-Folgen, die im Prinzip eine Umsetzung von Midi-Befehlen sind, haben Attribute (u.a. time, duration, type, pitch, channel, volume) und eigene Operatoren (+,/,-,&,% und {condition}). Das type-Attribut kann diverse Werte haben, so bewirkt z.B. PROGRAM eine program change message, wie sie durch das MIDI-Protokoll bekannt ist. Zeit und Dauer sind in Einheiten von clicks festgelegt, wobei die Relation von clicks zur Zeit bestimmt wird von tempo(), in Mikrosekunden pro Beat (Echtzeit), und dem Wert der Variablen Clicks (per default 96 pro Beat). Eine typische phrase wäre:

'bo2v60c7t96,e,f,xc005,e'%3

wobei hier die Notenfolge b,e,f,e in der 2. Oktave mit der Lautstärke 60 auf Kanal 7 zur Startzeit 96 ausgeben werden würde. Vor dem zweiten e würde ein program change stattfinden. Durch das %3 hört man aber nur das f, also den dritten Operanden.

Funktionen

Funktionen sind in der Library abgelegt und werden bei Bedarf geladen. Ihre Namen sind Variablen, deren Wert auf die Funktion zeigt. So zeigt bei

function minor (k) {return (k | transpose (k,3) | transpose (k,7))} )
function major (k) {return (k | transpose (k,4) | transpose (k,7))} )
function randchord() {
	if  (rand(2) == 0 )
		return (major)
	else
		return (minor)
}

der Rückgabewert von randchord() auf eine der beiden anderen Funtionen.

Variablen können gekapselt werden, um eine spezielle Verwendung sicherzustellen. "global" z.B. sichert den globalen Zugriff auf eine Variable bzw. eine Funktion.

FiFos, MIDI-IN, MIDI-OUT und Multi-Tasking

Mit Fifos (First-In-First-Out-Schlange) wird in KeyKit u.a. bei der Kommunikation mit Devices gearbeitet. Speziell gibt es sie für die Konsole, für Midi und für die Maus, aber auch um Files oder Pipes einzulesen. Ihr Wert ist oft ein Array.

Tasks können in großer Zahl gleichzeitig laufen, sie werden mit dem task-statement gestartet und durch die tid können sie identifiziert und mit kill() gestoppt werden. Sie sind eng gekoppelt an Fifos, denn die get()-Funktion der Fifos ist abgeschaltet, bis es "etwas für sie gibt". Mit wait() können einzelne Tasks abgewartet werden und sleeptill() wartet eine absolute Zeit lang.

MIDI-Output läuft in Echtzeit und wird in erster Linie durch Echtzeit-Variablen gesteuert. Current z.B. enthält an einem beliebigen Zeitpunkt alle gedrückten Noten dieses Zeitpunkts. MIDI-Input wird von der MIDI-Fifo eingelesen und kann dann vielfältig manipuliert werden.

Objekt-Orientierung und Beispiel

KeyKit ist auch objekt-orientiert, was vor allem die Programmierung neuer GUI-Elemnete erleichtert. Ein Handicap ist jedoch die fehlende Garbage-Collection, deren Effekt ein fehlerhafter Neuaufbau der einzelnen Fenster bedeuten kann.

Durch Vererbung wurde z.B. das spaßige Bounce-Tool geschaffen. Es besteht aus vier Riff-Tools (sie eignen sich zum einfachen und geloopten Abspielen von Musik-Dateien), die in der Mitte mit einem sich frei bewegenden Strich verbunden sind. Bei Kontakt des Striches mit der angrenzenden Seite des Riff-Tools wird das entsprechende Riff-Tool an- oder ausgeschaltet, d.h. man kann kein bis vier Musikstücke hören:

      

Source des Bounce-Tools:

class wbounce {
In der Init-Methode stehen neben Gui-Elementen wie Slider und Window auch vier Riff-Tools, die mit der Methode "addchild" instanziiert werden:
method init {
	$.w = new window()
	$.inherit($.w)

	$.c = []
	$.w2 = new window()
	$.addchild($.w2)
	$.togg = new ktoggle("On",$,"setonoff")
	$.addchild($.togg)
	$.slide = new kslider(0,100,60,$,"setspeed")
	$.addchild($.slide)
	$.r1 = new wriff() ; $.addchild($.r1)
	$.r2 = new wriff() ; $.addchild($.r2)
	$.r3 = new wriff() ; $.addchild($.r3)
	$.r4 = new wriff() ; $.addchild($.r4)
	$.tid = -1

	$.lines = []
	$.dirs = []
	$.sleeptm = 40
}

Es folgen die Getaltungsparameter für die Fenster und Methoden zum An- und Ausschalten der einzelnen Player, sowie zum Neuzeichnen:

method redraw {
	$.w.redraw()
	$.w2.redraw()
	$.line($.sep)
	$.w.text("Bounce",$.textxy)
	$.w.text("Speed",$.speedxy)
	for ( o in $.children() )
		o.redraw()
}
method dump { return([
	"r1"=$.r1.dump(),
	"r2"=$.r2.dump(),
	"r3"=$.r3.dump(),
	"r4"=$.r4.dump()
	])
}
method restore (state) {
	$.r1.restore(state["r1"])
	$.r2.restore(state["r2"])
	$.r3.restore(state["r3"])
	$.r4.restore(state["r4"])
}
method on {
	if ( $.tid >= 0 )
		print("wbounce is already on!?")
	else
		$.tid = task $.task_bounce()
}
method delete {
	kill($.tid)
	millisleep(1000)
}
method off {
	if ( $.tid < 0 )
		print("wbounce not running!?")
	else {
		kill($.tid)
		$.tid = -1
	}
}
method reset {
	$.w2.redraw()
	# $.line($.lines[0],CLEAR)
}
method setonoff (v) { if (v) $.on(); else $.off() }
method setspeed (v) { $.sleeptm = 100-v }

Diese Methode ist für den Balken verantwortlich:

method task_bounce() {
	onexit(global(domethod),$,"reset")
	$.lines[0] = xy($.w2.xmin()+10,$.w2.ymin()+10,
			$.w2.xmin()+30,$.w2.ymin()+30)
	$.dirs[0] = xy(1,2,2,1)
	$.line($.lines[0],XOR)
	while ( 1 ) {
		if ( $.sleeptm > 0 ) {
			sync()
			millisleep($.sleeptm)
		}
		$.line($.lines[0],XOR)
		sz = $.w2.size()

		$.adj($.lines[0],$.dirs[0],sz)

		$.line($.lines[0],XOR)
	}
}

Und das Folgende ist ein Auszug aus der Methode, die beim Kontakt des Balkens mit einem Riff-Tool ein Bang-Tool aktiviert, das zum An- und Ausschalten der Riff-Tools benutzt wird:

method adj(ln,d,sz) {
	ln["x0"] += d["x0"]
	if ( ln["x0"] < sz["x0"] ) {
		ln["x0"] -= d["x0"]
		d["x0"] = -d["x0"]
		$.r4.bang()
		$.reset()
	}
	else if ( ln["x0"] > sz["x1"] ) {
		ln["x0"] -= d["x0"]
		d["x0"] = -d["x0"]
		$.r2.bang()
		$.reset()
	}

Nicht dargestellt ist die Methode, die ein resizing des Tools ermöglicht.

KeyKit-Resümee

Wir bewerten KeyKit abschließend als eine für reine Midi-Anwendungen mächtige Sprache, die jedoch, will man sie effektiv und innovativ benutzen, eine erhebliche Einarbeitungszeit verlangt. Besonders zu berücksichtigen ist der regelmäßige falsche Neuaufbau von Seiten oder Tools, der wahrscheinlich durch eine fehlende Garbage-Collection verursacht wird und KeyKit von der professionellen Programmierung eigentlich disqualifiziert. Weiterhin unvorteilhaft kam uns die überbordende und ästhetisch nicht sehr ansprechende GUI-Gestaltung vor. Würde man tief in der Library neue GUI-Elemente programmieren, könnte man diesen Makel jedoch beseitigen. Positiv ist die komplexe und raffinierte Umgangsweise mit Midi-Befehlen zu vermerken, die den Umgang mit dem sonst eher spröden MIDI-Protokoll attraktiv macht und in Zeiten, in denen Soundsynthese und Sampling en vogue sind, noch einmal die Stärken eines Protokolls vorführt, das auch auf kleineren Rechnern prima in Echtzeit funktioniert.

II.3

Supercollider

Einführung

Supercollider ist eine interaktive Umgebung zum Erstellen von Audioanwendungen. Das Programm wurde von James McCartney entwickelt und ist aus der Vereinigung zweier getrennter Projekte des Autors hervorgegangen. Eines davon, "Synth-O-Matic", hatte er 1990 begonnen. Es ist also ein jüngeres Produkt als das alt-gediente Csound, aber doch auch schon einige Jahre gereift.

Supercollider wird (leider) kommerziell vertrieben - Kostenpunkt $250 - und ist nur auf Apple Macintosh lauffähig. Das Geld ist aber durchaus gut angelegt: Neben einem sehr leistungsfähigen Audio-Tool erhält man kostenlosen Support über eine Mailing-Liste.

Der Sprachkern von Supercollider ist im wesentlichen eine Mischung aus Smalltalk und C. Bei genauerem Hinsehen stellt man fest, dass von C nicht viel mehr als die Syntax übrig geblieben ist; im übrigen atmet die Sprache den "Geist" von Smalltalk. Dies kommt der Leistungsfähigkeit des Systems sehr zu gute: Die Abstraktionsmöglichkeiten dieser auch heute noch innovativen Sprache erlauben es, mit wenigen Zeilen komplexe Soundstrukturen zu entwerfen.

Wie man allerdings die Meisterschaft erwirbt, einen bestimmten Klang im Ohr zu haben und ein passendes Programm wie dieses

{
    var n = 4;		// number of simultaneous events
    OverlapTexture.ar({
        Pan2.ar(SinOsc.ar(
            Lag.kr(LFSaw.kr(
                LFPulse.kr(0.4+1.0.rand, 0.8.rand+0.1, 3.0.rand+4, 2)
                +LFPulse.kr(0.4+1.0.rand, 0.8.rand+0.1, 3.0.rand+4),
                (1000 + 800.rand).neg, 4000+1200.rand2), 0.05),
            0, Lag.kr(LFPulse.kr(0.5.rand+0.2, 0.4, 0.02), 0.3)), 1.0.rand2);
    }, 7, 4, n, 2)
}.play
"einfach so" hinzuschreiben, ist uns ein Rätsel geblieben (das Beispielprogramm erzeugt übrigens ein recht lebendiges Vogelzwitschern - hätten Sie's gewußt?).

Wir vermuten eher, dass nur das Gerüst des Programmcodes frei "erfunden" ist, und die zahlreichen Parameter interaktiv festgelegt wurden - denn dies lässt sich geradezu spielend mit Supercollider umsetzen. Vollständige grafische Oberflächen mit Schiebereglern, Oszilloskopen und natürlich gängigen UI-Controls wie Buttons und Radio-Boxen können sehr einfach erstellt werden.

Supercollider arbeitet grundsätzlich in Echtzeit. Dadurch kann man so etwas wie interaktives Sounddesign betreiben. Auch das Einbeziehen von externen Klangquellen ist möglich, etwa über Line-In oder ein Mikrofon. Dabei muss man aber mit den üblichen Latenzproblemen (Verzögerung der Ausgabe gegenüber der Quelle) rechnen, die kaum eine softwarebasierte Audio-Lösung in den Griff bekommt.

Klang-Beispiele

"Sprinkler"

play({
    BPZ2.ar(WhiteNoise.ar(LFPulse.kr(
        LFPulse.kr(0.1, 0.16, 10, 7),
    0.25, 0.1)))
})

Der Name spricht für sich - es klingt wie eine Bewässerungsanlage. Der Grundklang ist ein weisses Rauschen (WhiteNoise). Dieses wird von einem Oszillator rhythmisch unterbrochen (erstes LFPulse). Die Frequenz dieses Oszillators wird selbst wieder moduliert, und zwar von einem weiteren Rechteck-Oszillator. Dies führt dazu, dass der Sprinkler abwechselnd langsamer und schneller zu laufen scheint.

"Hell is busy"

var n = 8; // number of simultaneous events
{
	OverlapTexture.ar({
		Pan2.ar(
			FSinOsc.ar(400+2000.0.rand,
			    LFPulse.kr(1+10.0.rand, 0.7.rand, 0.04)),
		1.0.rand2)
	}, 4, 4, n, 2)
}.play

OverlapTexture ist geradezu eine Wunderwaffe, wenn es darum geht, sich ständig verändernde Klangteppiche zu erzeugen. Sie bekommt als ersten Parameter ein Stück Programmtext (erkennbar an den geschweiften Klammern; der Fachausdruck ist "funktionaler Abschluss" oder "Closure"). Dieser Programmtext wird in regelmässigen Intervallen immer wieder angestossen, sprich ausgeführt. Er muss als Rückgabewert ein Soundobjekt liefern; dieses wird der Textur hinzugefügt. Dabei gibt es ein ständiges Ein- und Ausblenden, so dass der Eindruck einer ständigen fliessenden Veränderung entsteht.

Die Aufrufe der Methode "rand" sind dabei entscheidend: sie berechnen Zufallszahlen. (Smalltalk-typisch ist rand keine Funktion wie sonst üblich, sondern eine Botschaft, die an ein Zahlenobjekt geschickt wird - daher die ungewöhnliche Syntax.) Bei jedem Durchlauf der Closure werden neue Zufallszahlen gewählt; dadurch entstehen immer neue Klänge.

Hier noch ein komplexeres Beispiel zum Abschluss:

"Native Algorythms"

var n, pat, texture;
n = 8; 	// n = number of partials for percussion instruments

// create an algorhythmic rhythm pattern
pat = Prand([ // choose one of the following patterns at random
		Pseq(#[2.0, 0.0, 2.0, 0.0, 1.0, 0.0, 1.0, 1.0]),
		Pseq(#[2.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0]),
		Pseq(#[2.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0]),
		Pseq(#[2.0, 0.3, 0.3, 1.0, 0.3, 0.3, 1.0, 0.3]),
		Pseq(#[2.0, 0.0, 0.3, 0.0, 0.3, 0.0, 0.3, 0.0]),
		Pseq(#[2.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]),
		Pseq(#[2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
		Pseq(#[0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0]),
		Pseq(#[1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
				0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0])
	], inf
);

play({
    OverlapTexture.ar({
        var freq, seq, excitation, resonator;

        freq = 40 + 300.0.rand;       // random base frequency

        seq = ImpulseSequencer.ar(    // outputs a sequence of single sample triggers
            pat.asStream,             // create a stream to iterate over pattern
            Impulse.ar(10));          // a clock for the sequencer at 10 beats per second

        excitation = Decay.ar(        // a decaying envelope for the noise
            seq,                      // impulse sequence triggers decay envelopes
            0.1,                      // 60 dB decay time
            PinkNoise.ar(0.01));      // noise is the exciter

        resonator = Klank.ar(         // use Klank as a percussion resonator
            `[                        // filter bank specification array:
                Array.fill(n, { freq + (4.0*freq).linrand }), // resonant frequencies
                nil,                  // amplitudes default to 1.0
                Array.fill(n, { 0.2 + 3.0.linrand })          // ring times
            ], excitation);

        Pan2.ar(resonator, 1.0.rand2) // random pan position
        // return the Pan2
    }, 8, 4, 4, 2); // sustainTime, fadeTime, overlapDensity, number of channels
});

GUI-Beispiele

1. Beispiel: Funktionsplotter

Synth.plot({
		[BrownNoise.ar,WhiteNoise.ar,PinkNoise.ar,FSinOsc.ar(300)]
},0.05);

Dieses - zugegeben nicht ganz repräsentative - Beispiel zeigt, wie schnell man mit Supercollider Visualisierungen erstellt. Man packt einfach vier Sound-Objekte in ein Array und kann sie mit einem einzigen Methodenaufruf übersichtlich als Funktionsplot untereinander darstellen lassen.

2. Beispiel: Fenster und Buttons

var f, h=0, v=0;

f = {
	var w, b;
	w = GUIWindow.new("panel", Rect.newBy( 65+h, 78+v, 124, 90 ))
		.backColor_(rgb(222,142,115));
	b = ButtonView.new( w, Rect.newBy( 29, 24, 64, 34 ), "NEW", 0, 0, 1, 0, 'linear')
		.backColor_(rgb(230,232,149));
	b.action = {
		h = (h + 82) % 500;
		v = (v + 82) % 300;
		f.value;
	};
};

f.value;

Durch Drücken eines NEW-Buttons öffnet man ein weiteres, identisch aussehendes Fenster. Um zu definieren, welche Aktion mit dem Drücken des Buttons verknüpft sein soll, kommt wieder eine Closure zum Einsatz: sie ist immer dann nützlich, wenn Programmcode erst zu einem späteren Zeitpunkt (oder sogar mehrfach) ausgeführt werden soll. In diesem Beispiel kommt sogar eine Rückbezüglichkeit ins Spiel: Die innere Closure ruft wiederum die äußere auf, die ein neues Fenster öffnet. Glücklicherweise kommt der Supercollider-Interpreter mit solch komplexen Strukturen klar.

Anhang

URLs