Um in Csound Sound zu generieren, muss der Programmierer eine Textdatei erstellen. Die .csd Datei. In dieser Datei sind zwei grundlegende Bestandteile enthalten:

  1. Das Orchester (Orchestra)
  2. Die Komposition (Score)

In älteren Versionen der Programmiersprache wurde diese beiden Komponenten in zwei unterschiedliche Dateien geschrieben (.orc und .sco), aber mittlerweile sind es einfach zwei verschiedene Abschnitte in der Csound Datei. Sie beschreiben zum einen aus welchen Komponenten unser virtuelles Instrument (Orchestra) besteht und zum anderen die Operationen, die wir mit ihm ausführen wollen (Score). Es bestünde aber immer noch die Möglichkeit, die Teile in zwei unterschiedliche Dateien unterzubringen.

Das Grundgerüst einer .csd Datei besteht also aus einem oder mehreren Instrumenten und einem Score. Außerdem gibt es vorher noch einen Abschnitt, in dem gewisse Einstellungen vorgenommen werden können. Diese drei Bestandteile werden in den <CsoundSynthesizer> Block geschrieben. Alles was innerhalb dieses Blocks beschrieben wird, wird nachher auch verarbeitet.

<CsoundSynthesizer>    ;Hauptblock

<CsOptions>            ;Block für Einstellungen
</CsOptions>

<CsInstruments>        ;Instrumente(Orchestra)
</CsInstruments>

<CsScore>              ;Anweisungen für das Orchestra (Score)
</CsScore>

</CsoundSynthesizer>   ;Ende des Hauptblocks

Alles was nach einem Semikolon geschrieben wird, ist ein Kommentar und wird vom Interpreter ignoriert. Neben dem Semikolon gibt es noch andere Kommentarformen, wie man sie bspw. aus C++ oder Java kennt:

// verhält sich ebenso wie das Semikolon

/*
    Dies ist ein
    mehrzeiliger Kommentarblock
    in Csound.
*/

Wie man ein Orchester programmiert

Ein Orchester, also der <CsInstruments> Codeblock besteht im Wesentlichen aus zwei Teilen:

  1. Header
  2. Instrumente

Der Header wiederum besteht aus verschiedenen Informationen, die für alle Instrumente des Orchesters gültig sind. Ein Orchester kann aus einem oder mehreren Instrumenten bestehen. Ein Header Statement wird nur einmal ausgeführt, zu dem Zeitpunkt wenn das Orchester quasi initialisiert wird.

In einem Header wird bspw. Folgendes bestimmt:

  • sr (sampling rate)
  • kr (control rate – mehr dazu später)
  • ksmps (sr/kr ratio)
  • nchnls (number output channels – 1=mono, 2=stereo)

Für alle Instrumente in einem Orchester gelten somit die Angaben, die in dem Header gemacht werden. Ein typischer Header könnte so aussehen:

sr      = 48000
kr      = 4800
ksmps   = 10
nchnls  = 1

Ein Instrument ist im Allgemeinen etwas komplizierter als der Header. Der Grad der Komplexität hängt davon ab, was man von dem Instrument erwartet 😉 Die erste Zeile des Instrumenten-Parts muss die ID-Nummer enthalten. Diese schreibt man nach dem instr Statement, gefolgt von einer Integer Zahl. Das Statement endin kennzeichnet das Ende eines Instruments. Die Grundform sieht dementsprechend so aus:

instr 1
...
(body of instrument)
...
endin

Ein Beispiel:

instr 1
    aneworc oscil 1000, 320, 1
    out aneworc
endin

Was bedeutet das im Detail? Eine Variable kann man sich wie eine Schublade mit einem Namensaufkleber vorstellen. In diesem Beispiel steht auf dem Aufkleber aneworc. In dieser Schublade wird das Ergebnis einer Operation temporär gespeichert. Also wird in aneworc das Ergebnis des oscil Operationscodes (Opcode) gespeichert. Der oscil Opcode führt die Arbeit eines Oszillators aus, dem spezielle Argumente übergeben wurden: Amplitude, Frequenz und die Identifikationsnummer einer gespeicherten Waveform-Funktion.

Der Oszillator hat hier also eine Amplitude von 10.000, eine Frequenz von 320Hz und eine Funktion, die in Funktionstabelle Nummer 1 bestimmt ist (diese wird später in dem Score festgelegt). Diese Argumente werden an den Oszillator übergeben, der einen Audio Oszillator emuliert. Das Ergebnis dieses Oszillators wird dann in aneworc gespeichert. Wichtig ist noch zu wissen, dass die Variable in diesem Fall unbedingt mit einem a beginnen muss! Der Oszillator liefert ein Ergebnis im hörbaren Audiobereich. Variablen, die Audiodaten speichern müssen mit einem a im Namen beginnen.

Als nächstes wird der Inhalt der Variablen aneworc an einen weiteren Opcode weitergegeben, nämlich out. Der out Opcode speichert den Inhalt des Arguments entweder auf der Festplatte oder gibt ihn in Echtzeit aus. Dieses Verhalten wird normalerweise in den <CsOptions> festgelegt.

Der endin (end of instrument) Ausdruck markiert das Ende eines Instrumentenblocks.

Wie man einen Score programmiert

Genau wie auch <CsInstruments> besteht <CsScore> normalerweise aus zwei Teilen:

  1. Funktionen
  2. Noten

Funktionsausdrücke (f statements) benötigt man um bestimmt Waveformen zu erzeugen. Wenn man Samples benutzt, dessen Waveform bekannt sind, braucht man keine Funktionen zu schreiben. Somit würde der Score dann nur aus Noten bestehen.

