Beruflich Dokumente
Kultur Dokumente
SC Tutorial
SC Tutorial
(
Synth.play({
Pulse.ar(
LFNoise0.kr(
8, // 8 Tonwechsel / Sekunde
6, // im Bereich ± Tritonus
73 // ueber cis
).round(2).midicps
)
// mit linearer Interpolation })
{LFNoise1.ar(100)}.plot )
Fortsetzung folgt
Synth.plot({Impulse.ar(3000)},0.01,"Impulse") Bezugsquellen
SuperCollider ist ein kostenpflichtiges Programm. Alle Infor-
Was bedeutet diese Schreibweise? Beim Rosa Rauschen fällt mationen zum Bezug des Programms und die Software selbst
die spektrale Amplitude umgekehrt proportional zu Höhe der Die letzte Zeile im ugenGraph erzeugt einen Stereo-Output: findet man auf der Homepage von SC:
Frequenz ab - im Prinzip verhält es sich hier wie beim links (im Scope-Window oben) die Impulse und rechts (im www.audiosynth.com
Spektrum der Sägezahnwellenform, das dem gleichen Bil- Scope-Window unten) die getriggerten Hüllkurven. Es ist auch möglich, die Software unregistriert als Demo-
dungsgesetz unterliegt. Beim 1/f2 - Rauschen nimmt die Version mit einigen Einschränkungen laufen zu lassen. Zur
Amplitude sogar mit dem Quadrat der Frequenz ab - ähnlich [trig, env] Einarbeitung mit dem Programm werden viele Beispiel-
wie bei der Dreiecksschwingung. dateien und einige Tutorials mitgeliefert. Eine weitere sehr
Um anstelle eines Monosignals ein mehrkanaliges Signal zu empfehlenswerte und ständig wachsende Quelle für Bei-
Neben diesen z.T. auch bei anderen Synthesizern vorhande- erzeugen, schreibt man einfach alle gewünschten Signale in spiele, musikalische Anwendungen, Tutorials und spezielle
nen „klassischen“ Rauschgeneratoren finden sich in SC eini- ein Array, d.h. zwischen eckige Klammern und durch Fragen der objektorientierten Programmierung befindet sich
ge weitere eher unkonventionelle aber klanglich und struk- Kommata getrennt. Auf diese Weise können auch 4, 8 oder unter dieser Internetadresse:
turell sehr interessante Zufallsgeneratoren, die zumeist auf eine beliebige Anzahl von Kanälen erzeugt werden. Diese swiki.hfbk.uni-hamburg.de:8080
Gleichungen aus der Chaostheorie basieren: Mit Hilfe der Objekte Decay oder Decay2 können Technik, die in SC Multichannel Expansion genannt wird, ist /MusicTechnology/6
Impulsgeneratoren auf einfache Weise auch als Trigger für sehr leistungsstark. Wir werden später darauf ausführlich
Weitere Links, vor allem zu SC-Anwendern und Projekten,
percussive Klänge verwendet werden: zurückkommen. Hier sollte sie uns nur helfen, mehrere
sind auf der SC-Homepage angegeben.
Signale gleichzeitig darzustellen.
Andre Bartetzki
40 SuperCollider SuperCollider 41
Als sequence kann entweder eine Referenz auf ein Array (
S u p e r C o l l i d e r oder ein Objekt, das auf die Message value antworten Synth.play({
kann, dienen.
Einführung in die Programmierung, Teil 3 Mit dem Objekt Sequencer können wir jedem dieser var melo, tempo, trig, amp;
Ein Array ist eine Reihe von Elementen in eckigen
perkussiven Töne eine andere Tonhöhe zuweisen. Dazu Klammern, z.B. tempo = 5;
Andre Bartetzki wird im Takte eines Triggers ein neuer Wert von einer
trig = Impulse.ar(tempo);
abart@snafu.de Sequenz abgerufen: melo = [60,64,67,72,67,69,65,67].midicps;
amp = Decay.ar(
Dieser mehrteilige Einführungskurs zum Softwaresynthe- Sequencer.kr(sequence, trig, mul, add) Die Werte in diesem Array sind offensichtlich MIDI- // Impulstrigger:
sizer SuperCollider (SC) begann in den Mitteilungen 40 Notennummern, die hier an dieser Stelle gleich per trig,
und wird an dieser Stelle fortgesetzt. Am Schluß dieses Zum Verständnis müssen wir aber etwas weiter ausholen: midicps-Message in Frequenzen konvertiert werden. // Decayzeit:
3. Teils finden sich noch einmal Hinweise auf die Quellen Ein Triggerimpuls wird dann erzeugt, wenn ein Signal einen Eine Referenz auf ein Objekt erhält man durch Voranstellen 1/tempo);
für die Software und ergänzendes Informationsmaterial. Übergang von nicht-positiven Werten (Null und negativ) zu eines accent grave `.
positiven Werten (größer als Null) vollführt. Somit können melo = [60,64,67,72,67,69,65,67];
Trigger und Controls (Fortsetzung) Das folgende Beispiel zeigt die Verwendung eines Arrays und
alle Signale, die zwischen positiven Werten und Null bzw. melo = melo.midicps;
eines Triggers für Sequencer. Der Sequencer ruft bei
Um perkusssive Klänge zu erhalten, müssen die von negativen Werten schwanken, als Trigger dienen. Im fol- Saw.ar(
jedem Triggerimpuls, die hier von einem Rechteckgenerator
Decay bzw. Decay2 erzeugten Signale mit kontinuierli- genden Beispiel triggert ein Sinusoszillator ein Trig1-
kommen, eine neuen Wert aus der Liste melo auf. Diese // Frequenz:
chen Signalen multipliziert werden. Da Decay und Objekt, das beim Auftreten eines Triggerimpulses einen
vom Sequencer erzeugte Wertefolge wird als Sequencer.ar(`melo, trig),
Decay2 ebenso wie fast alle UGens in SC einen Impuls mit der Amplitude 1.0 und definierter Dauer abgibt:
Frequenzargument des Sägezahngenerators benutzt:
Parameter mul besitzen, können wir uns eine explizite // Amplitude:
Multiplikation sparen und statt dessen das 2. Signal als ( amp);
(
mul-Parameter von Decay2 schreiben: Synth.plot({
Synth.play({ })
var sig, trig, exp;
( )
sig = SinOsc.ar(5); var melo, trig;
Synth.scope({ melo =
// Impulse mit 20 ms Dauer: Alternativ kann als sequence auch eine Funktion oder ganz
var trig, dec, env, sig;
trig = Trig1.ar(sig, 0.02); [60,64,67,72,67,69,65,67].midicps; allgemein etwas verwendet werden, das auf die value-
trig = Impulse.ar(8); Message mit der Rückgabe eines geeigneten Wertes rea-
[sig, trig] trig = LFPulse.ar(5);
dec = 0.1; // 100 ms Abklingen giert. In diesem Falle wird bei jedem Triggerimpuls eine
}, 1)
sig = Saw.ar(300); // Sägezahn Saw.ar(Sequencer.kr(`melo, trig)); value-Message an die Sequenz geschickt.
) })
env = Decay.ar(trig, dec, sig);
) (
[sig, env]
Der Plot zeigt, daß die Triggerimpulse immer beim Synth.play({
}, 0.4) Nulldurchgang der ansteigenden Flanke der Als Trigger für Sequencer kann normalerweise kein
Impulse verwendet werden, da die Impulse zu schmal var seq, tempo, trig, amp;
) Sinusschwingung ausgelöst werden:
sind, um von dem mit Controlrate (kr) arbeitenden tempo = 5;
Sequencer sicher erfaßt werden zu können (zur Controlrate trig = Impulse.ar(tempo);
siehe nächsten Abschnitt). Aus diesem Grunde wurde hier
amp = Decay.ar( trig, 1/tempo);
auf einen Rechteckgenerator zurückgegriffen. Alternativ
hätte man die Impulse mit Trig1 „verbreitern“ können: // Funktion für Zufallstöne
// zwischen 60 und 72:
trig = Trig1.ar(Impulse.ar(5), 0.02); seq = {rrand(60, 72).midicps};
Saw.ar(Sequencer.ar(seq,trig), amp);
Eine andere Möglichkeit besteht darin, den Sequencer mit
Samplingrate (ar) laufen zu lassen: })
)
Sequencer.ar(`melo, Impulse.ar(5));
Der Sequencer braucht durchaus nicht nur im
Um nun den Tönen perkussive Hüllkurven zuzuordnen, gleichmäßigen Tempo zu laufen - die Triggerimpulse könn-
können wir einen gemeinsamen Impulsgenerator sowohl ten auch von Dust generiert werden:
für Decay als auch zum Triggern des Sequencer lau-
fen lassen:
42 SuperCollider SuperCollider 43
( In gewisser Weise simuliert Sequencer die noch aus SuperCollider bietet, ähnlich wie MAX/MSP, auch eine ar und kr
Synth.plot({ analogen Synthesizertagen stammenden Stepsequenzer, Reihe von interaktiven Steuerungsmöglichkeiten: Schon oft sind uns die beiden Abtastraten in diesem Kurs
var vals, trig; die eine einmal mit Potentiometern eingestellte Wertefolge • eine Maussteuerung mit MouseX und MouseY begegnet. Die Audiorate bzw. Sampligrate, die in SC
vals = [0,2,4,6,8,10] / 10; in einem festen oder veränderlichen Tempo zyklisch durch- Sample Rate genannt wird, kann je nach ASIO-Treiber im
laufen. Die Verwendung von Arrays und Sequencer ist • eine WACOM-Grafiktablett-Steuerung mit TabletX,
trig = Dust.ar(15); TabletY, TabletZ, TablettPressure und Audio-Setup-Dialog (erreichbar über’s File-Menü)
Sequencer.ar(`vals, trig); ein nur sehr einfaches Mittel für die zeitabhängige eingestellt werden. Die üblichen Werte sind 44.1, 48 oder
Erzeugung von Parametern für Klänge. Mit neueren anderen UGens
}, 1) 96 kHz.
Konzepten wie Pattern und Streams lassen sich, verglichen • alle MIDI-Channel-Messages: MIDIController,
) Die Controlrate ist eine geringere Frequenz, die immer ein
mit Arrays, weitaus kompliziertere Strukturen erzeugen. Und MIDITouch, MIDIPitchBend, MIDIPolyTouch,
mit Spawn und den davon abgeleiteten Klassen stehen MIDIProgram, MIDINoteGate, MIDIMost- ganzzahliger Teil der Samplingrate sein muß. Anders aus-
verschiedene Objekte zur zeitlichen Steuerung von Klang- RecentVelocity, MIDIMostRecentNote gedrückt: in einen Controlrate-Takt fallen immer mehrere
ereignissen zur Verfügung, die weitaus leistungsfähiger Audiosamples. Wir können uns diese Gruppe von
• eine TCP/IP-fähige Schnittstelle durch OpenSoundControl
sind, als getriggerte Sequenzer. Beide Konzepte sollen spä- Audiosamples, die in eine Periode der Controlrate fallen,
• eine programmierbare grafische Oberfläche (GUI) mit als einen Block vorstellen. Die Größe des Blocks, in SC typi-
ter besprochen werden.
Reglern, Checkboxen, Buttons u.v.a.m. scherweise 64 oder 128, stellt man ebenfalls im Audio-
Selbstverständlich gibt es neben Decay und Decay2 in Setup-Dialog unter Default Block Size ein.
Bei der Abfrage der Mausposition mit MouseX oder
SC auch Möglichkeiten, komplexere Hüllkurven zu generie-
MouseY wird die aktuelle Position des Mauszeigers zwi- Dieser Block von Samples, auch Signal Buffer genannt,
ren. Den dafür benötigten Klassen Env und EnvGen zum
schen linkem und rechtem (bzw. oberem und unterem) wird bei aktiviertem Synth von den UGens immer als
Deklarieren und Anwenden von Envelopes wird ebenfalls
Bildschirmrand auf die angegeben Werte minval und Ganzes berechnet und erst dann als Paket an andere
ein eigener Abschnitt in diesem Kurs gewidmet werden.
maxval interpolierend abgebildet. Die Interpolation kann UGens weitergegeben. Der Vorteil ist eine wesentlich ver-
Zunächst aber noch zu einigen weiteren einfacheren dabei linear oder exponentiell sein (warp). Zur zeitlichen ringerte Anzahl von Unterprogrammaufrufen, die immer
Objekten zur Steuerung bzw. Modulation: Glättung dient eine 60dB-Integrationszeit, mit der ange- relativ viel Rechenzeit in Anspruch nehmen.
Neben gleichmäßigen und irregluären Triggern können mit Zur Generierung von einfachen Signalverläufen stehen geben wird, in welcher Zeit der neue Wert bis auf 0.1 % Im folgenden Beispiel wird also pro Controlrate-Takt ein
Hilfe von StepClock auch zeitliche Muster für Line und XLine zur Verfügung. Line wurde bereits in Differenz angenähert wird. ganzer Block von Samples vom Sägezahnoszillator an das
Sequencer verwendet werden. Hierzu übergibt man vorangegangenen Beispielen verwendet. XLine generiert Tiefpaßfilter übergeben, von der Maus hingegen nur ein
StepClock, ähnlich wie dem Sequencer, eine innerhalb der angegebenen Dauer einen exponentiellen MouseX.kr(minval,maxval,warp,lagTime) einziger Wert:
Referenz auf ein Array, das die Triggerimpulsabstände in Verlauf zwischen zwei Werten, die beide das gleiche Vor-
Sekunden enthält, sowie einen Geschwindigkeitsfaktor zeichen besitzen müssen und ungleich Null sein müssen. Mit der Abfrage der Mausposition hat man in SC ein schnell (
rate: einsetzbares Mittel zur Hand, um geeignete Wertebereiche
Synth.play({
(Synth.plot({ für Modulationen interaktiv auszutesten.
( var in, freq;
var lin, exp;
( in = Saw.ar(200);
Synth.plot({ // von 0.1 nach 0.9 in 2 Sekunden:
Synth.play({ freq = MouseX.kr(200, 2000);
var vals, trig, rhythm; lin = Line.ar(0.1, 0.9, 2);
var fr, amp; LPF.ar(in, freq);
vals = [0, 0.5, 1]; exp = XLine.ar(0.1, 0.9, 2);
fr = MouseX.kr(100,800,‘exponential’); })
rhythm = 1 / [4, 16, 8, 16]; [lin, exp]
amp = MouseY.kr(0.0,1.0,‘linear’); )
trig = StepClock.ar(`rhythm, 1); }, 2.5)) // plotte 2.5 Sekunden
Sequencer.ar(`vals, trig); SinOsc.ar(fr, 0, amp); Der Datenaustausch zwischen UGens, die mit Audiorate
}, 3) }) laufen, findet also mit Controlrate statt! Das hat aber kei-
) ) ne negativen Auswirkungen auf die zeitliche Präzision der
Audiodatenübertragung.
Ganz ähnlich funktiert die Steuerung über ein Aber: je größer die Default Block Size, umso
Grafiktablett, auf die hier aber nicht weiter eingegangen effizienter arbeitet SC! Gleichzeitig aber werden die
werden soll. UGens, die tatsächlich mit Controlrate abgetastet werden,
Die GUI-Programmierung und der Umgang mit MIDI und also nur ein Sample pro Controlrate-Takt erzeugen, zeitlich
OpenSoundControl (OSC) werden später in je einem eige- schlechter aufgelöst:
nen Abschnitt ausführlicher behandelt. Rate von 44.1 kHz und einer
Bei einer Sample
Allen 4 letztgenannten Steuerungsmöglichkeiten ist ge- Default Block Size von 64 werden immer gleich
meinsam, daß sie ihre Werte nicht mit der Samplingrate ar 64 Audiosamples gemeinsam berechnet und übertragen.
produzieren, sondern maximal mit der Controlrate kr Die Controlrate beträgt in diesem Fall nur etwa 689 Hz
abgefragt werden können. (1/64 von 44100 Hz), die Dauer einer Controlperiode bzw.
44 SuperCollider SuperCollider 45
eines Blocks beträgt etwa 1.45 ms (64/44100). Höhere Nicht alle UGens lassen sich mit beiden Rates erzeugen: ( Genau genommen haben wir beim letzten Beispiel nicht
Frequenzen als etwa 340 Hz können mit einer solchen • SinOsc, Osc, alle Rauschgeneratoren, Line, XLine Synth.scope({
einfach nur 4 Oszillatoren erzeugt, sondern ein Array mit 4
Controlrate nicht mehr fehlerfrei erfaßt werden (Aliasing!). und die meisten Filter laufen mit beiden Frequenzen, Oszillatoren als Elemente:
Die Controlrate ist also nur zum Abtasten relativ langsamer [SinOsc.ar(100),
• die im letzten Abschnitt besprochenen externen
Vorgänge geeignet, wie z.B. Mausbewegegungen, langsa- K2A.ar(SinOsc.kr(100))] SinOsc.ar([100,300,517,943]).postln;
Controller (Maus, MIDI, GUI, Tablett) laufen nur mit
me Hüllkurven, Reglerbewegungen, MIDI-Messages etc. Controlrate, }) -> [ a SinOsc, a SinOsc, a SinOsc, a SinOsc ]
Immer dann, wenn von UGens sich nur langsam ändernde • die bandbegrenzten Oszillatoren Saw, Pulse und )
Signale erzeugt werden sollen, sollte man diese UGens mit Blip laufen dagegen nur mit Audiorate. Das Ergebnis ist so, als hätte man geschrieben:
Controlrate laufen lassen, um Rechenzeit zu sparen und Ebenso gibt es Einschränkungen für die Verwendung von Zusammenfassend hier noch einmal einige Entscheidungs-
auf diese Weise effizienter zu programmieren. In die glei- UGens als Input für andere UGens: Beispielsweise dürfen hilfen zum Einsatz der beiden Abtastraten: [
che Richtung wirkt sich eine Verringerung der Controlrate Filterfrequenzen maximal mit Controlrate geändert wer- SinOsc.ar(100), SinOsc.ar(300),
ar verwendet man, wenn:
(entspricht einer Erhöhung der Blocksize!) aus. den. Solche Parameter werden im Helpfile des jeweiligen SinOsc.ar(517), SinOsc.ar(943)
Andererseits kann im Bedarfsfall die Controlrate auch • der UGen ein Signal erzeugen soll, das in direkter oder
UGens durch ein vorangestelltes k gekennzeichnet, z.B.: ]
höher eingestellt werden, etwa wegen besserer zeitlicher modifizierter Form letztendlich als Output des Synth die-
Präzision. Sogar eine Default Block Size von 1 Resonz.ar(in, kfreq, krq, mul, add) nen soll und also hörbar wird
ist möglich, das hieße Audiorate gleich Controlrate. • das zu erzeugende Signal als Input für einen anderen Immer wenn man einem UGen anstelle eines einfachen
UGen vorgesehen ist und dieser Input auf ar limitiert ist, Arguments ein Array von Argumenten übergibt, wird ein
Die Abtastrate eines UGens wird durch Senden der Fehlt das k, kann meistens (nicht immer!) jede Rate ver- Array von UGens erzeugt.Wir können uns das auch als ver-
Message ar oder kr an das Klassenobjekt des UGens wendet werden. In seltenen Fällen dürfen sich die Para- • der UGen ausschließlich mit Audiorate betrieben wer-
den kann (wie z.B. bandbegrenzte Oszillatoren), kürzte Schreibweise für ein UGen-Array vorstellen.
festgelegt. ar bzw. kr erzeugen eine Instanz dieser meter überhaupt nicht ändern. Dann wird ein i vorange-
Klasse, die dann mit der jeweiligen Rate läuft. stellt - i steht für init rate, also für den Zeitpunkt der Ini- • das zu erzeugende Signal Frequenzen oberhalb der hal- Diese Technik wird in SC Multichannelexpansion genannt.
tialisierung der UGens. Leider werden diese Konventionen ben Controlrate enthalten soll. Warum Multichannel? Weil dadurch nicht nur mehrere
SinOsc.ar(100); // 100 Hz, Audio bei der Beschreibung der UGens in den Helpfiles nicht kon- kr verwendet man, wenn: Objekte gleichzeitig generiert werden, sondern diese auch
SinOsc.kr(100); // 100 Hz, Controller sequent verwendet. Meistens läßt sich jedoch die maxima- noch „mehrkanalig“ verteilt werden. Ein Array von UGens
• der UGen ein niederfrequentes (fg < Controlrate/2) wird vom Synth immer wie ein mehrkanaliges Soundfile
le Rate aus der Art und der Bedeutung des betreffenden Signal erzeugen soll, das nur zur Modulation eines
Üblicherweise erzeugt man Instanzen von Klassen mit der Parameters ableiten. interpretiert: jedes Element dieses Arrays wird einem ande-
anderen Signals dient, ren Ausgang der Soundkarte zugewiesen und erklingt
Message new. UGens können aber auschließlich über ar Gelegentlich ist es notwendig, ein Controlrate-Signal in • der UGen ausschließlich mit Controlrate betrieben wer-
bzw kr. instanziert werden. new, ar und kr sind somit aus einem anderen Lautsprecher:
eines mit Audiorate zu verwandeln (Oversampling). Zum den kann (z.B. externe Controller),
Klassenmethoden, die dazu dienen, Instanzen auf Beispiel, um sich solche Signale in einem Scope-Window • das zu erzeugende Signal als Input für einen anderen
bestimmte Weise zu erzeugen. Im Gegensatz können ansehen oder als Soundfile speichern zu können, weil als UGen vorgesehen ist und dieser Input auf kr limitiert
Instanzenmethoden nur von bereits erzeugten Instanzen Output eines Synth nur Signale mit Audiorate erlaubt ist (z.B. Filterfrequenzen)
einer Klasse ausgeführt werden. Dazu später mehr. sind. Der UGen K2A erzeugt aus einem kr-Signal per
Die Klassenmethoden ar und kr können nur innerhalb linearer Interpolation die fehlenden Zwischenwerte im Takt Multichannelexpansion, Mix und Pan
der ugenGraph function eines Synth verwendet werden. der Samplingrate. Mit seiner Hilfe können wir zum Beispiel
Außerhalb des Synthesizers funktionieren sie nicht; der die Unterschiede zwischen verschieden abgetasteten Kommen wir nun zu einem sehr leistungsfähigen Feature
Versuch, sie zu benutzen, ruft eine Fehlermeldung hervor: Sinusoszillatoren hör- und sichtbar machen: von SuperCollider, das diesem Programm einen großen
Vorteil gegenüber anderen Syntheseprogrammen, wie
( etwa Csound, MAX oder Reaktor verschafft.
var in, freq; Übergibt einem unit generator als Parameter ein Array mit
Werten anstelle eines einzigen Wertes, so werden genau-
in = Saw.ar(200);
so viele Kopien dieses unit generators erzeugt, wie
Synth.play({ Elemente im Array vorhanden sind. Jede dieser Kopien
freq = MouseX.kr(200, 2000); bekommt als Parameter einen anderen Wert aus dem Array
LPF.ar(in, freq); zugewiesen.
})
// 1 Oszillator:
) SinOsc.ar(100);
• ERROR:
// 4 Oszillatoren mit untersch. Freq.
input UGen is not part of a Synth.
SinOsc.ar([100, 300, 517, 943]);
INPUT UGEN:
Instance of Saw ....
46 SuperCollider SuperCollider 47
( Synth.scope({ Möchte man die unterschiedlichen UGens statt auf ver- ( ramawert benutzen. Die Amplitude des Sinusgenerators
SinOsc.ar([100, 300, 517, 943]) schiedenen Ausgängen nur über einen einzigen Ausgang var n, freq;
braucht hier nicht angepaßt zu werden, da Oszillatoren im
ausgeben, gibt man einfach die Summe der Signale aus. Allgemeinen schon die für Panoramaobjekte passenden
})) n = 12; // 12 Oszillatoren
Wegen der zu erwartenden Übersteuerung wird das Werte zwischen -1 und +1 liefern:
Summensignal anschließend halbiert: freq = 100; // mittlere Frequenz
(
Synth.scope({
(
Mix.ar( Synth.scope({
Synth.scope({
Saw.ar( Pan2.ar(
( Saw.ar(300) + PinkNoise.ar Array.rand(n,0.99,1.02)*freq, Pulse.ar(300), // Signal
+ Dust.ar(800)) / 2 1/n) SinOsc.kr(3) // Position
}) ) )
) }) }, 1) // 1 Sekunde anzeigen
)
)
Aber auch ohne Multichannelexpansion können wir unter- Die erzeugten Werte werden anschließend mit dem Wert
schiedliche Signale auf unterschiedliche Lautsprecher auf- für die Basisfrequenz multipliziert, so daß dem Oszillator
teilen, indem wir diese Signale einfach einem Array über- schließlich ein Array mit 12 Werten zwischen 99.0 und
geben, also mit eckigen Klammern zusammenfassen: 102.0 als Frequenzargument übergeben wird. Das darauf-
hin erzeugte Array mit 12 gegeneinander verstimmten
( Synth.scope({ Sägezahnoszillatoren wird nun mit Mix auf einen Kanal Mit PanAz kann man ein Signal auf einem Kreis positio-
[Saw.ar(300),PinkNoise.ar,Dust.ar(800)] Das Objekt Mix mischt ein Mehrkanal-Array, also ein Array reduziert bzw. summiert. (Ohne Mix hätten wir an dieser nieren, der von beliebig vielen äquidistanten Laut-
von UGens, zu einem Mono-Kanal zusammen: Stelle eine 12-kanalige Soundkarte benötigt, um alle sprechern gebildet wird. Das Kürzel Az steht also für
}))
Klänge zu hören.) Azimuth. Zusätzlich zum Signal und zur Position muß man
( Selbstverständlich kann man mit SuperCollider für die noch die Anzahl der Lautsprecher angeben. Optional steht
Synth.scope({ Zuordnung von Klängen zu Audioausgängen bzw. Laut- noch ein Parameter width zur Verfügung, mit dem die
sprechern auch „klassische“ Panning-Objekte verwenden. „Breite“ des Signal, d.h. über wieviel Lautsprecher es sich
Mix.ar([ erstrecken soll, geregelt werden kann.
Pan2, Pan4 und PanAz sind Objekte für 2- und 4-kana-
Saw.ar(300), liges Panning bzw. Panning für eine beliebige (!) Anzahl Hier ein Beispiel für eine beschleunigte Rotation über 5
PinkNoise.ar, von Ausgängen. Hinzu kommen noch PanB für dreidi- Lautsprecher:
Dust.ar(800) mensionales Panorama im Ambisonics B-Format sowie
einige weitere Objekte, über die man sich im [Pan] - (
]) / 2
Helpfile informieren kann. Synth.scope({
})
Für eine einfache Positionierung im Stereopanorama über- PanAz.ar(
) gibt man dem Objekt Pan2 das zu positionierende Signal 5, // numChans
und als 2. Argument die Position in Form eines Wertes zwi- ClipNoise.ar, // in
Mit Hilfe von Mix und einer der etwas später noch aus- schen -1.0 (links) und 1.0 (rechts). Für die Positionsangabe
führlicher zu besprechenden Array-Methoden kann nun LFSaw.kr(XLine.kr(0.3,8,3)),//pos
sind, wie auch bei den UGens üblich, neben konstanten
ganz leicht ein „fetter“ Sägezahnklang erzeugt werden: Werten auch veränderliche Signale möglich. Man könnte 1, 2 // level, width
z.B. auch eine sinusförmige „Steuerspannung“ als Pano- )}, 3))
48 SuperCollider SuperCollider 49
Alle Pan-Objekte in SC arbeiten ausschließlich mit Array wird ein 1-dimensionales, aus einem 3-dimensiona- eigentlich ein 3-dimensionales Array: die Ebene der Cluster
Intensitätsunterschieden zwischen den Kanälen. Ein len Array wird ein 2-dimensionales usw. Dies geschieht (3 Sub-Arrays), die jeweils alle linken und alle rechten
Laufzeit-basiertes Panning-Objekt unter Ausnutzung des durch Summierung einander entsprechender Elemente. Anteile (2 Sub-Sub-Arrays) der jeweils 12 Sägezähne (12
Haas-Effekts gibt es im derzeitigen Standardumfang von Elemente) enthalten. Diese 3 Ebenen müssen nun durch
SC (Version 2.2.13) noch nicht, kann aber mit anderen geeignete Anwendung von Mix auf eine einzige Ebene
Objekten in SC programmiert werden. Doch zunächst reduziert werden. Dazu wird jeder Cluster zunächst zu
zurück zum Stereopanorama. einem Stereosignal zusammengefaßt. Die resultierenden 3
In einem Mischpult werden überlicherweise monophone Stereosignale werden dann in der letzten Zeile von einem
Kanäle bzw. Signale zunächst auf 2 Busse, links und rechts, weiteren Mix zu einem einzigen Stereosignal gemischt.
verteilt, bevor dann die jeweils linken und rechten Jeder der 3 Cluster belegt einen anderen Bereich auf der
Bussignale zusammengefasst werden. In SuperCollider Stereobasis: Grundton in der Mitte, Terz links-halblinks,
kann man mehrere stereophon verteilte Monosignale als Quinte halbrechts-rechts:
Array von Arrays auffassen. Aber wie sieht es eigentlich (
aus, wenn man ein Array von Stereosignalen erzeugt? Wie
var n, f, f1, f2, f3, cl1, cl2, cl3;
zum Beispiel, wenn man jedem der gegeneinander ver-
stimmten Oszillatoren aus dem vorletzten Beispiel eine n = 12; // 12 Oszill. pro Cluster
Während Pan2 und PanAz Objekte für Panning auf einer individuelle Position im Stereopanorama zuweisen würde: Im Falle unseres Arrays von Stereosignalen können wir f = 38; // Bezugston (D)
von den Lautsprechern begrenzten Kreisbahn sind, eignet sagen, daß Mix alle linken Anteile (= die jeweils 1. f1 = f.midicps; // Prime
sich Pan4 für die Einstellung einer Position in der durch 4 ( // Achtung: funktioniert noch nicht! Elemente der Sub-Arrays) und alle rechten Anteile ( = die f2 = (f + 3).midicps; // kl. Terz
quadratisch angeordnete Lautsprecher gebildeten Ebene. jeweils 2. Elemente der Sub-Arrays) getrennt summiert.
var n, freq; f3 = (f + 7).midicps; // Quinte
Hierzu existieren ein x-Parameter ( -1= links, +1=rechts) Das Ergebnis ist ein neues 2-elementiges 1-dimensionales
n = 12; // 12 Oszillatoren Array, das nun von SC problemlos den Audioausgängen Synth.scope({
und ein y-Parameter ( -1=vorne, +1=hinten):
zugeordnet werden kann. cl1 =
freq = 60; // mittlere Frequenz
Pan4.ar(in, xpos, ypos, level) Mix.ar(
Synth.scope({ Wir müssen also in das letzte Beispiel noch Mix einfügen,
um eine Stereosumme aller 12 Signale zu erhalten: Pan2.ar(
Pan2.ar(
Saw.ar(
Saw.ar( ( // jetzt funktioniert es! Array.rand(n,0.99,1.02) * f1,
Array.rand(n,0.99,1.02) * freq, var n, freq; 1/n),
2/n), n = 12; // 12 Oszillatoren Array.rand(n, -0.2, 0.2)));
Array.series(n, -1, 2 / (n-1)) freq = 60; // mittlere Frequenz cl2 =
) Synth.scope({ Mix.ar(
}) Mix.ar( Pan2.ar(
Pan2.ar( Saw.ar(
)
Array.rand(n,0.99,1.02) * f2,
Saw.ar(
In diesem Fall ist das Ergebnis der ugenGraph function ein 1/n),
Array.rand(n,0.99,1.02) * freq,
mehrdimensionales Array (ein Array von Arrays) der Form: 2/n), Array.rand(n, -1.0, -0.6)));
[ [ L1, R1 ] , [ L2, R2 ] , [ L3, R3 ] , [ L4, R4 ] , ... ] cl3 =
Array.series(n, -1, 2 / (n-1))
Mix.ar(
Jedes Stereosignal ist ein 2-elementiges Array. Diese 2-ele- )
Pan2.ar(
mentigen Arrays sind wiederum Elemente im äußeren )
Saw.ar(
Array. So ein mehrdimensionales Array kann SC aber nicht }) Array.rand(n,0.99,1.02) * f3,
Leider kann auch Pan4 das Problem nicht lösen, daß in ohne weiteres den Ausgängen der Soundkarte zuordnen! )
Wir müssen daher dieses Array wieder auf ein einfaches 1- 1/n),
der abgebildeten Konfiguration der Klang nicht wirklich
innerhalb der zwischen den Lautsprechern aufgespannten dimensionales Array reduzieren. Array.rand(n, 0.6, 1.0)));
Mix reduziert verschachtelte Arrays allerdings nur um eine
Ebene zu hören sein wird. Ein Klang wird immer nur in der Diese Aufgabe kann von Mix erledigt werden. Ganz allge- Ebene. Haben wir 3- oder höherdimensionale Arrays, muß Mix.ar([ cl1, cl2, cl3 ]);
Nähe der Kreislinie oder dahinter wahrzunehmen sein! })
mein wird durch Mix die Tiefe von verschachtelten Mix mehrfach angewendet werden. Um dies demonstrie-
Dieses Problem ist mit reiner Intensitätsstereophonie und Audio-Arrays um eine Ebene reduziert. Aus einem ren zu können, erweitern wir einfach das letzte Beispiel, )
einer kleinen Anzahl von Lautsprecher mit heutigen 1-dimensionalen Array wird ein 0-dimensionales Array indem wir Akkorde von gegeneinander verstimmten
Wandlerprinzipien wahrscheinlich generell nicht zu lösen. (d.h. ein einzelnes Element), aus einem 2-dimensionalen Sägezahnclustern stereophon anordnen. Das ergäbe
50 SuperCollider SuperCollider 51
Funktionen Eine Funktion, die Audioobjekte bzw. einen ugenGraph Soundfiles sein. Das heißt, die Anzahl der Kanäle des Soundfiles wird
Das vorangegangene Beispiel zeigt augenfällig, daß zurückgibt, darf nur innerhalb eines Synth aufgerufen Natürlich erlaubt SC auch die Speicherung der erzeugten nicht als Parameter angegeben, sondern resultiert aus der
Programmieren nicht nur Aufschreiben einer Idee sondern werden, da außerhalb von Synth die Messages ar und Klänge als Soundfiles. Die einfachste Möglichkeit, Klänge Größe des Arrays, das von der ugenGraph function zurück-
besser deren Formalisierung sein sollte. Wir wollen 3 mal kr nicht verstanden werden können. Trotzdem kann die auf die Festplatte zu schreiben, besteht darin, anstelle der gegeben wird. Beim folgenden Beispiel werden 8 dyna-
etwas ziemlich Ähnliches tun, nämlich 3 Cluster gleicher Funktion außerhalb von Synth deklariert werden. Message play die Message record an Synth zu sen- misch gefilterte Sägezähne in die 8 Kanäle eines 30
innerer Struktur und unterschiedlichen Parametern erzeu- Nun können wir den Akkord von stereophonen Clustern den. Dabei muß neben der ugenGraph function auch die Sekunden langen Soundfiles "filtered" gespeichert.
gen, und haben es umständlicherweise auch 3 mal aufge- etwas einfacher notieren: Gesamtdauer in Sekunden sowie ein Pfadname übergeben (
schrieben! werden:
( Synth.record({
In SuperCollider stehen aber verschiedene Werkzeuge Resonz.ar(
var f, f1, f2, f3, cluster; *record(ugenGraphFunc, duration,
bereit, um diese Idee einfacher formulieren zu können. Eine
f = 38; // Bezugston (D) pathName, headerFormat, sampleFormat) Saw.ar(
sehr einfache Möglichkeit besteht darin, den wiederkehren-
den Teil des Codes als Unterprogramm bzw. Funktion zu f1 = f.midicps; // Prime Array.rand(8, 80.0, 83.0)),
Der Pfadname kann absolut angegeben werden, etwa:
programmieren. (siehe dazu auch den Artikel von Torsten f2 = (f + 3).midicps; // kl. Terz LFNoise0.kr(
Anders in diesem Heft!) f3 = (f + 7).midicps; // Quinte path = "Mac HD:work:Audio:test.aiff" Array.rand(8, 0.3, 2.0),
Wir erinnern uns: alles zwischen { } ist eine Funktion. Eine cluster = { 500, 600),
oder relativ zum SuperCollider-Ordner, etwa:
Funktion kann Argumente haben. Eine Funktion gibt etwas arg freq, pan1, pan2, n = 12; 0.03)
zurück. Die Argumente können als Input und der path = ":Sounds:test.aiff" }, 30, "HD:Projekte:snd:filtered")
Mix.ar(
Rückgabewert als Output betrachtet werden. )
Pan2.ar( Relative Pfadnamen beginnen immer mit einem
cluster = { Saw.ar( Doppelpunkt.
arg freq, pan1, pan2, n = 12; Array.rand(n,0.99,1.02)*freq, record übernimmt eigentlich 2 Aufgaben: das Schreiben
Als 4. (optionales) Argument kann das Format des Sound-
Mix.ar( 1/n), files spezifiziert werden. Zur Verfügung stehen zur Zeit: von Soundfiles und die Wiedergabe der ugenGraph func-
Pan2.ar( Array.rand(n, pan1, pan2) tion. Bei sehr vielen UGens kann es schnell zu einer Über-
'AIFF', 'SD2', 'RIFF' (wav!) , 'Sun', lastung der CPU kommen. Trotzdem ist es möglich, das
Saw.ar( )) 'IRCAM', 'none' (ohne Header!) Ergebnis von Patches zu speichern, für dessen Echtzeit-
Array.rand(n,0.99,1.02)*freq, }; Das Format wird also immer als Symbol angegeben. generierung der Rechner zu langsam ist.
1/n), Synth.scope({ Entfällt die Angabe des Headerformats, wird ein AIFF-File Die Message write, deren Argumente genau die glei-
Array.rand(n, pan1, pan2) Mix.ar([ geschrieben. chen wie für record sind, schreibt Soundfiles, ohne sie
)) cluster.value(f1, -0.2, 0.2), Mit sampleFormat ist die Auflösung und Byte-Ord- gleichzeitig abzuspielen. Mit write können beliebig
}; cluster.value(f2, -1.0, -0.6), nung der Samples gemeint. Neben 16bit werden noch vie- komplexe Klänge abgespeichert werden. In der Regel geht
cluster.value(f3, 0.6, 1.0) le weitere Auflösungen unterstützt, so z.B. auch 64bit dies auch schneller als in Echtzeit. Allerdings ist dann die
Diese neue Funktion cluster bekommt als Argumente Fließkomma. Die genauen Symbole dafür findet man im Steuerung von Parametern durch externe Controller, wie
])
genau diejenigen Parameter, um die sich Cluster voneinan- Helpfile von SoundFile (nicht Synth!). Der default- Maus oder MIDI, nicht mehr möglich.
der unterscheiden sollen. Zusätzlich wurde auch die Anzahl })
Wert für sampleFormat ist 16bit Integer.
der Oszillatoren pro Cluster als Argument vorgesehen. )
Die Samplingrate der zu erzeugenden Soundfiles entspricht
Argumente, deren Deklaration mit Hilfe des Schlüssel- Der Programmtext ist nicht nur kürzer und besser lesbar immer dem im Audio-Setup eingestellten Wert.
wortes arg erfolgt, sind eigentlich nichts weiter als Fortsetzung folgt
geworden, es lassen sich jetzt auch eventuelle strukturelle
Variablen, die nur innerhalb der Funktion gültig sind - sol- (
Änderungen des Cluster einfacher vornehmen, weil man
che Variablen heißen lokal - und die von außen beim den Code jetzt nur noch an einer Stelle ändern muß. Synth.record(
Aufrufen der Funktion einen Wert zugewiesen bekommen. Außderdem ist es jetzt offenbar ganz einfach, weitere {
Man kann auch schon bei der Deklaration der Argumente Cluster hinzuzufügen. Es spricht also vieles für die // irgendein ugenGraph
einen Standardwert für jedes Argument festlegen, der Verwendung von Funktionen! },
immer dann verwendet wird, wenn dieses Argument beim Bezugsquellen
Bei diesem Beispiel ist allerdings auch Vorsicht geboten: wir 20, // 20 Sekunden
Aufrufen der Funktion nicht angegeben wird. So ein Die SuperCollider Homepage:
wollen hier sehr viele UGens erzeugen, die von langsameren "HD:Projekte:snd:klangx"
default-Wert wurde hier für das Argument n vorgegeben. www.audiosynth.com
Rechnern in Echtzeit möglicherweise nicht mehr generiert )
Eine Funktion wird aufgerufen, indem man die Message werden können! Die CPU-Last kann bei aktivem Synth in Weitere Informationen, Beispiele, Tutorials etc:
value an die Funktion schickt und die Argumente der )
der Informationsleiste oben links beobachtet werden: swiki.hfbk.uni-hamburg.de:8080
Funktion als Argumente von value notiert. Bei unserer SuperCollider beherrscht das Schreiben mehrkanaliger /MusicTechnology/6
Clusterfunktion könnte ein Aufruf so aussehen: Soundfiles problemlos. Dazu muß des Ergebnis der ugen-
cluster.value(440, -1.0, -0.0) Graph function eben ein Array mit mehr als 2 Elementen Andre Bartetzki
28 SuperCollider SuperCollider 29
Aber möglicherweise gibt es ein Problem beim Lesen des Diese und weitere Möglichkeiten im Umgang mit DiskIn
S u p e r C o l l i d e r Headers, z.B. weil das im Pfadnamen angegebene File gar werden im entsprechenden Helpfile ausführlich dargestellt.
nicht existiert oder nicht lesbar ist. Deshalb empfiehlt sich, Es sei an dieser Stelle darauf hingewiesen, daß auch ein ent-
Einführung in die Programmierung, Teil 4 gleich eine Prüfung vorzunehmen, ob das Lesen des sprechendes DiskOut Objekt existiert. In der Mehrzahl
Headers erfolgreich war: der Fälle wird man allerdings Synth.record benutzen,
Andre Bartetzki // Soundfile erzeugen:
um ein Soundfile auf die Festplatte zu schreiben.
abart@snafu.de ( if (
Die eben beschriebene Methode des direkten Lesens von
Synth.write( file.readHeader("HD:snd:klang"),
Dieser mehrteilige Einführungskurs zum Softwaresynthe- der Platte bringt einige Nachteile mit sich. Der größte
{ { „OK“.postln }, Mangel dürfte das Fehlen einer variablen Abspielgeschwin-
sizer SUPERCOLLIDER (SC) begann in den Mitteilungen
// irgendein ugenGraph { „Lesefehler!“.postln } digkeit bzw. Transposition sein. Darüberhinaus können
40 und wird an dieser Stelle fortgesetzt. Am Schluß dieses
}, ); beim Loopen mit DiskIn nur die im Header von AIFF-
4. Teils finden sich noch einmal Hinweise auf die Quellen
für die Software und ergänzendes Informationsmaterial. 20, "HD:Projekte:snd:sss" Files gespeicherten Loop-Punkte verwendet werden, eine
Das DiskIn Objekt muß innerhalb eines Synth erzeugt Veränderung der Loops während des Abspielens ist nicht
)
werden. Ihm werden das file und eventuell das optio- möglich.Außerdem wird beim Lesen einer größeren Anzahl
Soundfiles (Fortsetzung) ) nale Loop-Flag übergeben: Files von der Festplatte viel CPU-Kapazität verbraucht.
Ein per record bzw. write erzeugtes mehrkanaliges // Soundfile abspielen: Diese Probleme umgeht man durch das vorhergehende
Synth.play({
Soundfile kann möglicherweise nicht von jeder Audio- Laden der Files in den RAM. Die so geladenen Soundfiles
SoundFile.play("HD:Projekte:snd:sss") DiskIn.ar(file, true)
software geöffnet oder abgespielt werden, während es mit bzw. die entsprechenden Buffer können dann mit
Mono- oder Stereofiles in der Regel immer möglich sein })
Etwas komplizierter gestaltet sich das Lesen von Soundfiles PlayBuf hörbar gemacht werden.
sollte. In solchen Fällen kann man anstelle von record innerhalb von ugenGraph functions. Grundsätzlich gibt es
Im Falle eines Fehlers beim Lesen des Headers, würde hier ein Doch zuvor muß das komplette File mit Hilfe von read in
die Message recordMonoFiles verwenden, die in SC, wie auch in MAX/MSP, CSOUND und ähnlichen
vermeidbarer Folgefehler enstehen, weil DiskIn versuchen den Arbeitsspeicher eingelesen werden. Eigentlich müsste
anstelle eines mehrkanaligen Files entsprechend viele Programmen, 2 verschiedene Methoden, um Audiodateien
würde, ein fehlerhaftes oder nicht existierendes Soundfile zu an dieser Stelle ebenfalls ein Test erfolgen, auf den ich hier
Monofiles auf die Festplatte schreibt: zu importieren:
spielen! Deshalb ist es besser, den Synth mit DiskIn nur aber aus Platzgründen verzichte.
( 1. das unmittelbare Abspielen der Datei von der Festplatte dann zu starten, wenn das Lesen erfolgreich war.
var file;
f = Array.series(8, 100, 100); 2. das Einlesen der Datei in einen Buffer und anschließen- Hier das komplette Beispiel für das direkte Abspielen von
des Abspielen des Buffers file = SoundFile.new;
Synth.recordMonoFiles({ der Festplatte mit verbesserter Fehlerbehandlung:
file.read("HD:snd:klang");
SinOsc.ar(f)) Beide Methoden verwenden für den Zugriff auf die Dateien
das Objekt SoundFile. (
}, 5, "HD:Sinus") Die Message read bewirkt das Lesen des vollständigen
var file;
) In beiden Fällen besteht der erste Schritt zum Import von Soundfiles, das aus Header und Datenteil besteht.
Audiodateien darin, eine Instanz von SoundFile anzu- file = SoundFile.new;
PlayBuf erwartet nur die Audiodaten, jedoch nicht den
Hans Tutschku hat mit Hilfe von SUPERCOLLIDER ein legen: if ( Header. Daher müssen wir nun den Datenteil des Files in
eigenständiges Programm geschrieben, mit dem das file.readHeader("HD:snd:klang"), den eigentlichen signal buffer schreiben:
Splitten eines mehrkanaligen Soundfiles auch nachträglich var file;
{ Synth.play({
erfolgen kann. Darüber hinaus beherrscht dieses Pro- file = SoundFile.new; signal = file.data.at(0);
DiskIn.ar(file, true)
gramm auch das Zusammenfügen mehrerer Monofiles zu
})},
einem Mehrkanalfile sowie eine Reihe weiterer nützlicher Das direkte Abspielen von Soundfiles geschieht mit dem Der Datenteil, auf den mit data zugegriffen wird, ist ein
Bearbeitungsmöglichkeiten im Batchbetrieb, wie Filtern, Objekt DiskIn. Doch bevor wir unser file damit { „Lesefehler!“.postln } Array, dessen Elemente die Samples der einzelnen Kanäle
abspielen können, müssen wir noch den Pfad spezifizieren: );
Ein- und Ausblenden, Gleichspannungsfilter u.a. sind. Selbst bei Monosoundfiles ist der Datenteil ein Array,
) allerdings mit nur einem Element. Das erste Element - der
http://hanstutschku.multimania.com/ file.readHeader("HD:snd:klang") linke Kanal - trägt die Nummer 0. Mit at(index) kann
download/download.htm In der Version 2 von SUPERCOLLIDER darf das Lesen des man ein Element aus einem Array lesen. data.at(0)
Im Header eines Soundfiles stehen Informationen wie die Headers nicht bei aktiviertem Synth erfolgen. Das Lesen liest also immer den linken Kanal bzw. den einzigen Kanal.
Das Lesen von Soundfiles kann mit dem Objekt Samplingrate, das Sampleformat (Auflösung in bit), die von Dateien muß stets vorher vorgenommen werden! Die Audiosamples in einem Kanal werden als Signal
SoundFile und der Message play erfolgen. Hierzu ist Anzahl der Kanäle, Loop-Punkte, Originaltonhöhe und Möglicherweise wird man beim Ausführen dieses Beispiels interpretiert. Ein Signal in SC ist eigentlich nichts wei-
kein Synth nötig, da play in diesem Fall einen ent- Tastaturbereich sowie weitere Angaben. eine kleine Verzögerung zu Beginn wahrnehmen, die damit ter als ein Array für Fließkommazahlen.
sprechenden Synth selbst erzeugt. Auf diese Weise lässt Interessierte könnten sich diese Angaben nach dem Lesen zusammenhängt, daß das Lesen von der Festplatte immer
sich ein mit write „blind“ geschriebendes Soundfile mit In MAX/MSP heißt das entsprechende Objekt
des Headers anzeigen lassen: einige Zeit in Anspruch nimmt und grundsätzlich länger dau-
SC wieder abspielen. SoundFile.play ist besonders buffer~ , während PlayBuf in etwa die gleichen
ert, als das Lesen aus dem Arbeitsspeicher. Deshalb gibt es Aufgaben wie das MSP-Objekt groove~ übernehmen
gut für die unkomplizierte Wiedergabe mehrkanaliger file.dump; die Möglichkeit, mit preloadData einen kleinen Teil des
Soundfiles geeignet. kann: das Abspielen von im Buffer gehaltenen Samples.
Files zu laden, bevor es dann im Synth abgespielt wird.
30 SuperCollider SuperCollider 31
Das PlayBuf Objekt erwartet viele Argumente; die meis- Hier nun ein vollständiges Beispiel für das Laden von Objekte - Klassen und Instanzen Einige Eigenschaften von Objekten können mit der
ten dienen zur Modifzierung des Abspielens. Samples und das Abspielen aus dem RAM: SUPERCOLLIDER ist eine objekt-orientierte Programmier- Message dump summarisch angezeigt werden:
( sprache. Das heißt, Algorithmen werden durch
PlayBuf.ar(signal, sigSampleRate, 42.dump
Manipulationen von Objekten beschrieben.
playbackRate, offset, loopstart, var file, signal; -> Integer 42
loopend, mul, add) file = SoundFile.new; Ein Objekt ist etwas, das interne Zustände (Daten) hat und
verschiedene Verhaltensweisen bzw. Reaktionen (Opera- \a.dump
file.read("HD:snd:klang");
Die Parameter im Einzelnen: tionen) kennt. Objekte sind somit in gewisser Weise mit
signal = file.data.at(0); // 1. Kanal -> Symbol ‘a’
signal bezeichnet das Array bzw. den Buffer mit der realen physischen Objekten vergleichbar.
Synth.play({
abzuspielenden Wellenform. Alle Objekte, die sich durch gleiches Verhalten und gleiche [a, 2, 3.0].dump
var sr,transp,offset,start,end; interne Datenstruktur auszeichnen, gehören einer gemein-
sigSampleRate ist die Samplingrate in Hz. Hier kann -> Instance of Array { (...)
sr = file.sampleRate; samen Klasse an.
man beliebige Werte eintragen, die PlayBuf dann zur indexed slots [3]
Berechnung der Abspielgeschwindigkeit heranzieht. transp= LFNoise0.kr(1, 0.3, 1.0); Eine Klasse beschreibt die strukturellen Eigenschaften
offset = 0; 0 : nil
Sinnvoll ist allerdings entweder die Angabe der Sampling- (Daten und Operationen) von Objekten auf abstrakte
start = offset; 1 : Integer 2
rate aus dem Audiosetup oder besser die Samplingrate des Weise. Eine Klasse ist also eine Abstraktion einer Reihe von
gelesenen Soundfiles: file.sampleRate. Falls sich end = signal.size - 2; Objekten. 2 : Float 3
die für PlayBuf angegebene Samplingrate von der des PlayBuf.ar(signal, sr, transp, }
Dahingegen können Objekte als Realisationen einer Klasse
Synth unterscheidet, führt SC eine Konvertierung der offset, start, end); aufgefasst werden. In der objekt-orientierten Program-
Samplingrate in Echzeit durch. Instanzen können mit der Message new erzeugt werden,
}) mierung bezeichnet man solche konkreten Objekte als
playbackRate ist die relative Abspielgeschwindigkeit die dazu an das (abstrakte) Klassenobjekt geschickt wer-
) Instanzen einer Klasse.
bzw.. der Transpositionsfaktor. 1 bedeutet Originalton- den muss. Ein Klassenobjekt dient zur Notation der Klasse
Ein Beispiel: Die Zahl 42 ist, wie alles in SUPERCOLLIDER, selbst und nicht einer Instanz.
höhe, 0.5 eine Oktave tiefer und 2.0 eine Oktave höher. -1 PlayBuf ist auch in der Lage, mit mehrkanaligen ein Objekt. Genauer gesagt, 42 ist eine Instanz der Klasse
spielt das Sample rückwärts. Dieser Wert kann dynamisch Samples umzugehen. Hierfür übergibt man einfach anstel- Array, im folgenden Beispiel, ist also ein Klassenobjekt,
Integer (ganze Zahlen), also eine konkrete ganze Zahl.
verändert werden, z.B. von den diversen Controllern le eines Signals ein Array von Signals. PlayBuf ordnet während a eine neue Instanz der Klasse Array ist:
(Hüllkurvengeneratoren, Maus, MIDI, GUI etc.). dann, gemäß der Multichannelexpansion, jedem Signal- Zu welcher Klasse ein bestimmtes Objekt gehört, erfährt
buffer einen Output zu. Da file.data schon das man durch Senden der Message class an das Objekt: a = Array.new;
offset gibt den Startpunkt innerhalb des Sounds an.
Dieser Wert wird nicht in Sekunden sondern in Audio- gewünschte Array von Signals zurückgibt, ist eigentlich a.dump;
42.class.postln
samples, d.h. Abtastwerten, gemessen. Zum Beispiel nicht mehr viel zu tun. Vorsicht ist geboten bei der
-> Integer -> Instance of Array { ... }
bedeutet ein Wert von 1000 bei einer Samplingrate von Berechnung des Loopend-Punktes: signal.size gibt
44100 ca. 44 ms. Wer den offset statt in Samples lie- jetzt die Anzahl der Kanäle zurück! Wir können aber ein- Array.dump;
42.3.class.postln
ber in Sekunden angeben möchte, kann sich einer einfa- fach auf die Anzahl der Sampleframes im Soundfile zurück-
greifen. Hier das modifizierte Beispiel, das sowohl Mono- -> Float -> class Array (03F214E8) { ... }
chen Formel bedienen:
als auch Mehrkanalfiles verarbeiten kann:
Sekunden * file.sampleRate [a, 2, 3.0].class.postln Von fast jeder Klasse lassen sich Instanzen mit new erzeugen:
Die nach offset folgenden Looppunkte loopstart ( -> Array
und loopend werden ebenfalls in Audiosamples angege- var file, signals; b = SinOsc.new;
\a.class.postln
ben. Es ist wichtig zu wissen, daß sich PlayBuf im file = SoundFile.new; c = MouseX.new;
Gegensatz zu DiskIn immer im Loopmodus befindet. Das -> Symbol
file.read("HD:snd:klang");
heißt, man kann das Loopen nicht abschalten. Um einen signals = file.data; // alle Kanäle! `a.class.postln Aber für viele Klassen gibt es noch weitere Messages, die
Sound nur ein einziges Mal zu spielen, muß man sich ande- ebenfalls Instanzen erzeugen. Dies sind z.B. die bekannten
Synth.play({ -> Ref
rer Mittel bedienen, z.B. Hüllkurven - doch dazu später. Messages ar und kr, die bei den UGen-Klassen einge-
var sr,transp,offset,start,end;
Will man das komplette Soundfile loopen, stellt man 0 und "a".class.postln setzt werden:
sr = file.sampleRate;
signal.size-2 ein. Die Message size ermittelt die -> String
Größe eines Arrays. Das letzte Sample der Wellenform hat transp= LFNoise0.kr(1, 0.3, 1.0); b = SinOsc.ar;
den Index size-1. Für die Berechnung der Interpola- offset = 0; {}.class.postln
c = MouseX.kr;
tionen bei der Transposition mit PlayBuf muß der loo- start = offset; -> Function
pend-Punkt vor dem letzten Audiosample liegen. Daher end = file.numFrames - 2; Auch Synth ist eine Klasse bzw. ein Klassenobjekt. Die
size-2. SinOsc.new.class.postln
PlayBuf.ar(signals, sr, transp, Message play (mit ihren Argumenten) erzeugt eine
Die Looppunkte können auch in Echtzeit moduliert werden. -> SinOsc Instanz der Klasse Synth:
offset, start, end);
mul und add sind die schon von anderen Audioobjekten })
Synth.play(...)
bekannten Modulationsparameter. )
32 SuperCollider SuperCollider 33
Instanzen können weiterhin auch durch spezielle Dazu einige Beispiele: Functional und Receiver Notation Die unterschiedliche Bedeutung der Argumente von
Sonderzeichen erzeugt werden - solche direkt notierten Messages können auf verschiedene Arten notiert werden. round wird durch die Receiver-Schreibweise deutlicher:
Instanzen nennt man Literale: // Message new an ein Klassenobjekt:
Aus anderen Programmiersprachen ist die funktionale
a = List.new; 1.267.round(0.1).postln
Schreibweise geläufig, die wir auch in SC schon oft ver-
\bla.dump -> 1.3
// Message add an eine Instanz: wendet haben. Hier wird die Message als Funktion aufge-
-> Symbol 'bla' faßt, der man in der Regel eine oder mehrere Argumente
b = a.add(3); Diese gemischte Schreibweise ist in SC überall anzutreffen,
$c.dump übergeben muß: insbesondere bei den UGen-Objekten, z.B.
// Message postln an eine Instanz:
-> Character 99 'c'
b.postln; // function(arguments) SinOsc.ar(440)
Auch Zahlen und spezielle Werte sind Instanzen einer -> List[ 3 ] sqrt(6)
oder
Klasse und im Programmtext zugleich Literale:
sin(pi)
Synth.play({ Saw.ar(200) })
7.class.postln // Message series an ein Klassenobjekt: midicps(60)
-> Integer c = Array.series(5, 60, 3); postln("Hallo!") Man könnte stattdessen tatsächlich auch schreiben:
7.0.dump // Message postln an eine Instanz: min(6, pi) ar(SinOsc, 440)
-> Float 7.0 c.postln;
rand(10) bzw.
pi.dump -> [ 60, 63, 66, 69, 72 ] rrand(10, 20)
-> Float 3.14159 play(Synth, { ar(Saw,200) })
// Message midicps an eine Instanz:
true.class.postln Tatsächlich sind die Argumente dieser Funktionsaufrufe play und ar können also auch als Funktionen mit
c.midicps.postln; eigentlich Objekte. Deshalb lassen sich diese Ausdrücke
-> True Argumenten aufgefaßt werden
-> [261.626,311.127,369.994,440,523.251] auch durch eine gleichwertige Message-Receiver-
inf.class.postln Schreibweise darstellen: Der Vorteil der Receiver-Notation wird vor allem dann
-> Infinitum ersichtlich, wenn man eine Folge aufeinander bezogener
// Message new an ein Klassenobjekt: // receiver.message Operationen notieren will. Im folgenden Beispiel soll
d = Point.new(10, 50); Betrag des Sinus von 4.3 angezeigt werden. Hier zuerst in
Messages und Methoden 6.sqrt Form von Funktionen notiert:
// Message value an eine Instanz:
Das Verhalten eines Objekts wird in Form von Methoden pi.sin
beschrieben. Eine Methode ist eine klasseninterne d.value.postln; postln(abs(sin(4.3)))
Funktion, die die Reaktion eines Objekts definieren, wenn 60.midicps
-> 11.3214 Die Reihenfolge der Operationen - und damit der Aufbau
das Objekt eine Message gleichen Namens empfängt. "Hallo!".postln
des Ausdrucks selbst - wird aber wesentlich besser lesbar,
Eine Message ist also eine Anforderung an ein Objekt (den 10.rand wenn man die Message-Receiver-Schreibweise verwendet:
Receiver), eine bestimmte Operation auszuführen. Die // Message new an ein Klassenobjekt:
Methoden können interne Zustaende ändern, Werte e = Point.new(100, 122); Allerdings kann eine einzelne Message nicht an 2 Objekte 4.3.sin.abs.postln
berechnen und diese zurückgeben. // Messages x und y an Instanzen: gleichzeitig geschickt werden, wie das z.B. im Falle der min-
Die Operationen werden streng von links nach rechts abge-
Einige Methoden, besonders solche zum Erzeugen einer [ e.x, e.y ].postln; Funktion notwendig wäre. In so einem Fall schickt man die
arbeitet. Man beachte, daß die Message abs nicht etwa an
Instanz, wie z.B. new, sind nur für die Klasse selbst defi- Funktion als Message nur an das erste Objekt. Alle weiteren
sin geschickt wird, sondern an das Ergebnis von
niert. Solche Methoden werden Klassenmethoden ge- -> [ 100, 122 ] Objekte werden als Argumente der Message übergeben:
4.3.sin, also an eine Instanz von Float.
nannt. Typische Klassenmethoden sind neben new auch
ar, kr, play oder record, wie bereits oben gezeigt // receiver.message(arguments) Diese Punkt-Schreibweise hat eine gewisse Ähnlichkeit mit
// Message an ein Klassenobjekt: der Notation einfacher mathematischer Ausdrücke, wie:
wurde. Der Receiver einer solchen Message darf nur ein 6.min(pi)
Klassenobjekt sein. Wäre der Receiver eine Instanz einer Synth.sampleRate.postln;
10.rrand(20) 5 * 3 - 4 / 7
Klasse, würde man in den meisten Fällen eine Fehler- -> 44100
meldung erhalten. Auch hier gilt die Abarbeitung von links nach rechts. Die
// Message an ein Klassenobjekt: Einige Funktionen sollten besser in der Receiver-Notation
Andere Methoden beziehen sich nur auf Instanzen einer übliche Vorrangregel „Punktrechnung geht vor Strich-
f = Synth.new({SinOsc.ar}); geschrieben werden, weil sie so einfacher zu lesen sind.
Klasse und können nicht gleichzeitig auf Klassen bzw. rechnung“ gilt in SC nicht! Es wird also nicht zuerst 5*3
Beispiel: Die round-Funktion rundet ihr 1. Argument auf
Klassenobjekte angewendet werden. Solche Messages // Message an eine Instanz: und 4 / 7 gerechnet und dann subtrahiert! Die tatsächli-
ein Vielfaches des 2. Arguments:
setzen also voraus, daß eine entsprechende Instanz mit f.dump; che Reihenfolge könnte man so darstellen:
Hilfe eines Klassenobjekts und einer Klassenmethode round(1.267, 0.1).postln
-> Instance of Synth { ... } (((5 * 3) - 4) / 7)
bereits erzeugt wurde. -> 1.3
34 SuperCollider SuperCollider 35
Die beiden einzigen Abweichungen von der ansonsten all- verwenden müssen. Dies drückt sich auch in der üblichen Eine Übersicht über Operatoren für Zahlen erhält man im Ein Bag ist eine ungeordnete Menge von Objekten:
gemein gültigen Reihenfolge „links vor rechts“ sind: Darstellung als Flußdiagramm aus: Helpfile für SimpleNumber. Für listenähnliche Objekte
b = Bag[ 1, 2, 3, 4, 3, 2 ];
1. geklammerte Ausdrücke enthält die Hilfe zu SequenceableCollection eine
b.dump;
Aufzählung solcher Operatoren. BinaryOpUGen und
2. Funktionen bzw. Messages -> Instance of Bag { ... }
UnaryOpUGen haben ebenfalls Helpfiles. Eine kurzge-
Beide werden vorrangig behandelt. faßte Übersicht findet man auch am Anfang der b.size.postln;
Hier werden zuerst 5*3 und die Wurzel von 2 berechnet, [UGen_Ref_Sheet] - Liste. -> 6
dann werden beide Ergebnisse und 100 addiert und die
Summe anschließend angezeigt: Collections, Arrays, Listen und Iterationen Ein Set ist eine ungeordnete Menge von Objekten, die kei-
Im Verlaufe dieses Kurses sind wir schon mehrfach auf ne gleichen Objekte enthält. Aus diesem Grunde ist im fol-
(100 + (5 * 3) + 2.sqrt).postln listenähnliche Objekte gestoßen, zum Beispiel: genden Beispiel die Größe von c nicht 6 sondern 4, da nur
das Signal aus dem vorangegangenen Abschnitt über 4 unterschiedliche Elemente vorhanden sind:
Binäre und unäre Operatoren Soundfiles ist eigentlich ein FloatArray, also ein Array c = [ 1, 2, 3, 4, 3, 2 ].asSet;
Auch die einfachen mathematischen Operatoren +, -, * von Fließkommazahlen, c.dump;
und / sind eigentlich Funktionen bzw. Messages. Sie kön- der Datenteil eines mehrkanaligen Soundfiles wird von SC -> Instance of Set { ... }
nen allerdings nicht in einer der beiden oben besproche- als Array von Signals aufgefaßt,
nen Schreibweisen notiert werden. c.size.postln;
im Abschnitt über Controls haben wir ein Array mit MIDI-
-> 4
Diese Operatoren werden binäre Operatoren genannt, weil In SC gibt es eine beeindruckende Fülle von nützlichen Notennummern für einen Sequencer benutzt,
sie 2 Ausdrücke miteinander verknüpfen. Binäre Operatoren für UGens, die eine sehr effiziente Lösung ver- im Abschnitt über Multichannelexpansion wurde ein Array Eine andere Methode ist z.B. add, mit der ein neues
Operatoren werden in der Regel mit Sonderzeichen, wie schiedener Probleme erlauben. Zum Beispiel läßt sich mit mit Zufallswerten für die Frequenz eines Saw generiert. Element der Gruppe hinzugefügt werden kann.
! @ % & * - + = | < > ? / dargestellt. dem binären Operator trunc ein einfacher LoFi- Arrays sind nur eine von mehreren möglichen Daten-
Quantisierer konstruieren, mit dem man die Auflösung bzw. strukturen, mit denen gleichartige oder verschiedene a = [ 1, 2, 3, 4, 3, 2, 1, 2 ];
a + b // Summe
Sample-Wortbreite von 16bit auf geringere Werte dyna- Objekte auf unterschiedliche Weise zusammengefaßt wer- a.add(7);
c > d // ist c groesser als d ? misch reduziert kann: den können. Beispielsweise sind ungeordnete Mengen in a.postln; // a ist ein Array!
e % f // e modulo f der Mathematik etwas ganz anderes als Listen: erstere ent- -> [ 1, 2, 3, 4, 3, 2, 1, 2 ]
( // Quantisierung eines Sinustons
halten Elemente ohne irgendeine Reihenfolge oder Hier-
g ** h // g hoch h
Synth.scope({ archie, letztere bestehen aus einem ersten Element und b = [ 1, 2, 3, 4, 3, 2, 1, 2 ].asBag;
SinOsc.ar(200, 0, 0.9) einem Zeiger auf eine Subliste, die wiederum aus einem b.add(7);
In SC können Funktionen, die 2 Argumente erwarten, auch
ersten Element und einer Subliste besteht usw.
als binary operators notiert werden, indem dem Bezeichner trunc: b.postln;
ein Doppelpunkt nachgestellt wird: Das allgemeinste Objekt zum Gruppieren anderer Objekte -> Bag[ 1, 1, 2, 2, 2, 3, 3, 4, 7 ]
(2 ** MouseY.kr(0, -15));
in SUPERCOLLIDER ist die Collection.
10 rrand: 20 }, 0.01) c = Set[ 1, 2, 3, 4, 3, 2, 1, 2 ];
Collection ist eine abstrakte Klasse, d.h. von ihr kann
) keine Instanz gebildet werden. Collection dient ledig- c.add(7);
6 min: pi
lich zum Deklarieren allgemeiner Methoden für die Sub- c.postln;
Die Position auf der MouseY-Achse gibt hier die 2er- klassen von Collection. Subklassen von
Die (mathematischen) Funktionen, die nur ein Argument -> Set[ 1, 2, 3, 4, 7 ]
Potenz an, die als Quantisierungsraster des Truncation- Collection sind z.B. Set, Bag, Array, Signal,
erwarten, heißen unäre Operatoren.Also sind sqrt, sin,
Operators verwendet wird. Bei einem MouseY-Wert von Wavetable, List u.a.
abs, rand usw. unary operators. d = List[ 1, 2, 3, 4, 3, 2, 1, 2 ];
-2 ergibt sich beispielsweise eine Genauigkeit von 1/4 für
Alle Subklassen von Collection kennen z.B. eine d.add(7);
Binäre und unäre Operatoren sind nicht nur für einfache die Momentanwerte einer Wellenform, d.h. wir erhalten 4
Methode size, die die Anzahl der Elemente in einer d.postln;
Objekte, wie Zahlen, oder für zusammengesetzte Objekte, positive und 4 negative Stufen, also ingesamt 8
Instanz ermittelt.
wie Arrays, definiert. Sie gelten auch für UGens: hier wer- Quantisierungsstufen (3 bit): -> List[ 1, 2, 3, 4, 3, 2, 1, 2, 7 ]
den sie selbst unit generators, sogenannte BinaryOp- Ein Array ist eine nicht erweiterbare geordnete Menge
UGens bzw. UnaryOpUGens.
von Objekten: Bei Array funktioniert das Hinzufügen von 7 nicht, weil
Arrays nicht ohne weiteres vergrößert werden können!
Das Plus-Zeichen in diesem Beispiel erzeugt einen UGen, a = [ 1, 2, 3, 4, 3, 2 ];
Die Reihenfolge der Objekte in Set und Bag ist nicht die
der beiden Sinussignale addiert. a.dump; gleiche, wie bei der Deklaration! Dagegen ist List erwei-
-> Instance of Array { ... } terbar und enthält alle Elemente in ursprünglicher Reihen-
SinOsc.ar(200) + SinOsc.ar(233)
a.size.postln; folge. Es existieren viele in dieser und ähnlicher Weise spe-
In älteren Synthesesprachen, wie z.B. MusicV, hätte man zialisierte Subklassen von Collection.
-> 6
an dieser Stelle tatsächlich ein besonderes add-Modul
36 SuperCollider SuperCollider 37
Die Subklassen von Collection sowie die von ihnen Auf ein einzelnes Element eines Arrays, einer Liste oder Dazu noch 2 weitere Beispiele mit Soundfiles.
abgeleiteten weiteren Subklassen kann man sich durch die einer anderen geordneten Collection kann mit der Das nachfolgende Patch schreibt 50 durchnumerierte
Message dumpClassSubtree anzeigen lassen: Message at(index) zurückgegriffen werden, wobei Soundfiles à 20 Sekunden Dauer mit je unterschiedlichen
das erste Element eines Arrays den Index 0 hat. Tonimpulsfolgen auf die Festplatte:
Collection.dumpClassSubtree Mit put(index,item) kann ein Element überschrie-
ben werden: (
(siehe nebenstehende Abbildung ->)
x = Array.series(5, 7, 3); n = Array.rand(50, 80.0, 100.0);
Zu den wichtigsten subclasses von Collection
x.at(2).postln; n.do({arg pitch, nr;
gehören sicherlich Array und List.
x.put(2,333); Synth.write({
Instanzen von Array haben, wie bereits angedeutet wur-
de, eine einmal festgelegte Größe und eignen sich daher x.at(2).postln; SinOsc.ar(pitch.midicps, 0,
nicht für eine dynamische Erweiterung. Es gibt eine Reihe -> 13 Decay2.ar(Dust.ar(20),
interessanter Klassenmethoden, mit denen sich solche -> 333 0.01, 0.2, 0.5))
Instanzen erzeugen lassen: },20,"HD:snd:Schrabbel-" ++ nr)
Mit choose kann ein Element an einer zufälligen Position })
Array.series(5, 7, 3).postln; herausgelesen werden: )
-> [ 7, 10, 13, 16, 19 ]
x = "Dies ist ein String";
Im zweiten Beispiel iteriert do über eine Liste mit File-
Array.geom(5, 2, 1.5).postln; x.choose.postln;
namen. In jedem Schleifendurchlauf wird der jeweilige
-> [ 2, 3, 4.5, 6.75, 10.125 ] x.choose.postln; Header gelesen, die Dauer ermittelt und das File abgespielt:
-> n
Array.rand(5, 50, 80).postln; (
-> e
-> [ 78, 66, 64, 75, 53 ] var path, files;
Array, List, String und andere Collections kennen path = "HD:snd:fx:";
Mit fill(n,function) kann die Regel zum Er- den Konkatenator ++ zum Zusammenfügen:
zeugen des Arrays selbst programmiert werden. Diese files = [ "slap", "bang", "boom",
Regel schreibt man in Form einer Funktion, die als s = ("Alles " ++ "klar" ++ "?"); "clap", "crash"];
Argument den Index des gerade zu erzeugenden Elements s.postln; files.do({arg name;
übergeben bekommt. Das folgende Beispiel erzeugt 5 auf- a = Array.rand(5, 10, 30); var snd, dur;
einanderfolgende Primzahlen und ihre Ordnungszahlen als snd = SoundFile.new;
b = a ++ s.last;
Paare in einem Array beginnend mit Primzahl Nummer 8:
b.postln; snd.readHeader(path ++ name);
p = Array.fill(5, dur = snd.duration;
-> Alles klar?
{ arg i; [i+8, (i+8).nthPrime]; }); ("Playing " ++ name).postln;
-> [ 13, 10, 26, 18, 29, ? ]
p.postln; SoundFile.play(
-> [[8,23],[9,29],[10,31],[11,37],[12,41]] path ++ name, duration: dur);
Für die Bearbeitung aller Elemente einer Collection stehen
});
eine Reihe von Methoden zur Iteration bereit.
Listen, als erweiterbare Collections, lassen sich so wie Arrays )
mit series, geom, rand und ähnlichen Messages erzeu- do wendet die übergebene Funktionen nacheinander auf
gen. Man kann aber auch mit einer leeren Liste beginnen alle Elemente einer Collection an. Um innerhalb der Funk- Ein anderer Iterator ist collect. Diese Methode wendet
und diese später mit add(item) erweitern: tion Zugriff auf das aktuelle Element zu erhalten, übergibt ebenso wie do eine Funktion nacheinander auf alle
do dieser Funktion bei jedem Aufruf 2 Argumente: 1. das Elemente an und sammelt die Resultate der Funktions-
a = List.new; Element selbst, 2. dessen Index (beginnend mit 0): aufrufe in einer neuen Liste:
a.add(5);
h = [pi, 40, "Hi", 3.77]; k = [1,2,3,4,5].collect(
a.add("sc");
h.do({arg item,i; [i,item].postln;}); { arg item, i; item + 10 });
a.add(Array.geom(4, 1, 1 + 5.sqrt/2));
a.postln; -> [ 0, 3.14159 ] k.postln;
[ 1, 40 ] -> [ 11, 12, 13, 14, 15 ]
-> List[5,sc, [1,1.61803,2.61803,4.23607]]
[ 2, Hi ]
[ 3, 3.77 ]
38 SuperCollider SuperCollider 39
SequenceableCollections und ihre Subklassen ver- Filter Hier ein Vergleich zwischen LPF und RLPF: Durch Ringz wird der Umgang mit Resonatoren verein-
stehen darüber hinaus auch unäre und binäre Operatoren, In SUPERCOLLIDER steht eine große Auswahl verschiede- facht, wenn es auf das Zeitverhalten der Filter ankommt:
die ebenfalls auf alle Elemente angewendet werden. Die ner Filter zur Verfügung. Einen (unvollständigen) Überblick
Resultate werden wie bei collect gesammelt: (
erhält man im Helpfile [Filters]. Für die vollständige
Liste schlägt man im [UGen_Ref_Sheet] nach. Synth.play({
[1,2,3,4,5,6].even.postln; var decay;
Neben elementaren Filtern mit 1 und 2 Polen bzw. Null-
-> [false,true,false,true,false,true] decay = XLine.kr(0.01, 2.0, 10.0);
stellen, die eher als Bausteine für eigene DSP-Algorithmen
([1,2,3,4] * 10).postln; betrachtet werden können, finden sich auch eine ganze Ringz.ar(Dust.ar(5), 2000, decay)
Reihe von gebrauchsfertigen Tief-, Hoch- und Bandpässen })
-> [ 10, 20, 30, 40 ] sowie Resonatoren. )
([1,2,3,4] * (10**[0,1,2,3])).postln; Ein (Butterworth-) Tiefpaß hat folgende Parameter:
Das am Ende des vorhergehenden Abschnitts bereits er-
-> [ 1, 20, 300, 4000 ] LPF.ar(in, freq, mul, add) wähnte Objekt Klank stellt eine Resonanzfilterbank dar.
( Ähnlich wie die Oszillatorbank Klang, erwartet Klank
Der Input in des Filters nimmt jegliches Audiosignal auf. Synth.fftScope({ eine Referenz auf ein Array von 3 Arrays mit den Resonanz-
Es existiert neben den hier besprochenen Methoden eine
Vielzahl weiterer Iteratoren und anderer Methoden zum Die Grenzfrequenz freq kann sich theoretisch mit jeder LPF.ar(WhiteNoise.ar(0.3), 5000) frequenzen, den Amplituden und den Abklingzeiten (siehe
Geschwindigkeit ändern, ar oder kr oder konstant. }) Ringz). Auch bei Klank sind die Parameter nicht verän-
Bearbeiten von Collections, über die man sich im Helpfile
zu Collection und dessen Subklassen informieren Jedoch ist bei Filtern in dieser Hinsicht Vorsicht geboten: ) derlich, dafür sind wesentlich mehr Resonatoren erzeug-
kann. wenn die Koeffizienten bzw. Filterfrequenz und Güte sich bar, als mit Resonz bzw. Ringz.
sehr schnell ändern, kann es zu Instabilitäten kommen, die Klank eignet sich gut zum Simulieren resonanter physika-
Einige UGens erwarten als Argumente bzw. Parameter sich z.B. als Übersteuerung äußern können.
Arrays von Objekten statt einfacher Objekte. Einen solchen lischer Objekte:
UGen haben wir mit Mix bereits kennengelernt. Ein ande- Das Butterworth-Bandpaß-Filter besitzt neben der Mitten-
frequenz auch eine Filtergüte bzw. einen Quality-factor, der (
res Objekt, das Arrays als Parameter erfordert, ist die
Oszillatorbank Klang. Im Gegensatz zum SinOsc kön- in SC allerdings immer als Kehrwert rq der Güte angege- var pt, amp, dec, freq, in;
nen deren Frequenzen und andere Parameter nicht dyna- ben werden muß:
n = 20;
misch verändert werden. Dafür lassen sich mit Klang freq = 100.0;
BPF.ar(in, freq, rq, mul, add)
aber wesentlich mehr Oszillatoren in Echtzeit berechnen
pt = Array.fill(n, {arg i;
als mit SinOsc.
rq kann auch als relative Bandbreite verstanden werden: 1.27**(i+1) * (1 + 0.03.rand2) });
Die Werte für die Frequenzen,Amplituden und Phasen wer- je selektiver das Filter arbeiten soll, um so kleiner muß rq amp = Array.rand(n, 0.1, 1.0);
den als Reference auf ein Array von 3 Arrays notiert. sein. Bei einer Mittenfrequenz von 500 Hz und einer
Das Referenzieren ist notwendig, um die Multichannel- ( dec = Array.geom(n, 1.0, 0.8);
gewünschten Bandbreite von 10 Hz muß also 0.02 für rq
expansion zu verhindern, die sonst automatisch beim eingetragen werden: Synth.fftScope({ Synth.scope({
Übergeben von Arrays als Parameter in Kraft treten würde. RLPF.ar(WhiteNoise.ar(0.3),5000,0.1) in = AudioIn.ar(1);
( }) Klank.ar(`[pt*freq, amp/n, dec], in);
( Synth.play({ ) })
var freq, amp, ph; BPF.ar(AudioIn.ar(1), 500, 0.02)
)
n = 100; })
Neben dem Butterworth-Bandpaß gibt es auch den klassi-
freq = Array.rand(n, 200.0, 500.0); )
schen Resonator, der sich von BPF geringfügig in der Fortsetzung folgt
amp = Array.rand(n, 0.1, 1.0) / n; Durchlaßcharakteristik unterscheidet, aber ansonsten die
Der klassische Synthesizer-Tiefpaß kennt auch eine Reso-
ph = Array.rand(n, 0.0, 2*pi); gleichen Parameter besitzt.
nanz. In SC nennt sich das entsprechende Objekt RLPF: Bezugsquellen
Synth.play({ Resonz.ar(in, freq, rq, mul, add) Die SUPERCOLLIDER Homepage:
RLPF.ar(in, freq, rq, mul, add)
Klang.ar(`[freq, amp, ph]); www.audiosynth.com
}) Die Bandbreite eines Resonators kann auch durch die
Die Parameter sind genau die gleichen wie beim Bandpaß. Weitere Informationen, Beispiele, Tutorials etc:
) Abklingzeit determiniert werden. Ringz (ringing filter)
Im Unterschied zum normalen Tiefpaß gibt es beim reso- swiki.hfbk-hamburg.de:8080/
stellt hierfür einen entsprechenden Zeitparameter (in
nanten Tiefpaß bei der Grenzfrequenz eine Anhebung im MusicTechnology/6
Ein ähnliches Objekt, Klank, lernen wir im nächsten Sekunden) anstelle der inversen Güte rq bereit.
Frequenzgang, die umgekehrt proportional zum rq ist.
Abschnitt über Filter kennen.
Ringz.ar(in,freq,decaytime,mul,add) Andre Bartetzki
42 SuperCollider SuperCollider 43
So eine Filterantwort auf einen Impuls kann als Grain auf- (
S u p e r C o l l i d e r gefasst werden, wobei hier keine Grains überlagert werden
Synth.record({
müssen, wie das bei FOF der Fall ist, da bei Formlet das
Einführung in die Programmierung, Teil 5 Filter immer nur neu angestoßen werden muß. Mix.ar(
Die zeitliche Dichte der Impulse, die als Input übergeben // Grundtonsteuerung:
Dieser mehrteilige Einführungskurs zum Softwaresynthe-
sizer SUPERCOLLIDER (SC) begann in den Mitteilungen werden, bestimmt den Tonhöheneindruck, während freq XLine.kr(2.0, 200.0, 6),
40 und wird an dieser Stelle fortgesetzt. die Mittenfrequenz des Formanten steuert. Ein- und
Ausschwingzeit beeinflussen schließlich Bandbreite und 0.5),
Informationen zum Download der Version 2 sowie zum
Form des Formanten: lange Zeiten entsprechen geringen // Formantfrequenzen:
Stand der neuen Version 3 für MacOSX siehe im grauen
Bandbreiten und umgekehrt.
Kasten am Ende des Artikels. [750, 1200, 2800, 3800],
Zur Illustrierung dieses FOF-Filters nun das klassische
Filter (Fortsetzung) Beispiel einer beschleunigten Tonimpulsfolge, die sich zu // Attack, Decay:
Zum Abschluß des Überblicks über die Filter in SC soll
Die Filtersektion von SUPERCOLLIDER ist sehr vielseitig. einem A-Vokalklang verdichtet. Dazu werden hier 4 paral- 0.005, 0.03,
Formlet besprochen werden. Mit Formlet wird die
Außer den bereits vorgestellten im Bereich der Klang-syn- lele Formlets erzeugt, die mit den Frequenzen und den
bekannte FOF-Synthese, eine Variante der Granular- // relative Amplituden:
these üblichen Tief-, Hoch- und Bandpassfiltern sowie den Amplituden der ersten vier Formanten des Vokals A einer
synthese, in Form eines Filters implementiert. Bei der FOF-
verschiedenen Versionen von Resonatoren finden wir eine durchschnittlichen Tenorstimme versehen werden. [0,-6,-16,-24].dbamp)
Synthese (FOF = Fonction d’ondes formantiques) werden
Reihe von Filtern, die in ihrer aus der Theorie der Signal- Während die Formanten fixiert bleiben, erhöht sich der
synthetische Grains mit spezieller Hüllkurvenform peri- )
verarbeitung stammenden „Rohfassung“ vorliegen. Dazu „Grundton“, d.h. die Frequenz der Impulsfolge, im Verlauf
odisch erzeugt. Die Anzahl der Grains pro Sekunde ergibt
gehören FOS, SOS, OnePole, OneZero, TwoPole, von 6 Sekunden von 2 auf 200 Hertz. }, 6, "ah.snd")
den Grundton des neuen Klangs, während die Frequenz der
und TwoZero, bei denen als Parameter statt Frequenz Sinuswelle im Innern der Grains als Formant gehört wird. Die untenstehende Abbildung enthält ein mit AUDIO- )
oder Filtergüte die Koeffizienten des zugrundeliegenden FOF ist also gut geeignet zur Synthese von Sprachlauten SCULPT angefertigtes Sonagramm des Resultats. Die dun-
linearen Filtermodells angegeben werden müssen. Weitere und anderen Klängen, bei denen es auf die genaue klen horizontalen Linien markieren die Orte der 4 Forman-
Filter stellen Spezialfälle der eben aufgezählten dar - bei Kontrolle von Formanten ankommt. Dieses Verfahren wur- ten. Das rechtsseitige Spektrum zeigt die Verhältnisse bei
ihnen entfallen auch die Koeffizienten als Parameter. Zu de mit der am IRCAM entwickelten Software CHANT etwa 5 Sekunden (an der Position des Mauszeigers).
diesen unveränderlichen Filtern gehören LPZ1 und LPZ2 populär. Ein solcher FOF-Generator steht auch in SC zur
(beides Tiefpässe bzw. Mittelwertfilter), HPZ1 und HPZ2 Verfügung und heißt hier Formant.
(Hochpässe bzw. Differenzfilter) sowie BPZ2 und BRZ2
Mit Formlet werden im Gegensatz zu FOF keine Grains
(Bandpass und -sperre). Andere Filter, wie Integrator
generiert. Vielmehr legt Formlet eine spezielle
(Tiefpaß), Slope, Decay und Median, finden ihre
Impulsantwort fest, die der eines engen Resonators mit
Verwendung eher bei der Modifikation von Controller-
einstellbarer Ein- und Ausschwingzeit entspricht. Diese
signalen und weniger bei der direkten Klangbearbeitung.
Impulsantwort kann nun durch impulsförmige Eingangs-
LeakDC dient zur Entfernung von Gleichspannungsantei- signale „getriggert“ werden. Das Ergebnis sind kurze
len im Signal, ist also eine Art Hochpass. Der Koeffizient Tonimpulse, von McCartney Formlets genannt:
von LeakDC regelt die „Grenzfrequenz“ des Hochpasses:
Werte nahe 1.0 entsprechen einer sehr geringen Grenz-
frequenz, kleinere Werte wirken sich wie höhere Grenz-
frequenzen aus. Das folgende Patch zeigt, wie der auf den
Sinuston addierte Offset von 0.5 (Argument add von
SinOsc) durch LeakDC wieder entfernt wird. Links das
Signal mit Offset, rechts die gleichspannungsfreie Version:
( { var sig;
sig = SinOsc.ar(700, 0, 0.3, 0.5);
[sig, LeakDC.ar(sig, 0.995)]
}.plot)
44 SuperCollider SuperCollider 45
Alle bisher vorgestellten Synthese-Patches hatten ein Da die Beschreibung einer Wellenform noch nichts mit Der hier verwendete Osc1 ist ein spezieller Oszillator, der Endpunkte sowie ihren Kurvenverlauf spezifiziert. Alle
gemeinsames Merkmal, egal im welchen Aspekt von ihrem „Abspielen“ zu tun hat, kann sie außerhalb der anstelle anstelle einer Wavetable-Instanz ein Signal Werte, Zeiten und Kurvenformen werden in je einem Array
Synthese oder Sampling es sich dabei handelte: was ugenGraph function erfolgen, während der Oszillator als erwartet und diese Tabelle nur einmal abspielt. zusammengefasst:
erzeugt wurde, war stets nur ein einziger Klang oder eine UGen natürlich nur in einem Synth funktionieren kann: Als zweites Argument wird anstelle der Frequenz die
Textur von gleichartigen Klängen. Mit Ausnahme von Abspieldauer in Sekunden erwartet. *new(levels, times, curves, ...)
Sequencer im Verbund mit Decay und ähnlichen (
Die Klasse Signal, die wir schon im Zusammenhang mit Betrachten wir eine konkrete Hüllkurve und ihre
Objekten haben wir bisher noch keine Methode kennen- var w; Samples und PlayBuf kennengelernt haben, stellt Spezifikation mit Env.new. Zunächst die grafische
gelernt, mit einem Patch mehrere Klangereignisse nach- w = Wavetable.sineFill(512, einige Fensterformen (wie hamming, hanning oder Repräsentation:
einander oder unabhängig voneinander generieren zu Array.geom(20, 1, 0.5)); welch) für die Fourieranalyse bereit, die auch als Hüll-
können. In gewisser Weise ist uns SUPERCOLLIDER bisher kurven für die Granularsynthese nützlich sein können.
Synth.play({
als ein mit vielen Reglern ausgestatteter Synthesizer bzw.
Sampler erschienen, dem aber eine Tastatur zum Spielen zu Osc.ar(w, 440);
(
fehlen scheint! })
s = Signal.hanningWindow(512);
Die Generierung von Events, seien es mehrere Samples, ) s.plot;
Töne oder Grains, erfolgt in SUPERCOLLIDER mit Hilfe von Synth.scope({
Spawn oder einigen anderen davon abgeleiteten Klassen. Man kann nun solche Wellenformen auch als Amplituden-
a = Osc1.ar(s, 2);
Im engen Zusammenhang mit der Erzeugung von Klang- Hüllkurven benutzen. Dazu muss der Wertebereich ange-
Saw.ar(400, a);
ereignissen steht natürlich die Gestaltung ihrer Hüllkurven passt werden, der sich bei Wellenformen typischerweise
}, 2)
mit Env und EnvGen, die darüber hinaus auch für das symmetrisch um den Nullpunkt von -1.0 bis 1.0 erstreckt,
aber bei Hüllkurven nur von 0.0 bis 1.0 gehen sollte. )
korrekte Funktionieren von Spawn wichtig werden.
Für die zeitliche Organisation und die algorithmische Außerdem wird eine Hüllkurve in der Regel nicht periodisch
wiedergegeben, sondern nur einmal durchlaufen: In runden Klammern sind hier die Koordinaten der
Strukturierung von Events zu musikalischen Ereignissen,
Eckpunkte als Paar (Wert, Zeit) angegeben. Zu beachten
wie Rhythmen, Motive, Melodien, Akkorde usw. steht eine
( ist, daß die Zeit die Dauer eines Segments wiedergibt und
umfangreiche Bibliothek von Pattern zur Verfügung, die
nicht die absolute (von Beginn an gerechnete) Zeit dieses
fast schon so etwas wie eine eigene kleine Sprache inner- w = Wavetable.sineFill(1024, Punktes. Die absolute Zeit ist hier trotzdem zum Vergleich
halb von SC darstellen.Auch die konventionelle Steuerung 1.0/[1,2,3,4,5,6]); unter der Zeitachse eingetragen.
von Events über MIDI-Note-Messages wird von SC unter-
w = w.asSignal.abs; Das Array der Level sieht in diesem Fall so aus:
stützt.
w.plot; [0, 1, 0.2, 0.5, 0]
Hüllkurven und Spawner sind die Voraussetzungen für die
Synth.scope({
Programmierung von Events und Pattern. Beschäftigen wir Das Array der Zeiten enthält nur 4 Elemente - ein
uns daher zunächst mit der Beschreibung und Erzeugung a = Osc1.ar(w, 2); Dauernwert für jedes Segment:
von Hüllkurven bevor wir uns dann Spawn und seinen Saw.ar(400, a); [0.01, 0.04, 0.1, 0.05]
Verwandten zuwenden: }, 2)
Da die Form der Segmente hier gleichbleibend linear ist,
) muß anstelle der Kurvenformliste nur das Symbol
Envelopes
'linear'eingetragen werden.
Die Arbeit mit Hüllkurven ist in SC ein zweistufiger
Prozess: zuerst muß die Form einer Hüllkurve beschrieben Die Spezifikation dieser Hüllkurve sieht nun so aus:
Diese Technik ist bei anderen Syntheseprogrammen, wie
werden, erst dann kann sie gestartet und ihre Abspiel- e = Env.new([0,1,0.2,0.5,0],
z.B. CSOUND, häufig anzutreffen. Allerdings ist der
parameter eventuell modifiziert werden.
Umgang mit solcherart zu programmierenden Hüllkurven [0.01,0.04,0.1,0.05], 'linear');
Eine ähnliche Zweiteilung findet man übrigens beim ungewohnt, vor allem, wenn man an die klassische ADSR-
Wavetable-Oszillator: hier muss zunächst die Wellenform Segmentstruktur denkt. Außerdem fehlen auf diese Weise Die Klasse Env kennt 2 Messages, mit denen Hüllkurven an
konstruiert und in einer Tabelle abgelegt werden. In SC einige für das Echtzeitspiel besonders wichtige Eigen- Ort und Stelle getestet werden können: plot und test.
gibt es dafür eine eigene Klasse Wavetable: schaften, wie die Bestimmung von Release-Phasen oder Für die eben spezifizierte Hüllkurve e ruft e.plot das fol-
geloopten Segmenten, völlig. Deshalb bietet SC eine eige- gende Fenster auf:
w = Wavetable.sineFill(512,
ne Klasse zur Spezifikation von segmentierten Hüllkurven
1 / [1,2,3,4,5,6]); sowie eine Klasse zu ihrem Abspielen an.
Dann kann diese Wellenform innerhalb eines Synth von Diese Spezifikation erfolgt über das Objekt Env. Dazu wird
einem Audio- oder Controlrate-Oszillator benutzt werden: die Message new zusammen mit mehreren Arrays, die die
Segmente beschreiben, an das Klassenobjekt Env
Osc.ar(w, 440); geschickt. Die Segmente werden durch Wert und Zeit ihrer
46 SuperCollider SuperCollider 47
Die Message test „spielt“ die Hüllkurve mit einem Mit den Zahlenwerten der curvature können beliebig stark Env.new kennt also letztlich 5 Argumente: levels, Soll die Hüllkurve die Amplitude eines Audiosignals steu-
800 Hz Sinuston ab. konkav oder konvex gekrümmte Verläufe erzeugt werden. times, curves, releaseNode und loopNode. ern, ordnet man dieses Audiosignal typischerweise dem
Für den Verlauf der Segmente stehen verschiedene Kurven- Der Wert 0 entspricht einem linearen Verlauf. Um den Umgang mit den Arrays und der Fülle der Para- mul-Eingang von EnvGen zu:
formen bereit, die entweder durch Symbole oder durch meter etwas zu erleichtern, wurden für Env eine Reihe von
Env([0,1,0],[2,1], -2).plot; ( var env, dur;
Zahlen spezifiziert werden. Hier die Bilder einer einfachen weiteren Messages programmiert, die einfache Standard-
2-teiligen Hüllkurve mit den unterschiedlichen Formen: verläufe in die für Hüllkurven notwendige times- und dur = 2.0;
levels-Arraystruktur nach Art eines Makros übersetzen. env = Env.perc(0.01, dur);
Env([0,1,0],[2,1],'linear').plot; Synth.play({
Um zum Beispiel einen Klang am Beginn sowie am Ende
nur kurz ein- bzw. auszublenden, var sig;
sig = Saw.ar(500);
Env([0,1,0],[2,1], 5).plot; EnvGen.ar(env, sig);
})
)
Bei exponentiellen Hüllkurven dürfen keine Level mit dem Da bei einer Multiplikation die Faktoren vertauscht werden
Wert 0 vorkommen, außerdem müssen alle Level das glei- können, kann man es äquivalent auch so schreiben:
che Vorzeichen haben:
( var env, dur;
Env([0.01,1,0.01],[2,1],'exponential').plot;
dur = 2.0;
Mehrere dieser Zahlen können zusammen in einem Array
übergeben werden, so daß jedes Segment eine eigene env = Env.perc(0.01, dur);
Krümmung aufweisen kann: Synth.play({
müsste man in etwa folgendes schreiben: var amp;
Env.new([0,1,0.3,0.8,0],[2,3,1,4],
amp = EnvGen.ar(env);
[0,-5,3,-3]).plot; dur = 10; // Gesamtdauer
Saw.ar(500, amp);
e = Env.new([0,1,1,0],
})
Env([0,1,0],[2,1],'sine').plot; [0.1,dur-0.3, 0.2], ‘linear’);
)
Mit Hilfe der Message linen läßt sich das Gleiche aber
kürzer formulieren: Der einzige Unterschied zwischen diesen beiden Methoden
besteht darin, daß im letzten Fall der Hüllkurvengenerator
dur = 10; // Gesamtdauer EnvGen durchaus auch mit Controlrate kr betrieben
Ein Teil der Segmente lassen sich der Release-Phase eines e = Env.linen( 0.1, dur-0.3, 0.2); werden könnte. Auf diese Weise ließe sich in kritischen
Klanges zuordnen. Dazu gibt man nach dem Kurvenwert Echtzeitanwendungen etwas CPU-Leistung einsparen.
Wie man sieht, muß man nur die Zeiten der 3 Phasen
bzw. -array einfach die Nummer der Knotenpunktes an, ab Env und EnvGen können selbstverständlich auch zur
Env([0,1,0],[2,1],'welch').plot; (Einschwingen, Haltephase, Ausschwingen) angeben. Alle
dem die Abklingphase beginnen soll. Die Zählung der Steuerung anderer Parameter herangezogen werden, z.B.
anderen Werte werden von linen selbst erzeugt!
Punkte beginnt mit 0. Falls ein Release-Knoten angegeben für die Tonhöhe:
wurde, wird die Hüllkurve beim Abspielen nur bis zu diesem triangle, sine, perc, adsr und asr erzeugen
Punkt durchlaufen, dann wird der letzte Wert beibehalten weitere häufig verwendete Hüllkurvenverläufe. Diese
( var mod, dur;
bis der Synth oder der Spawn, die die Hüllkurve enthal- Methoden und ihre Argumente findet man ausführlich im
dur = 2.0;
ten haben, eine Release-Message erhalten - erst dann wird Helpfile von Env beschrieben.
mod = Env.new([0, 24, 0],
der Rest der Hüllkurve abgearbeitet. Solche Release-
Messages werden z.B. bei der Steuerung durch MIDI-Note- Wie bereits angedeutet wurde, existiert für das Generieren [dur/2, dur/2], [6, -6]);
Env([0,1,0],[2,1],'step').plot; Messages oder durch Event-Pattern erzeugt. bzw. „Abspielen“ von mit Env spezifizierten Hüllkurven
Synth.play({
ein spezieller Unit Generator: EnvGen.
Schließlich kann auch noch ein Loop-Knoten festgelegt var pitch;
werden. Gelangt eine Hüllkurve an den Release-Knoten EnvGen, der als UGen nur innerhalb eines Synth ange-
pitch = EnvGen.kr(mod, 1, 69);
und wurde ein Loop-Knoten spezifiziert, werden die legt werden kann, erwartet als erstes Argument eine Env-
Spezifikation, danach folgen mul, add, einige Modi- Saw.ar(pitch.midicps)
Segmente zwischen diesen beiden Knoten in einer Schleife
fikationsparameter für die Hüllkurve und ein gate-Input })
solange abgespielt, bis eine Release-Message eintrifft.
zum Triggern und Re-Triggern der Hüllkurve. )
48 SuperCollider SuperCollider 49
Unter Zuhilfenahme der anderen EnvGen-Parameter Spawn können diese Werte deshalb auch nicht als nextTime Jetzt die Einsatzabstände ebenfalls variabel, aber leider
levelScale, levelBias und timeScale kann Sehen wir uns die do-Schleife des letzten Beispiels noch eintragen, zumal es mehrere Werte wären und nicht nur ein immer noch nicht mit den Dauern synchronisiert!
eine per Env beschriebene Hüllkurve nachträglich skaliert, einmal an: Es gibt eine ugenGraph function, die n-mal für einziger! Die Parameter numChannels, nextTime, Bevor wir eine Lösung für dieses Problem finden, soll noch
vertikal verschoben und zeitlich gedehnt bzw. gestaucht eine bestimmte Dauer aufgerufen wird. Gleichzeitig wird maxRepeats haben mit der übergebenen Funktion einmal genauer betrachtet werden, was das „Ausbrüten“
werden. Letzteres ermöglicht es zum Beispiel, Hüllkurven festgelegt, wann die nächste Wiederholung sein wird. nichts zu tun, es sind von ihr unabhängige Werte. Als von Events durch Spawn eigentlich bedeutet. Gehen wir
zunächst mit einer abstrakten Gesamtdauer von 1.0 zu „Notlösung“ wurde deshalb vorerst ein konstanter Wert sogar noch einen Schritt zurück: was macht ein Synth?
Dies sind auch die Eigenschaften, die das Objekt Spawn
definieren, und dann später beim Abspielen auf die von 0.5 Sekunden für die nextTime gewählt. Das Helpfile zu Synth gibt folgende Auskunft: „A Synth
auszeichnen:
gewünschte kürzere oder längere reale Dauer zu skalieren: Wenn man nun diesen Wert verkleinert, z.B. auf 0.3, kann is a container for one or more unit generators that execu-
Spawn.ar(newEventFunc, numChannels, man deutlich bemerken, daß sich einige Events überlap- te as a group“. Unter welchen Bedingungen diese Gruppe
( var e;
nextTime, maxRepeats, mul, add) pen! Spawn startet also die Events sequentiell, aber je von UGens ausgeführt wird, hängt von der aufrufenden
e = Env.sine(1.0); nach ihrer Dauer können sie sich überlagern oder ein Pause Message an Synth ab: play erzeugt eine Instanz von
15.do({ Spawn „brütet“ eine bestimmte Anzahl (maxRepeats) lassen. In unserem Beispiel sieht das Möglichkeitsfeld für Synth und spielt das Ergebnis hörbar ab, scope macht
var dur, freq; neuer Events gemäß der newEventFunc nach jeweils Dauern und Einsatzabstände so aus: es zusätzlich sichtbar, write schreibt es offline auf die
dur = rrand(0.1, 0.7); nextTime Sekunden aus und mischt die enstandenen Festplatte usw.
freq = rrand(500.0, 1000.0); ein- oder mehrkanaligen ( numChannels) Audiosignale Diese „Container“ können auch verschachtelt werden:
Synth.play({ in den Output. Spawn ist also ein ganz besonderer unit
generator, der weder Klänge generiert, wie etwa ein (
EnvGen.ar(e, Saw.ar(freq),
Oszillator, noch Klänge bearbeitet, wie etwa ein Filter oder Synth.play({
timeScale: dur) ein Delay. Vielmehr erfüllt Spawn eigentlich die Aufgaben,
}, dur + 0.2) die bisher ein Synth übernommen hat: komplexe Klang- Synth.play({SinOsc.ar(300)}, 0.5);
}) ereignisse gemäß einer ugenGraph function zu erzeugen. Synth.play({
) Trotzdem muß das Spawn-Objekt selbst auch in einem Resonz.ar(
Synth installiert werden.
Saw.ar(350),
Das vorige do-Beispiel mit Hilfe von Spawn formuliert,
Dieses Beispiel zeigt auch, auf welche Weise es möglich ist, XLine.kr(2000.0, 300.0, 1.5))
könnte in etwa so aussehen:
in SUPERCOLLIDER mehrere Klangereignisse nacheinan-
der zu erzeugen. do haben wir bereits im Abschnitt über }, 1.5);
( var e;
Collections und Iterationen kennengelernt. Hier sehen wir, Impulse.ar(20, 0.2);
daß do auch über ganze Zahlen iterieren kann: do zählt e = Env.sine(1.0);
}, 3.0)
dabei von 0 bis zum Wert-1 des Receivers. Synth.play({
Es ist allerdings auch erlaubt, als nextTime eine )
Diese Methode ist allerdings reichlich unflexibel, da sie nur Spawn.ar({
Funktion anstelle eines konstanten Wertes einzusetzen. In
streng sequentielle Abläufe gestattet, denn der nächste var dur, freq; Dabei wird auch hier die Audiofunktion des äußeren
diesem Fall wird bei jedem neuen Event diese Funktion auf-
Schleifendurchlauf wird erst dann gestartet, wenn alle da- Synthesizers Schritt für Schritt abgearbeitet. Das heißt,
dur = rrand(0.1, 0.7); gerufen und das Ergebnis als neue nextTime verwen-
rin enthaltenen Programmschritte abgearbeitet worden zuerst wird ein neuer Synth erzeugt, dessen Audio-
freq = rrand(500.0, 1000.0); det. Im folgenden Beispiel wurde die Zahl 0.5 durch eine
sind, also erst, wenn der aktuelle Synth nach dur+0.2 funktion evaluiert, gemäß der Message play gespielt und
EnvGen.ar(e, Saw.ar(freq), solche Funktion ersetzt:
Sekunden gestoppt wurde. nach 0.5 Sekunden wieder deinstalliert. Dann wird wieder
Besser wäre es, wenn die Events, d.h. die einzelnen timeScale: dur)
ein neuer Synth, diesmal mit einer komplexeren Audio-
Synth-Objekte, zeitlich unabhängig voneinander erzeugt }, 1, 0.5, 15) ( var e;
funktion aus 3 unit generators für eine gewisse Zeit
werden könnten. Genau für diesen Zweck wurde die Klasse }) e = Env.sine(1.0); gespielt. Schließlich wird ein normaler Impulsgenerator im
Spawn geschaffen - eines der leistungsfähigsten ) Synth.play({ Rahmen des äußeren Synth erzeugt und für 3 Sekunden
Konzepte in SUPERCOLLIDER. Spawn.ar({ gespielt. Wie in der do-Schleife, werden auch hier die
Dabei gibt es aber ein entscheidendes Problem: in der inneren Synth-Instanzen sequentiell abgearbeitet, wobei
var dur, freq;
ursprünglichen do-Schleife waren Dauer und Einsatz- mit dem Ende eines Synth auch die von ihm aufgerufe-
dur = rrand(0.1, 0.7); nen unit generators „ausgeschaltet“ werden und somit die
abstand synchronisiert. Nach verstrichener Dauer soll also
das nächste Event erzeugt werden. Aber in unserem freq = rrand(500.0, 1000.0); CPU nach getaner Arbeit nicht mehr belasten.
Spawn-Beispiel wird die Dauer der Hüllkurve im Inneren EnvGen.ar(e, Saw.ar(freq),
der Audiofunktion per Zufall generiert, während die timeScale: dur)
Angabe der nextTime außerhalb dieser Funktion als 3. }, 1, {rrand(0.1, 0.7)}, 15)
Argument von Spawn.ar erfolgen soll. Wir können aber
})
vorher die einzelnen Dauern noch gar nicht kennen und
)
50 SuperCollider SuperCollider 51
Die beim Ausführen von Synth-Instanzen erzeugte Erst der Einsatz einer Hüllkurve mit bestimmter Dauer kann die Um den gezielten Zugriff auf die Eigenschaft einer Instanz zu Ein Beispiel für die Verwendung des eventCounts:
Informationsleiste ermöglicht die Beobachtung der wech- von Spawn installierten Synthesizer auch wieder entfernen: ermöglichen, sollte dieses Objekt bei der Erzeugung
selnden Anzahl von UGens, je nachdem, welcher Synth benannt werden: (
zur Zeit aktiv ist: ( Synth.play({
Synth.play({ a = List[3,4,5,6];
Spawn.ar({ arg sp, i, sy;
Spawn.ar({ a.size.postln;
EnvGen.ar(Env.sine(0.2),
EnvGen.ar( -> 4 Saw.ar(i*200 + 200, 0.2))
Env.perc(0.01,rrand(0.1,2.0)), }, 1, 0.1, 24)
Damit auch in einer ugenGraph function die Instanz des
SinOsc.ar(rrand(3.0,5.0)*1000, aufrufenden Spawn oder Synth bekannt ist, wird diese }))
0, 0.1)) Instanz der Audiofunktion als Argument übergeben. Dabei
}, 1, {rrand(0.1, 0.3)}, nil) kann der Name dieses Arguments frei gewählt werden. Es soll an dieser Stelle noch einmal betont werden, daß
Kehren wir nun zurück zu Spawn. Die eigentlich Aufgabe nicht der Name des Arguments für dessen Bedeutung
von Spawn ist es, das Erzeugen solcher Synth-Instanzen }) Wie bei allen Funktionen erfolgt die Angabe der
Argumente zu Beginn der Funktion nach dem reservierten wichtig ist, sondern nur die Position dieses Names in der
in einer Art Schleife zu automatisieren und gleichzeitig )
Bezeichner arg: Deklaration der Argumente, d.h. nach dem Wort arg! Die
deren parallele Abarbeitung zu erlauben, indem die von Deklaration der Argumente kann außerdem nur unmittel-
ihnen erzeugten Audiosignale gemischt werden. Die von Ist eine Hüllkurve an ihrem Ende angelangt, wird der
Synth.play({ arg synth .... bar zu Beginn einer Funktion erfolgen. Direkt danach kön-
Spawn erzeugten Events, von denen bisher gesprochen „gespawnten“ Synth-Instanz ebenfalls das Ende signali-
siert. Daraufhin werden die von diesem Synth aktivierten nen sich dann eventuelle Variablendeklarationen ansch-
wurde, sind also eigentlich dynamisch erzeugte lokale Spawn.ar({ arg spawn .... ließen. Sinnvollerweise vergibt man für diese Argumente,
Synth-Instanzen, die alle die gleiche Audiofunktion UGens deinstalliert und der Synth gestoppt. Spawn soll-
Der erste Programmschritt in der folgenden Audiofunktion wie für alle Variablen, aussagekräftige Bezeichnungen.
besitzen - nämlich die, die dem Spawn als 1. Argument te also immer im Zusammenhang mit einer Amplituden-
druckt den Wert einer Variablen einer Synth-Instanz, Deshalb wurde im letzten Beispiel für eventCount das
übergeben wurde. Spawn ist also ein Art Maschine, die hüllkurve betrieben werden!
während in der darauffolgenden Zeile der Wert einer für Schleifenvariablen typische i gewählt und für die bei-
Synth-Objekte herstellt und sie mit Kopien ein und der- Auf den ersten Blick mag es als merkwürdige Umkehrung Variablen der Klasse Synth gedruckt wird. den Instanzenargumente entsprechende Abkürzungen.
selben Audiofunktion ausstattet. von Zuständigkeiten erscheinen, daß ein Hüllkurven-
generator einer ugenGraph function den Synthesizer, der ( Da wir nun innerhalb der Audiofunktion auch auf die
Die so erzeugten Synth-Instanzen kennen allerdings kein Spawn-Instanz zugreifen können, sind wir jetzt in der
Dauernargument, wie es bei der Message play existiert. die in dieser Funktion enthaltenen Generatoren ja erst ins Synth.play({ arg synth;
Leben ruft, beenden kann! In SUPERCOLLIDER stellt Lage, auch deren nextTime-Variable zu verändern!
Stattdessen muß in der Audiofunktion eine Hüllkurve auf- synth.blockSize.postln; Kommen wir also zu unserem ersten Beispiel zurück und
gerufen werden, nach deren Durchlauf die jeweilige jedoch der Zugriff auf Eigenschaften von „Mutter-
objekten“ wie Synth und Spawn ein weiteres wichtiges Synth.sampleRate.postln modifizieren es entsprechend, um Dauer und Einsatz-
Synth-Instanz terminiert wird. abstand zu synchronisieren:
und leistungsfähiges Konzept dar. Innerhalb einer Audio- WhiteNoise.ar(0.3)
Startet man das folgende Patch
funktion können damit eine ganze Reihe von Eigen- }, 4.0) ( var e;
schaften der aufrufenden Synth- oder Spawn-Instanzen
( ) e = Env.sine(1.0);
gelesen oder verändert werden.
Synth.play({ -> 128 Synth.play({
Hierbei muss man zwischen Eigenschaften der gesamten
Spawn.ar({ Klasse, sogenannten Klassenvariablen, und Eigenschaften -> 44100 Spawn.ar({ arg sp, i, sy;
Impulse.ar(rrand(1.0, 7.0), 0.2) der Instanzen, den Instanzenvariablen, unterscheiden. Auf var dur, freq;
erstere kann direkt zugegriffen werden, ohne daß man vor- Die Deklaration von Argumenten in der ugenGraph func- dur = rrand(0.1, 0.7);
}, 1, 3, nil) tion ist optional: wenn sie erfolgte, kann man auf den Wert
her eine Instanz dieser Klasse erzeugt hat:
des Arguments zugreifen, wenn man dagegen kein Argu- sp.nextTime = dur + 0.2;
})
Synth.hardwareName.postln ment angegeben hat, kann man es auch nicht verwenden! freq = rrand(500.0, 1000.0);
)
-> Apple Sound Manager Spawn übergibt seiner newEventFunc gleich 3 sol- EnvGen.ar(e, Saw.ar(freq),
und beobachtet gleichzeitig in der Informationsleiste die cher Argumente: timeScale: dur)
Anzahl der UGens, wird man feststellen, daß nach jeweils Synth.sampleRate.postln }, 1, nil, 15)
spawn - das 1 Argument ist die Instanz von Spawn, die
3 Sekunden (nextTime) ein neuer Impulsgenerator hin- -> 44100 durch Spawn.ar generiert wurde. })
zukommt. Da die Variable maxRepeats auf nil gesetzt
eventCount - das 2. Argument ist die Nummer des )
ist, da also unbegrenzt viele Synths „gespawnt“ wer- hardwareName und sampleRate sind also Eigen-
erzeugten Events, beginnend bei 0 für das erste Event.
den, ist die Überlastung der CPU nach einer gewissen Zeit schaften der Klasse Synth und somit allen Synth- Jedes Event kann dem Eventgenerator also mitteilen, wann
unvermeidlich! Dieses Patch sollte also spätestens beim Instanzen gemeinsam. synth - das 3. Argument ist die Instanz des von Spawn
das jeweils folgende Event starten soll. Damit lässt sich
Überschreiten der 90%-Marke der CPU-Auslastung mit erzeugten lokalen Synth-Objektes
zum Beispiel die folgende gleichzeitig von inneren und
[CMD-.] abgebrochen werden. äußeren Bedingungen abhängige Regel für Einsatz-
abstände implementieren: falls die zufällig erzeugte
52 SuperCollider SuperCollider 53
Frequenz einen bestimmten Wert überschreitet, folgt der EnvGen.ar( In SUPERCOLLIDER verwirklicht OrcScore dieses
nächste Ton nach 20 ms, ansonsten wird der Abstand zum Env.perc(0.01,dur), Prinzip durch zwei zu übergebende Listen: eine Liste mit
nächsten Ton von der aktuellen Mausposition bestimmt. PanAz.ar( „Instrumenten“, d.h. ugenGraph functions, und eine Liste
nchan, mit Events, die jeweils wiederum durch eine Liste mit
( RLPF.ar( Einsatzabstand, Instrumentennummer bzw. -name, Dauer
Synth.play({ Mix.ar( und beliebigen weiteren Parametern beschrieben werden,
m = MouseX.kr(1.0, 0.1); ähnlich den pfields in CSOUND.
Saw.ar(
Spawn.ar({ arg sp, i, sy; [freq, freq*rrand(0.9,1.1)], OrcScore.ar(orchestra, score, numChannels,
var dur, next, freq; amps.at(i) / 4)), nextTime, maxRepeats, mul, add)
dur = rrand(0.1, 0.4); XLine.ar(
Jedem „Instrument“ im orchestra müssen dabei nicht
freq = rrand(40.0,100.0).midicps; freq*2, freq/2, dur/2),
nur die normalen Spawn-Argumente spawn, i und
if(freq > 1000, rqs.at(i)), synth übergeben werden, sondern auch der Einsatz-
{ next = 0.02 }, pan)) abstand, die Nummer bzw. das Symbol für eben dieses
}, nchan);
Instrument sowie die anderen optionalen Parameter, die
{ next = m.poll });
für dieses Instrument Verwendung finden sollen, z.B.
sp.nextTime = next; }))
Dauer, Frequenz, Amplitude usw. Diese Parameter werden
EnvGen.ar(Env.perc(0.01, dur), }) dann aus der Score-Liste dem Instrument beim Aufruf über-
Saw.ar(freq, 0.2)) ) geben.
}, 1) Im Folgenden soll nur kurz die Struktur dargestellt werden.
In diesem Patch werden 3 Spawn-Objekte erzeugt, deren Für vollständige Beispiele sei auf das Helpfile zu
}) Events aus je 2 gegeneinander leicht verstimmten Oszilla- OrcScore sowie auf mitgelieferte Examples, wie z.B. in
) toren bestehen, die durch einen resonanten Tiefpass dyna- der Datei „time structures“, verwiesen.
Da das Objekt Spawn sich auch wie ein normaler UGen Parallele Eventstreams misch gefiltert, anschließend quadrophon positioniert und
verhält, kann eine Spawn-Instanz auch mit anderen mit einer perkussiven Hüllkurve versehen werden. Durch (
Ebenso können mehrere Spawner „parallel“ aktiviert wer-
UGens verknüpft werden. Hier wird ein Strom von Events die unterschiedlichen Werte für Frequenzbereich, Dauer, Synth.play({
den, um voneinander unabhängige Eventströme zu erzeu-
durch ein interaktiv gesteuertes Filter geleitet: Amplitude und Filtergüte, die aus jeweils 3-elementigen OrcScore.ar(
gen. Für die Ausgabe müssen diese dann selbstverständlich
Listen kommend mit Hilfe der collect-Schleife den drei // orchestra array:
noch zusammengemischt werden, wie im folgenden
( Spawns zugeordnet werden, werden diese 3 Eventströme [
Beispiel.
Synth.scope({ klanglich differenziert. Weil jeder der drei Spawns in die- // instr 1
var source, res, rq; sem Patch 4 Audiokanäle erzeugt, müssen diese 3 x 4 { arg spawn, i, synth, deltaTime,
(
res = MouseX.kr(60.0,130.0).midicps;
Kanäle mit Mix zu 4 Kanälen zusammengefasst werden. instrumentNum, ...;
Synth.scope({
rq = MouseY.kr(1.0,0.02, \exponential); // code
var franges, durs, amps, rqs, nchan; OrcScore
},
source =
franges = Will man ganz unterschiedliche Synthese-Patches mit Hilfe // instr 2
Spawn.ar({
[[80,150], [500,570], [5000,6000]]; von Parameterlisten steuern, bietet sich statt Spawn die { arg spawn, i, synth, deltaTime,
arg sp; davon abgeleitete Klasse OrcScore an. OrcScore rea-
durs = [1, 12.0, 0.07]; instrumentNum, ...;
var dur, freq, pan; rqs = [0.03, 0.1, 0.4]; lisiert den klassischen Ansatz der Zweiteilung der Klang- // code
freq = rrand(40.0, 80.0).midicps; amps = [0.7, 0.2, 0.1]; erzeugung in eine Beschreibung der inneren Struktur von }
dur = 20.0 / freq; nchan = 4; Klangerzeugern (Instrument, Patch, Preset u.ä.) und eine
],
sp.nextTime = dur / 4; Mix.ar( franges.collect({ arg range, i; Steuerung derselben (Partitur, MIDI-File u.ä.). Schon der
Name OrcScore verweist auf das aus den MUSICN- // score array:
pan = 1.0.rand2; Spawn.ar({ arg sp;
EnvGen.ar(
Sprachen entwickelte CSOUND, das lange Zeit mit 2 zwei [
var dur, freq, pan;
getrennten Dateien operierte: dem Orchestra- und dem [ <parameterliste event 1>],
Env.perc(0.01,dur), freq = Score-File. (Inzwischen ist die strenge Zweiteilung in [ <parameterliste event 2>],
Pan2.ar(Saw.ar(freq, 0.2), rrand(range.at(0),range.at(1)); CSOUND durch verschiedene Echtzeitversionen, Event- [ <parameterliste event 3>],
pan)) dur = steuerung in den Instrumenten und ein neues Dateiformat (...)
}, 2); durs.at(i) * rrand(0.6, 1.2); relativiert worden.) ],
RLPF.ar(source, res, rq) sp.nextTime =
1, nil, 1)
}) dur * rrand(0.5, 2.0);
})
pan = 1.0.rand2;
) )
54 SuperCollider SuperCollider 55
TSpawn und verschachtelte Eventstreams Hier triggern die unregelmäßig auftretenden positiven ( Note die jeweils älteste noch nicht beendete Note
Spawn, OrcScore sowie die meisten anderen, hier Impulsflanken von Dust hohe perkussive Klänge: Synth.play({ „gestohlen“.
nicht besprochenen Spawner, wie Cycle, TSpawn.ar({ Voicer.ar(newEventFunc, numChannels,
RandomEvent, SelectEvent, XFadeTexture (
var trans; midiChannel, maxVoices, mul, add)
und OverlapTexture erzeugen die Events zu einer Synth.play({
bestimmten Zeit nextTime, die entweder eine trans = XLine.kr(500.0, 2000.0, 2);
TSpawn.ar({ Die newEventFunc bekommt neben den üblichen 3
Konstante ist oder aus einer Liste stammt oder auf irgend- Spawn.ar({ Argumenten spawn, eventCount und synth noch
eine Weise errechnet wird. EnvGen.ar(
EnvGen.ar( jeweils deltaTime, channel, note und veloci-
Insbesondere in Echtzeitanwendungen wird es aber oft Env.perc(0.01,rrand(0.1,2.0)), ty übergeben. Die hier offensichtlich nutzlose
Env.perc(0.01,rrand(0.1,1.0)),
nötig sein, Klangereignisse in Abhängigkeit von nicht vor- SinOsc.ar( deltaTime (die Zeit bis zur nächsten Note ist unbe-
hersehbaren, meist externen Ereignissen auszulösen. SinOsc.ar( kannt!) ist aus Kompatibilitätsgründen zu OrcScore
rrand(3.0,5.0)*1000,
Die Spawn-Klassen TSpawn, TrigXFade und rrand(3.0,5.0)*trans.poll, eingefügt worden, damit die „Instrumente“ in beiden
0, 0.1)
Voicer erlauben die Verwendung von Triggersignalen 0, 0.1) Spawn-Varianten verwendet werden können.
) )
zur Erzeugung von Events. Es folgt ein sehr einfaches Beispiel für einen 8-stimmigen
}, 1, nil, Dust.ar(4)) }, 1,
Im Falle von TSpawn (= triggered spawn) wird der Trigger Voicer, der auf MIDI-Note-Messages von Channel 1
anstelle des nextTime-Arguments eingesetzt. Ansons- }) // nextTime 20...120 ms reagiert.
ten funktioniert TSpawn genauso wie Spawn: ) { rrand(0.02, 0.12) },
// 4 bis 20 Events (
TSpawn.ar(newEventFunc, numChannels, rrand(4, 20)) e = Env.adsr(0.03,0.1,0.3,0.5,1,-3);
maxRepeats, trig, mul, add) Synth.scope({
}, 1, nil, MouseX.kr(-1,1))
P s t e p 2 a d d(pattern1, pattern2)