In Csound ist es möglich jegliche Art von Funktion zu erstellen. Im vorangegangen Instrumentenbeispiel haben wir dem Oszillator eine Referenz auf eine Funktion mit der Nummer 1 als Argument übergeben.

aneworc oscil 10000, 320, 1

Das folgende f-Statement generiert eine Sinusfunktion für dieses Instrument:

f1 0 4096 10 1

wobei:

f1 die Identifikationsnummer der Funktion bestimmt (1)
0 ist die Aktionszeit dieser Funktion. D.h. in dieser Zeit wird Csound diese Funktion generieren. Wäre dieser Wert 3, dann würde diese Funktion 3 Sekunden nach dem Start des Scores generiert werden.
4096 sind die Anzahl der Punkte in der Funktion. D.h. die Waveform besteht aus Werten, die in einem Array von 4096 einzelnen Punkten gespeichert sind. Allgemein kann man sagen, je höher die Anzahl der Punkte, desto akkurater wird die Waveform gezeichnet / abgespielt.
10 ruft eine bestimmte Methode zum Generieren der Funktion auf. Bei diesen Methoden handelt es sich um die sogenannten GEN Routinen, jede hat seine eigene ID Nummer. Mit der Nummer 10 wird dementsprechend die GEN10 Routine aufgerufen. Diese ist sehr nützlich, wenn man eine simple Sinusfunktion benötigt.
1 bedeutet, dass GEN10 eine Waveform generieren soll, die nur eine Sinuskomponente enthält. Hätten wir Folgendes geschrieben:

f1 0 4096 10 1 1 1

würden wir eine Sinuskurven mit drei harmonischen Obertönen (fundamental, second harmonic und third harmonic) bekommen.

Das alles mag jetzt ziemlich kompliziert klingen, nur um einen Sinuston auszugeben. Allerdings zeigt dies, wie komplex ein einfacher Synthesizer intern arbeitet. Um einen Synthesizer wirklich zu verstehen und wenn man Csound Klänge entlocken will, die jenseits der Presets eines Casio Keyboards liegen, dann muss man wissen, was im Inneren passiert.

Und wie schreibe ich nun Noten? Noten werden mithilfe der Instrument Statements (i Statements) generiert. Ein i Statement bezieht sich dabei immer auf ein Instrument des Orchesters mit derselben ID-Nummer. Csound i Statements bestehen aus Parameter, die alle eine bestimmte Position in der Parameterreihe haben. Diese Positionen nennt man p-fields (Parameter Felder). Die ersten drei p-fields sind die Einzigen, die immer gefüllt werden müssen.

  • p1 (erstes p-field): Bestimmt welches Instrument des Orchesters gespielt werden soll. i1 bspw. bezieht sich auf #1.
  • p2 (zweites p-field): Action time. 0 bedeutet dass die Note sofort beginnt (0 Sek); 3 bedeutet dass die Note 3 Sekunden nach dem Beginn gespielt wird; 3.555 würde bedeuten: nach 3 Sekunden und 555 Millisekunden.
  • p3 (drittes p-field): Dauer der Note. Der Wert 2 würde demnach eine Notendauer von 2 Sekunden bedeuten. .5 bedeutet eine halbe Sekunde.

Der Programmierer kann beliebig viele Parameter (p4, p5, p6, etc.) anhängen, jeder für eine bestimmte Aufgabe. Wie viele Parameter wir benutzen hängt ganz von unserem selbst erstellten Orchester ab. Im folgenden Beispiel nutzen wir die drei festen ersten Parameter plus p4 zur Bestimmung der Frequenz des Oszillators. Zu bemerken wäre noch, dass wir einige Kommentare eingefügt haben. Es ist immer ratsam möglichst viel zu kommentieren, damit nach ein paar Wochen noch weiß, was man sich bei der Programmierung gedacht hat.

<CsoundSynthesizer>

<CsOptions>
-odac               ;realtime audio
</CsOptions>


<CsInstruments>
  sr = 48000
  ksmps = 10
  nchnls = 2

  instr   1
      aneworc oscil 10000, p4, 1  ;oscillator with 10000 amplitude, p4 
                                  ;(frequency), function #1
      out aneworc
  endin
</CsInstruments>

<CsScore>
  f1 0 4096 10 1    ;function #1, action time, table size, GEN, 
                    ;ampl. of fundamental
  i1 0 3  440       ;plays instrument 1 starting at the beginning of the 
                    ;piece, lasts 3 seconds,
                    ;frequency: 44Hz.
  i1 4 2  320       ;plays instrument 1 starting at 4 seconds,
                    ;lasts 2 seconds, frequency: 320Hz
  i1 6 2  280       ;plays instrument 1, starts at 6 seconds, 
                    ;lasts 2 seconds, frequency: 280Hz
</CsScore>

</CsoundSynthesizer> 

Wir haben gerade unser erstes … nicht wirklich interessantes … Musikstück geschrieben, mit drei Noten und einer Dauer von acht Sekunden! Wichtig wäre noch anzumerken, dass die Parameter in den Funktionen und den Noten immer mit einem Leerzeichen getrennt werden! Während die Parameter des Oszillators mit einem Komma zu trennen sind.

Mit dem Wissen, dass wir uns bis hier angeeignet haben, könnten wir auch schon eine Akkord spielen. Dazu schreiben wir folgende Noten:

i1 0 5 440       ;A4
i1 0 5 329.628   ;E4
i1 0 5 293.665   ;D4

Soviel erstmal zu Orchester und Noten. Im nächsten Artikel gehe ich dann noch etwas mehr auf Funktionen, Instrumente und Routinen ein…