% Content-encoding: UTF-8 \documentclass[ngerman]{article} \usepackage[utf8]{inputenc} \usepackage{multicol} % \usepackage{babel} \usepackage{xspace} \setcounter{secnumdepth}{0} \setcounter{tocdepth}{0} \usepackage{german} %\newcommand{\code}[1]{\texttt{#1}} %\newcommand{\ret}{\textsf{$<$ret$>$}\xspace} %\newcommand{\ret}{$\hookleftarrow$\xspace} \renewcommand{\reftextbefore}{auf der vorherigen Seite} \renewcommand{\reftextfacebefore}{auf der gegenüberliegenden Seite} \renewcommand{\reftextafter}{auf der nächsten Seite} \renewcommand{\reftextfaceafter}{auf der gegenüberliegenden Seite} \renewcommand{\reftextfaraway}[1]{auf Seite \pageref{#1}} %\renewcommand{\figurename}{Listing} \title{Wave Engine (1)} \ifx\shorttitle\undefined\else %\shorttitle{WE(1)} % der Titel ist kurz genug! \fi \author{Hannes Teich} % Erste Korrekturen 01.09.2011 \begin{document} \maketitle \begin{abstract} In der letzten VD--Nummer (2011/2) habe ich unter dem Titel \emph{Top One Partitur} das Thema kurz angeschnitten und ein Musikstück in zweierlei Notenschrift präsentiert, in üblicher und in maschinenlesbarer. Diese \textit{Partitur} wird von einem \emph{Forth--Programm} gelesen und in eine akustisch abspielbare wav--Datei übersetzt, die leicht ins populäre mp3--Format gewandelt werden kann. \end{abstract} \begin{multicols}{2} \begin{figure} \begin{center} \includegraphics[scale=1.0]{2011-03/Bild-1} %\caption{Viermanualige Orgel} \end{center} \end{figure} Aus Sicht des Partitur--Verfassers handelt es sich um eine viermanualige Orgel mit großem Tonumfang, nämlich vom \textit{Subkontra--C} mit 16,3 Hz bis zum \textit{sechsgestrichenen C} mit 8345 Hz, also 9 Oktaven. Jedes Manual kann sechs Töne gleichzeitig spielen. Dieser PC--Orgel, ich nenne sie ab sofort \emph{Wave Engine}, können verschiedene Klänge entlockt werden, die vorab in der Registrierung festgelegt werden. Zudem ist sie imstande, sich von der üblichen Tonstimmung etwas zu entfernen. Verstehen wir uns recht: Hier funktioniert nichts live, man kann keine Klaviatur anschließen, und man muss nach dem Start einige Zeit auf das Ergebnis warten. Da erhebt sich natürlich die Frage, wozu das Ding gut ist, --- und da wird die Sache persönlich\dots{} \section{Was mich bewogen hat} Einerseits ist es ein verschleppter Jugendtraum, wenigstens andeutungsweise die furiosen Klänge eines Les Paul nachahmen zu können, jenes Multiplayback--Gitarristen, der in den Fünfzigern mit seinem \textit{new sound} die Musikszene aufgemischt hat (siehe z.\,B.\@ \url{www.stocket.de/WE}). Davon abgesehen, war es ein nachwirkender Schock für mich, eines Tages erkennen zu müssen, dass mit unserem Tonsystem etwas nicht in Ordnung ist. Was da nicht stimmt, ist leicht gesagt und schwer einzusehen. Im letzten Heft hatte ich auf (m)einen Artikel verwiesen, der dieses Thema näher behandelt. Auch Wikipedia tut gute Dienste. Hier nur in aller Kürze: \section{Ein Lamento} Musikalische Intervalle sind durch ganzzahlige Schwingungsverhältnisse definiert: Oktave 2/1, Quinte 3/2, Quarte 4/3, Durterz 5/4, Mollterz 6/5 etc. Ein Mathematiker sieht sofort: Die passen nicht nahtlos zueinander. In der Praxis werden Quinten und Terzen so verbogen, dass sie dennoch passen. Ein 85--tastiges Klavier hat einen Ton\-um\-fang von sowohl 7 Oktaven als auch 12 Quinten. \begin{figure} \begin{center} \includegraphics[scale=1.0]{2011-03/Bild-2} \caption{12 Quinten = 7 Oktaven?} \end{center} \end{figure} Der eigentliche Unterschied, das \emph{pythagoreische Komma}, wurde wegjustiert. In der Praxis hat sich das allerdings bestens bewährt. Verwerflich finde ich allerdings, diesen (rein akustischen) Kompromiss auf die \textit{Semantik} zurückschlagen zu lassen, also auf die Bedeutung der \textit{gemeinten} Töne im Zusammenhang eines Musikstücks. Aber genau das geschieht in zunehmendem Maße. Weil der Kompromiss zu einer gleichmäßigen \emph{Zwölftelung} der Oktave führte, denken Komponisten und Interpreten mehr und mehr in Zwölftel--Oktaven. Das können sie sich nur leisten, weil die temperierte Stimmung sich nicht zu weit von den reinen Intervallen entfernt. Diese sind die eigentlichen Bausteine des Tonsystems. Die Oktave ist ein Paradebeispiel für die \emph{Definition durch Verhältniszahlen}. Das Ohr ist tolerant, die Semantik nicht. Der Einwand, so genau ginge es doch nicht zu in der Musik, greift nicht, denn die Definition einer Quinte oder Terz hat mit Genauigkeit ebenso wenig zu tun wie die Winkelsumme im Dreieck. \section{Was also ist zu tun?} Die „Wave Engine"' trägt solchen Bedenken dadurch Rechnung, dass sie --- zusätzlich zur üblichen Stimmung --- praktisch \emph{reine Intervalle} erzeugen kann. Da aber ein System aus reinen Intervallen in ein grenzenloses Tongestrüpp ausufern würde, muss, allerdings nur akustisch, ein wenig geschummelt werden. Durch einen glücklichen Umstand führt eine Teilung der Oktave in 53 gleiche Mikro--Intervalle, \emph{Kommas} genannt, zu nicht mehr wahrnehmbar kleinen Fehlern. \begin{figure} \begin{center} \includegraphics[scale=1.0]{2011-03/Bild-3} \caption{\label{fig:intervalle}Oktavteilung mit reinen Intervallen} \end{center} \end{figure} Hier ist die \emph{Zwölftel--Oktave} verschwunden. \textit{Ganztöne} und \textit{Halbtöne} treten in mehrerlei Größen auf. Groß geschriebene Töne (C, D, F, G) sind quintverwandt, die übrigen stehen zu ihnen im Terzabstand. Ein unterstrichenes \textit{es} erklingt ein Komma höher als \textit{Es}, ein überstrichenes \textit{fis} ein Komma niedriger als \textit{Fis}. Beim Übergang in andere Tonarten ändert sich die Auswahl aus den 53 Tonhöhen entsprechend. Pythagoreische und syntonische \emph{Kommas} bleiben erhalten (als 1/53 Oktave). Die Ballone \textit{(Abb.\@ \ref{fig:intervalle})} zeigen den Versuch, durch einen Lupeneffekt grafische Ordung in die Menge der Töne zu bringen, wobei die Waagrechte für die Klavierstimmung steht. Und damit genug der Musiktheorie. Wikipedia gibt gern weitere Auskunft (z.\,B.\@ unter \textit{Reine Stimmung}). \section{Der Sinus--Generator} Nun aber „in medias res!"' Was wir zunächst brauchen, ist eine Methode zur Tonerzeugung. Billige Rechteck--Kurven kommen nicht in Frage. Ein Sinus--Generator übernimmt die Erzeugung fast aller Klänge. Aber weil ein simpler Sinuston eine Zumutung wäre, muss er abklingen und mit Obertönen versetzt werden. Die \emph{Wave Engine} spendiert dafür 15 Obertöne (das sind mitsamt Grundton 16 Teiltöne oder \textit{partials}.). Ich beginne mit dem Wesentlichen, ohne jedes Fitzelchen zu kommentieren. Leo Brodie hat dieses Verfahren „fun--down approach"' genannt. :--) \begin{footnotesize} \begin{verbatim} \ --------------------------------------------------------- \ waver-A - Sinus-Generator für Manual A. (B, C, D ebenso.) \ --------------------------------------------------------- \ <<< Definitionen gemeinsam für alle Manuale >>> 6 constant v/m \ voices per manual 0 value v/m_ \ loop index 3 constant r/v \ p-rows per voice 0 value r/v_ \ loop index 16 constant p/r \ partials per p-row 0 value r/v_ \ loop index 48 constant p/v \ partials per voice \ 0 value p/v_ \ not used \ 288 constant p/m \ partials per manual 0 value p/m_ \ # of actual partial \ --------------------------------------------------------- \ <<< KURVENBERECHNUNG (MANUAL A) >>> : sinus-A ( --) p/m_ :ampl-A* f@ f0> \ avoid multiply by zero IF v/m_ :phase-AL* f@ \ root's phase (L) p/r_ 1+ s>f f* \ multiply by partial# fsin \ sinus (L) p/m_ :ampl-A* f@ f* \ multiply by ampl accu-AL* f+! \ update accu (L) stereo_ IF \ second channel (R) v/m_ :phase-AR* f@ \ root's phase (R) p/r_ 1+ s>f f* \ multiply by partial# fsin \ sinus (R) p/m_ :ampl-A* f@ f* \ multiply by ampl accu-AR* f+! \ update accu (R) THEN THEN ; \ --------------------------------------------------------- \ <<< ABKLINGVORGÄNGE (MANUAL A) >>> : fadings-A ( --) p/m_ :ampl-A* f@ \ 6*16*3=288 gap-A_ v/m_ :newton-A @ and \ new tone following? IF stac-A* f@ f* \ staccato ELSE v/m_ :pflag-A @ \ pause running? IF r/v_ :sust-A* f@ f* \ sustain ELSE r/v_ :fade-A* f@ f* \ fading THEN THEN p/m_ :ampl-A* f! ; \ --------------------------------------------------------- \ <<< TONGENERATOR (MANUAL A) >>> : waver-A ( --) empty-accu-A \ AKKU LEEREN (A) v/m 0 DO i to v/m_ \ 6 voices per manual r/v 0 DO i to r/v_ \ 3 p-rows per voice p/r 0 DO i to p/r_ \ 16 partials per p-row v/m_ p/v * \ 0 - 48 - 96 - 144 - 192 - 240 r/v_ p/r * + \ 0 - 16 - 32 p/r_ + \ 0 - 1 - 2 - ... - 14 - 15 to p/m_ \ 0 ... 287 sinus-A \ KURVENBERECHNUNG (A) fadings-A \ ABKLINGVORGÄNGE (A) LOOP ( p/r_) \ next partial in p-row LOOP ( r/v_) \ next p-row of voice phase-incr-A \ PHASE INKREMENTIEREN (A) LOOP ( v/m_) \ next voice of manual timer-decr-A \ TIMER DEKREMENTIEREN (A) store-accu-A ; \ AKKUINHALT AUFADDIEREN (A) \ --------------------------------------------------------- \end{verbatim} \end{footnotesize} Es gibt insgesamt: \begin{tabular}{rl} 4 & Manuale (\textit{A, B, C, D}); \\ 6 & Stimmen (\textit{voices}) pro Manual; \\ 3 & Teiltonreihen (\textit{p--rows}) pro Stimme; \\ 16 & Teiltöne (\textit{partials}) pro Teiltonreihe. \\ \end{tabular} Das ergibt 48 Teiltöne pro Stimme, 288 Teiltöne pro Manual und 1152 Teiltöne insgesamt. Ganze zwei der drei Teiltonreihen sind nur für die Einschwingvorgänge zuständig, für die das Ohr besonders empfindlich ist. Zu Beginn wird der Akku geleert: \begin{footnotesize} \begin{verbatim} : empty-accu-A 0e accu-AL* f! 0e accu-AR* f! ; \end{verbatim} \end{footnotesize} Der Tongenerator für das Manual A (\texttt{waver--A}) durchläuft drei geschachtelte \texttt{DO\dots{}LOOP}--Schleifen, die äußere für 6 Stimmen pro Manual, die mittlere für 3 Teiltonreihen pro Stimme, die innere für 16 Teiltöne pro Reihe. Die Schleifen--Indizes werden in Values geladen, das erschien mir am übersichtlichsten. Die Daten aller Teiltöne werden der Kurvenberechnung \texttt{sinus--A} zugeführt, wo die Sinuspunkte ermittelt werden. Die Aktion \textit{multiply by partial\#} ist nötig, weil die höheren Teiltöne den Sinus entsprechend schneller durchlaufen. Der berechnete Sinuswert wird sodann mit dem zugehörigen aktuellen Amplitudenwert multipliziert und im Akku mit den anderen Stimmen aufaddiert. Es gibt dreierlei Dämpfungsvorgänge: das normale Abklingen eines gezupften Tons, \emph{„fading"'} genannt, dann das Nachklingen eines nominell beendeten Tons, \emph{„sustain,"'} sowie das, was ich \emph{„staccato"'} genannt habe. \begin{figure} \begin{center} \includegraphics[scale=1.0]{2011-03/Bild-4} \caption{Dreierlei Dämpfungstypen} \end{center} \end{figure} Hier sind alle drei zu sehen: Die Amplitude schwindet langsam („fading"'), am Ende sieht man den Nachklang eines beendeten Tons („sustain"'), und zwischen den Tönen die „staccato"'--Dämpfung, ohne die unliebsame hörbare Spitzen auftreten würden. „staccato"' wird nur vor einem neuen Ton gebraucht, nicht vor einer Pause --- da gilt „sustain."' Deshalb ist eine Vorausschau („new tone following?"') nötig, beiläufig erwähnt. Die Anfangs--Amplituden der Teiltöne sind durch die Registrierung festgelegt. Nicht benutzte Stimmen könnten eigentlich unterdrückt werden, im Moment durchlaufen sie noch den Generator, den das nicht weiter stört. Phase inkrementieren erfolgt nur bei den Grundtönen, denn die Obertöne hängen als Vielfache davon ab. \begin{footnotesize} \begin{verbatim} : phase-incr-A v/m_ :freq-A* f@ \ frequency vibr-A* f@ f/ \ 1-x (pitch down) v/m_ :phase-AL* f+! \ increment phase (L) v/m_ :freq-A* f@ \ frequency vibr-A* f@ f* \ 1+x (pitch up) v/m_ :phase-AR* f+! ; \ increment phase (R) \end{verbatim} \end{footnotesize} Die sechs Stimmen pro Manual haben jede ihre eigene Sinus--Phase. Die kleine Korrektur der Tonhöhe („pitch--down,"' „pitch--up"') für die beiden Stereo--Kanäle bewirkt eine leichte \textit{Schwebung}, wie man sie vom Klavier kennt, das für einzelne Töne sogar drei leicht gegeneinander verstimmte Saiten vorsieht. Der Timer (\texttt{timer-A} in \texttt{waver-A}) bestimmt die Dauer eines Tons. Hier wird er heruntergezählt: \begin{footnotesize} \begin{verbatim} : timer-decr-A -1 timer-A +! ; \end{verbatim} \end{footnotesize} Am Ende werden die Akkuwerte der vier Manuale zusammengemischt (mono oder stereo): \begin{footnotesize} \begin{verbatim} : store-accu-A accu-AL* f@ accu-L* f+! \ mono & stereo stereo_ IF accu-AR* f@ accu-R* f+! \ stereo only THEN ; \end{verbatim} \end{footnotesize} Damit ist Generator A beschrieben. Die Manuale B, C und D werden separat, aber analog bedient. Aufgerufen werden die vier Routinen wie folgt: \begin{verbatim} : waver ( --) 0e accu-L* f! 0e accu-R* f! manual-A_ IF waver-A THEN manual-B_ IF waver-B THEN manual-C_ IF waver-C THEN manual-D_ IF waver-D THEN accu-L* f@ stereo_ IF accu-R* f@ THEN write-frame ; \end{verbatim} \section{Die Wave--Datei} Mit write--frame kommen wir zum Thema \textit{Aufbau der Wave--Datei}. Zur Illustration eine lächerlich kleine Datei im little--endian--Format, die als „Musik"' nur 8 Bytes enthält, nämlich \texttt{\$1111, \$2222, \$3333, \$4444}. \begin{footnotesize} \begin{verbatim} --------------------------------------------------------- 0: 52 49 46 46 5C 00 00 00 57 41 56 45 66 6D 74 20 10: 10 00 00 00 01 00 02 00 44 AC 00 00 88 58 01 00 20: 04 00 10 00 64 61 74 61 08 00 00 00 11 11 22 22 30: 33 33 44 44 4C 49 53 54 28 00 00 00 51 57 45 34 40: 34 20 2D 20 51 27 73 20 57 61 76 65 20 45 6E 67 50: 69 6E 65 20 31 37 2D 61 75 67 2D 30 38 20 31 33 60: 3A 33 30 20 --------------------------------------------------------- 0 0000 52494646 RIFF groupID 4 0004 5C000000 92 << to be patched waveChunkSize 8 0008 57415645 WAVE riffType --------------------------------------------------------- 12 000C 666D7420 fmt formatChunkID 16 0010 10000000 16 formatChunkSize 20 0014 0100 1 no compr wFormatTag 22 0016 0200 2 stereo (mono: 1) wChannels 24 0018 44AC0000 44100 (draft: 11025) dwSamplesPerSec 28 001C 10B10200 176400 (draft: 44100) dwAvgBytesPerSec 32 0020 0400 4 (bytes per frame) wBlockAlign 34 0022 1000 16 (blkalign/chnls*8) wBitsPerSample --------------------------------------------------------- 36 0024 64617461 data dataChunkID 40 0028 08000000 8 << to be patched dataChunkSize 44 002C 1111 $1111 (sample 1 left) 46 002E 2222 $2222 (sample 1 right) 48 0030 3333 $3333 (sample 2 left) 50 0032 4444 $4444 (sample 2 right) --------------------------------------------------------- 52 0034 4C495354 LIST listChunkID 56 0038 28000000 40 << to be patched listChunkSize 60 003C 51574534 QWE44 - Q's Wave (text) 64 0040 34202D20 68 0044 51277320 72 0048 57617665 76 004C 20456E67 Engine (text) 80 0050 696E6520 84 0054 31372D61 17-aug-08 13:30 (text) 88 0058 75672D30 92 005C 38203133 96 0060 3A333020 --------------------------------------------------------- \end{verbatim} \end{footnotesize} Der Anfang ist immer gleich: \texttt{RIFF}, Gesamt(rest)länge und \texttt{WAVE}. Es folgen die drei Abschnitte („Chunks"' genannt): Format, Daten und Text. Im Format--Chunk (\texttt{fmt}) wird gewählt: Mono/Stereo und Nomalbetrieb/Draft (44 KHz oder 11 KHz). Die Werte für \texttt{wFormatTag}, \texttt{wBlockAlign} und \texttt{wBitsPerSample} sind hier unveränderlich. Der Daten--Chunk (\texttt{data}) läutet die Musik ein: Sie wird in einer Folge von 16--bit--Frames abgelegt und enthält einfache Werte, quasi wie eine digitalisierte Schallplattenrille. Im List--Chunk (\texttt{LIST}) wird Text versteckt, der nur durch einen Dump zum Vorschein kommt. Während des Aufbaus der Wave--Datei sind die Längen der Chunks nicht bekannt, daher müssen sie später gepatcht werden. Wave--Dateien kennen noch weitere Chunks, die hier nicht gebraucht werden --- Wikipedia weiß da mehr. Mit \texttt{write-frame} stehe ich noch in der Pflicht: \begin{footnotesize} \begin{verbatim} : write-frame ( stereo: r-L r-R --- | mono: r-L --) mono_ IF f>s minimax w>buf \ in Datei schreiben 2 data-bytes +! ELSE fswap f>s minimax \ Amplitude begrenzen (L) [ hex ] FFFF and \ low word f>s minimax \ Amplitude begrenzen (R) [ hex ] 10000 * + \ high & low word [ decimal ] n>buf \ in Datei schreiben 4 data-bytes +! \ Datenbytes aufaddieren THEN ; \end{verbatim} \end{footnotesize} Die Werte bewegen sich (mit 16 bit pro Kanal) zwischen $-2^{15}$ und $+2^{15}-1$. Mit \texttt{minimax} werden diese Grenzen erzwungen. \texttt{w>buf} ruft \texttt{\$>buf} auf und schreibt damit in einen Zwischenpuffer, der sich bei Überlauf in die Wave-Datei ergießt. \begin{footnotesize} \begin{verbatim} \ Schreibpuffer laden und ggf. in die Wave-Datei über- \ tragen. So lange die Daten nicht in den Puffer passen, \ wird der Puffer randvoll gefüllt und sodann in die \ Wave-Datei entleert. Dann wird der Datenrest in den \ Puffer geladen. : $>buf ( addr len --) BEGIN bufsize bufcnt @ - ( free) >r dup r@ >= \ len >= free ? WHILE over wbuffer bufcnt @ + r@ move \ transfer swap r@ + swap \ update addr r> - \ update len wbuffer bufsize >file \ empty wbuffer 0 bufcnt ! \ zero byte count REPEAT r> drop \ discard free dup \ len > 0 ? IF >r wbuffer bufcnt @ + r@ move \ transfer bufcnt @ r@ + bufcnt ! \ update byte count bytecnt @ r> + bytecnt ! \ dataChunk byte count ELSE 2drop \ discard addr len THEN ; \ 4-byte-Wert in den Schreibpuffer schreiben : n>buf ( u --) tmp ! tmp 4 ( addr len) $>buf ; \ 2-byte-Wert in den Schreibpuffer schreiben : w>buf ( u --) tmp ! tmp 2 ( addr len) $>buf ; \ 1-byte-Wert in den Schreibpuffer schreiben : c>buf ( u --) tmp ! tmp 1 ( addr len) $>buf ; \end{verbatim} \end{footnotesize} \section{Partitur: Die Manualzeilen (Notenzeilen)} Das Thema Partitur bringt mich in ein Dilemma: Es hat mir das meiste Kopfzerbrechen bereitet und hat mit Forth eigentlich nichts zu tun. In der ersten Version der \emph{Wave Engine} konnten noch Forth--Worte eingestreut werden. Das habe ich verworfen, mit Rücksicht auf Nicht--Forther und um die Eingabe fehlerfester zu machen. Ich möchte nun so vorgehen, dass ich die Regeln für die Partitur quasi neu entwickle, denn das Ringen um Lösungen ist doch eigentlich der interessanteste Teil eines Projekts. Bevor die Angelegenheit forthig werden kann, muss die Aufgabenstellung klar sein, sonst hängt der Code in der Luft. Beginnen wir mit den \textit{Musiknoten}, die aufwändige Registrierung wird dann später behandelt. Die Partitur ist eine einfache Textdatei. Ich gebrauche im Folgenden die Bezeichnung \emph{Partiturzeilen}, wobei diese Zeilen unabhängig von Textdateizeilen sind. Sie können sich über mehrere Textdateizeilen erstrecken oder auch zu mehreren in einer Textdateizeile Platz finden. Irgendwann habe ich mich entschlossen, Partiturzeilen durch geschwungene Klammern kenntlich zu machen. Die wichtigsten Partiturzeilen sind die \emph{Manualzeilen} für die vier (virtuellen) Manuale: \begin{verbatim} A{ ... } B{ ... } C{ ... } D{ ... } \end{verbatim} Enthalten müssen sie in erster Linie \textit{Musiknoten}, die aus Tonhöhe und Tondauer bestehen. Um Platz zu sparen, wird die Tondauer nicht jeder einzelnen Note zugeordnet, sondern gilt für alle folgenden Noten. Daneben gibt es den Unterstrich \texttt{\_} für eine \emph{Pause} und die Tilde \texttt{\~} für die \emph{Verlängerung} einer vorhergehenden Note. Als Beispiel hier der Anfang von „Hänschen klein"' in C--Dur auf Manual A: \begin{footnotesize} \begin{verbatim} A{ :1/4 4G 4E 4E ~ 4F 4D 4F ~ 4C 4D 4E 4F 4G 4G 4G ~ } \end{verbatim} \end{footnotesize} Es handelt sich um Viertelnoten, zwischen die mit Hilfe der Tilde drei Halbenoten eingestreut sind. Die Ziffer 4 gibt die Oktave an, in der sich der Song abspielt. \begin{footnotesize} \begin{verbatim} „0C" bedeutet ,,C (Subkontra-Oktave) 16,3 Hz „1C" bedeutet ,C (Kontra-Oktave) 32,6 Hz „2C" bedeutet C (Große Oktave) 65,2 Hz „3C" bedeutet c (Kleine Oktave) 130,4 Hz „4C" bedeutet c' (Eingestrichene Oktave) 260,8 Hz „5C" bedeutet c'' (Zweigestrichene Oktave) 521,6 Hz „6C" bedeutet c''' (Dreigestrichene Oktave) 1043,1 Hz „7C" bedeutet c'''' (Viergestrichene Oktave) 2086,3 Hz „8C" bedeutet c''''' (Fünfgestrichene Oktave) 4172,5 Hz „9C" bedeutet c'''''' (Sechsgestrichene Oktave) 8345,0 Hz \end{verbatim} \end{footnotesize} Das ist der Tonumfang von \emph{9 Oktaven}. Die Frequenzangaben leiten sich vom Kammerton a' mit exakt 440 Hz ab. Mit diesen Vereinbarungen können wir schon fast musizieren, aber ein bisschen was fehlt noch. So muss dafür gesorgt sein, dass die Manuale zugleich erklingen wie bei einer echten Orgel. „Hänschen klein"' mit Bass--Begleitung kann etwa so aussehen: \begin{footnotesize} \begin{verbatim} A{ :1/4 4G 4E 4E ~ 4F 4D 4F ~ 4C 4D 4E 4F 4G 4G 4G ~ } D{ :1/4 3C _ 3C _ 2G _ 2G _ 3C _ 2G _ 3C _ 3C _ } \end{verbatim} \end{footnotesize} Da die Zeilen „normal"' gelesen werden, ist klar, dass die erste Zeile zwischengespeichert werden muss, um mit der zweiten zugleich zu erklingen. Und es ist auch klar, dass beide Zeilen zeitlich gleich lang sein müssen, da sonst alles Weitere nicht mehr synchron laufen kann. Ich habe vor, bei Verletzung der Sorgfaltspflicht einen automatischen Ausgleich am Zeilenende einzubauen, aber den gibt es noch nicht. Damit zusammenhängend besteht die Forderung, dass die Tondauer--Angaben in Bruchform ohne Rundungen auskommen, da sonst die gemeinsame Zeilenlänge nicht garantiert ist. Ich habe gegrübelt und folgendes beschlossen: Das Maß für die Tondauer wird gerastert mit einer Auflösung von 1/2880 einer Ganznote. Und nicht etwa eines Taktes, denn ein Dreivierteltakt bringt nur drei Viertelnoten unter. Das Programm sorgt dafür, dass nur Nenner zugelassen werden, die in 2880 ganzzahlig enthalten sind. Die Zähler sind beliebig. Wichtig war mir, dass sich 2880 durch 9 teilen lässt, der Triolen wegen. \texttt{:1/4} ist gleichbedeutend mit \texttt{:720/2880} oder nur \texttt{:720} Des Weiteren stellte sich in der Praxis heraus, dass eine \emph{Justage der Notendauern} erforderlich ist. Bei der Phrasierung von Tonfolgen klingt „dadldadl"' einfach besser als „dadadada."' Also sind zwei Klangfarben im Wechsel nötig, und zudem sollen „da"' und „dl"' verschiedene Länge aufweisen können, um den Melodierhythmus nicht steif und zackig erklingen zu lassen. Es „swingt"' bzw. „groovt"' dann einfach nicht. Die Freiheiten, die sich ein menschlicher Interpret nimmt, müssen in der Partitur mitformuliert werden, leider (oder auch nicht). Die „dadldadl"'--Notenpaare müssen also justiert werden: \begin{footnotesize} \begin{verbatim} A{ :1/8+90 4C :1/8-90 4D :1/8+90 4E :1/8-90 4F } \end{verbatim} \end{footnotesize} Die erste Note wird um 90 Einheiten verlängert, die zweite um 90 Einheiten verkürzt, und so weiter. Hier eine vereinfachte Schreibweise, die auch mit längeren Rhythmus--Ketten funktionieren würde: \begin{verbatim} A{ :1/8+90,1/8-90; 4C 4D 4E 4F } \end{verbatim} Die Schreibweise der Noten ist noch nicht komplett. \texttt{3C 3D 3E 3F 3G 3A 3B 3C} --- das ist die \textit{C--Dur}--Tonleiter in der 3. (der „kleinen"') Oktave. Dazu kommen aber noch die „schwarzen Tasten:"' Die Note \textit{fis} wird als \texttt{3F\#}, \textit{ges} als \texttt{3Gb} geschrieben. Das deutsche \textit{h} wird zu \texttt{3B}, das deutsche \textit{b} zu \texttt{3Bb} --- das entspricht der internationalen Gepflogenheit. Und weil wir gerade dabei sind (siehe Abb.\@ \ref{fig:intervalle}): Ein unterstrichenes \textit{des}, das um ein Komma höher erklingt als ein \textit{Des}, wird in der eingestrichenen Oktave als \texttt{4Db\textbackslash}, und ein überstrichenes \textit{fis}, das um ein Komma tiefer klingt als ein \textit{Fis}, als \texttt{4F\#/} geschrieben. Hier noch ein Vorschlag für übliche Notenschrift: \begin{figure} \begin{center} \includegraphics[scale=1.0]{2011-03/Bild-5} \caption{Feinjustage für (fast) reine Stimmung} \end{center} \end{figure} Ein Manual muss auch \emph{mehrstimmig} spielen können. Dafür braucht es eine Notation, und die sieht so aus: Was von eckigen Klammern umschlossen wird, ertönt zugleich. In diesem Fall zwei Stimmen: \begin{verbatim} A{ :1/4 [4G 4E] [4E 4C] [4E 4C] [~ ~ ] } A{ [4F 4D] [4D 3B] [4F 3B] [~ ~ ] } A{ [4C 3E] [4D 3G] [4E 4C] [4F 4D] } A{ [4G 4E] [4G 4E] [4G 4E] [~ ~ ] } \end{verbatim} Geltungsbereiche, auch schachtelbar, von Notenlängen und/oder Habitus--Marken werden rundgeklammert: \begin{footnotesize} \begin{verbatim} A{ :1/4 *1 4C 4D 4E ( :1/8 *2 4F 4G 4A 4B ) 5C 4C } \end{verbatim} \end{footnotesize} Dann gibt's noch (schachtelbare) Schleifen. Die vier Töne in spitzen Klammern werden zweimal gespielt: \begin{footnotesize} \begin{verbatim} A{ *1 :1/4 4C 4D 4E 4F <2 4G 4A 4B 5C > 5D 5E 5C 4C } \end{verbatim} \end{footnotesize} Damit sind die Manualzeilen fast beschrieben --- bis auf eine Kleinigkeit, die ich \emph{Habitus} genannt habe. Das ist eine simple Marke (Beispiel: \texttt{*12}), die eine vorher definierte Umregistrierung vornimmt oder (im einfachsten Fall) einen Akzent für die nächste Note setzt, diese also etwas lauter ertönen lässt als ihre Nachbarn. Und das war's auch schon mit den Manualzeilen. Dies: \begin{footnotesize} \begin{verbatim} fix{ ... } set{ ... } reg{ ... } {{ title }} [[ comment ]] [[[ comment ]]] \end{verbatim} \end{footnotesize} sind weitere Partiturzeilen--Typen. Dazu später mehr. So werden Arrays gebastelt: \begin{footnotesize} \begin{verbatim} \ Ein 4-byte-Array :abc und der Zugriff darauf. \ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \ | »0« | »1« | »2« | »3« | »4« | »5« | \ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \ create abc| 6 4 * allot \ 6 4-byte-Werte \ : :abc ( cell# -- addr) \ cell# = 0...5 \ 4 * abc| + ; \ \ 111 0 :abc ! ok \ 1. Wert schreiben \ 666 5 :abc ! ok \ 6. Wert schreiben \ 0 :abc @ . 111 ok \ 1. Wert lesen \ 5 :abc @ . 666 ok \ 6. Wert lesen \ Ein Floatpoint-Array :xyz* und der Zugriff darauf. \ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \ | »0« | »1« | »2« | \ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \ create xyz*| 3 8 * allot \ 3 8-byte-Werte \ : :xyz* ( cell# -- addr) \ cell# = 0...2 \ 8 * xyz*| + ; \ \ 111e 0 :xyz* f! ok \ 1. Wert schreiben \ 666e 2 :xyz* f! ok \ 3. Wert schreiben \ 0 :xyz* f@ f. 111 ok \ 1. Wert lesen \ 2 :xyz* f@ f. 666 ok \ 3. Wert lesen \end{verbatim} \end{footnotesize} \section{Goodies} Die folgenden Funktionen stecken in einer Prelude--Datei, die auch anderswo zum Einsatz kommt. \begin{footnotesize} \begin{verbatim} \ --------------------------------------------------------- \ Increment a cell in memory. \ a may be »' value-name >body«. : incr ( a --) dup @ 1+ swap ! ; \ --------------------------------------------------------- \ Check a char for matching with a given char table \ (counted string). If matching, return the character, \ otherwise zero. Called by _leading and _trailing . : matching? ( c a -- c|0) ( c a) dup 0= ( c a f) IF ." matching? error" QUIT THEN count ( c a u) 0 ?DO over swap ( c c a) dup i + c@ ( c c a c') rot xor 0= ( c a f) IF nip i + c@ UNLOOP EXIT THEN ( char) LOOP 2drop 0 ; ( zero) \ --------------------------------------------------------- \ Discard leading blanks (sp, tab) from a character string. here 2 c, 32 ( sp) c, 9 ( tab) c, constant blanks \ same as: create blanks 2 c, 32 ( sp) c, 9 ( tab) c, : _leading ( $a1 u1 -- $a2 u2) dup 0= IF EXIT THEN BEGIN over c@ blanks matching? ( a u c|0) WHILE proceed ( a' u') REPEAT ; \ --------------------------------------------------------- \ Discard trailing blanks from a character string. \ Like -trailing , but also skipping tabs. : _trailing ( $a1 u1 -- $a2 u2) dup 0= IF EXIT THEN BEGIN 1- 2dup + c@ ( a u-1 c) blanks matching? ( a u-1 c|0) 0= ( no blank) ( a u-1 0|-1) UNTIL 1+ ; ( a u) \ --------------------------------------------------------- \ In Gforth vordefiniert: \ OPEN-FILE ( c-addr u wfam -- wfileid wior) \ READ-LINE ( c_addr u1 wfileid -- u2 flag wior) \ --------------------------------------------------------- \ Read a line from a text file and store it in PAD. \ Trailing blanks (sp, tab) are cut off. \ Input: file-ID, memory address, memory size. \ Output: u (number of characters successfully read, \ not including line delimiters, if any); f for flag. \ If the file is elapsed, flag is false, otherwise true. : read-a-line { fileID pad padsize -- pad u f } pad padsize fileID READ-LINE ( u f wior) ( 0=ok) drop >r ( u) ( f) pad swap _trailing ( a u) ( f) r> ; ( a u f) \ --------------------------------------------------------- \end{verbatim} \end{footnotesize} \section{Der Partitur--Interpreter} Im Interpreter wird's komplexer. Ich kommentiere (funktionell) von der Hauptschleife aus rückwärts. Diese tut nichts anderes als im Wechsel Interpreter und Generator aufzurufen. \texttt{interpreter} findet über \texttt{headers extract-token} den Kopf einer Partiturzeile. Im Fall einer Notenzeile wird \texttt{noteline} aufgerufen. Dort wird eine Schleife durchlaufen, bis das Ende der Notenzeile erreicht ist. \texttt {read-multiline} (in \texttt{exec-note}) sorgt dafür, dass Partiturzeilen mehrere Dateizeilen in Anspruch nehmen können. Wenn \texttt{extract-0..9} den Anfang einer Note wie 4F\# erkennt, wird \texttt{note} gerufen. Dort wird mit \texttt{extract-A..G} (nicht dargestellt) ein Ton der Tonleiter C--D--E--F--G--A--B extrahiert, sodann mit \texttt{extract-\#b} und \texttt{extract-\textbackslash/} ein mögliches Vorzeichen und/oder Komma--Vorzeichen erkannt. Im Falle, dass \texttt{extract-0..9} auf etwas anderes als eine Note stößt, also false liefert, wird mit \texttt{notesym extract-token} nach weiteren zulässigen Symbolen gefahndet. Bleiben wir aber zunächst mal bei den Noten. \begin{footnotesize} \begin{verbatim} \ --------------------------------------------------------- \ Aus 1..53 und oct# = 0..9 den Notenwert 1..478 bilden. : justify ( val oct# -- notenwert) 0 max 9 min swap 52 + 53 mod swap 53 * + 1+ dup 478 > IF 53 - THEN ; --------------------------------------------------------- \ Eine Musiknote (z.B. 4F#/ oder 4Eb\) wird verarbeitet. \ oct# ist die Oktavnummer (0..9). \ extract-A..G liefert die Werte der Grundtöne \ (C= 1 D= 10 E=19 F=23 G=32 A=41 B=50). \ Vorzeichen (#, b) verändern den Wert um 5 bzw -5. \ Komma-Vorzeichen (\, /) verändern den Wert um 1 bzw -1. \ Vorzeichen können auch mehrfach auftreten. \ Der resultierende Wert wird an pitch! übergeben. \ Called by exec-note. : note ( a u oct# -- a' u' true) >r extract-A..G ( a u c|val f) ( oct#) not IF ." note " |ERR| THEN >r ( a u) ( oct# val) BEGIN extract-#b ( a u c|val' f) WHILE r> + >r ( #=5, b=-5) ( a u) REPEAT ( a u c) drop BEGIN extract-\/ ( a u c|val' f) WHILE r> + >r ( \=1, /=-1) ( a u) REPEAT drop r> r> ( a u val" oct#) ( val oct#) justify ( a u val') ( val) pitch! ( a u) bracket-count @ note-type ! ( a u) \ for load-fifo fetch-habit ( habit!) ( a u) \ habit to coll fetch-durat durat! ( a u) \ durat to coll true pend ! ( a u) \ load-fifo true ; ( a u true) \ --------------------------------------------------------- \ As long as string (a u) empty, read a new line, \ skipping leading blanks : read-multiline ( a u -- a' u') BEGIN dup 0= ( a u f) WHILE 2drop sheetfileID_ ( id) pad pad-size read-a-line ( a u f) linecount incr not ( a u f) IF ." read_multiline error " QUIT THEN REPEAT _leading ; \ --------------------------------------------------------- \ Fetch first non-blank char of the string given by a u. \ If the character is 0..9, return its value with f=true. \ Otherwise return char with f=false and a u unchanged. : extract-0..9 ( a u -- a' u' c|val f) _leading over c@ test-0..9 ( a u c|val f) IF >r proceed r> true ( a' u' val true) ELSE false ( a u c false) THEN ; \ --------------------------------------------------------- \ extract-token - Aus einer Zeichenkette wird ein Symbol \ extrahiert, das in einer Tabelle aufgesucht und dessen \ zugehöriger Token zurückgegeben wird. Schema (tabel): \ »symbol token symbol token symbol token 0«. \ Leading blanks (sp, tab) allowed. \ \ Wenn f=t, dann ist token gültig und a u zeigt auf den \ Rest der Kette. Wenn f=0, dann ist token ungültig und \ die Adresse der Kette unverändert. \ Called by: exec-note, interpreter. : extract-token ( str-a u tab-a -- str-a' u' token f) >r _leading r> BEGIN >r over r@ @ count ( a u a $a $u) ( a') tuck str= not ( a u f) ( a') WHILE r> 2 cells + ( a u a") dup @ 0= ( a u a" f) IF drop 0 0 EXIT THEN ( a u 0 0) REPEAT r@ @ count nip ( a u $u) ( a') rot over + -rot - ( a" u") ( a') r> cell+ true ; ( a" u" a' t) \ --------------------------------------------------------- \ Zulässige Symbole (außer Noten selbst) einer Notenzeile. \ Verwendet in exec-note via extract-token. : notesym$ ['] paran-left c" (" ['] paran-right c" )" ['] bracket-left c" [" ['] bracket-right c" ]" ['] chevron-left c" <" ['] chevron-right c" >" ['] duration c" :" ['] habitus c" *" ['] pause c" _" ['] elong c" ~" ['] quotes c" '" ['] replic c" %" ['] note-brace-right c" }" ['] note-dummybar c" |" ; create notesym notesym$ , , , , , , , , , , , , , , , , , , , , , , , , , , , , 0 , \ --------------------------------------------------------- \ Element innerhalb einer Notenzeile interpretieren. \ Ruft load-fifo, wenn ein Symbol (außer '>') erkannt wird. \ Damit ist es möglich, Schleifenenden ('>') der vorigen \ Note mitzugeben, damit die aktuelle Note wieder ein \ Schleifenbeginn sein kann. Das Zeilenende ('}') gilt \ hier ebenfalls als Zeilensymbol. Called by noteline. : exec-note ( a u -- a' u' f) read-multiline ( a u) extract-0..9 ( a u c|num f) IF load-fifo note ( a u) ELSE drop over c@ (>) <> ( a u f) IF load-fifo THEN ( a u) notesym extract-token not ( a u token f) IF ." exec-note " |ERR| THEN ( a u) PERFORM ( a u f) THEN ; \ --------------------------------------------------------- \ noteline - >Aline/>Bline/>Cline/>Dline detected \ - Push habits onto the habits stack. \ - Execute inline items until '}' detected. \ - Pop habits from habits stack. \ Called by >Aline|>Bline|>Cline|>Dline : noteline ( a u -- a' u') 1 voice# ! BEGIN exec-note ( a' u' f) ( success) not UNTIL ; \ --------------------------------------------------------- \ Partitur-Zeilen. Die Notenzeilen geben flag=-1 zurück, \ die anderen 1. (-1 bewirkt Aufruf des Generators, \ nur nach Notenzeile sinnvoll.) : >fixline ( a u --) fixline 1 ; : >setline ( a u --) setline 1 ; : >regline ( a u --) regline 1 ; : >Aline ( a u --) man-A! noteline -1 ; : >Bline ( a u --) man-B! noteline -1 ; : >Cline ( a u --) man-C! noteline -1 ; : >Dline ( a u --) man-D! noteline -1 ; : >title ( a u --) titleline 1 ; : >comment ( a u --) commentline 1 ; \ --------------------------------------------------------- \ --------------------------------------------------------- \ Zulässige Partiturzeilen-Köpfe. \ Verwendet in interpreter via extract-token. : headers$ ['] >fixline c" fix{" ['] >setline c" set{" ['] >regline c" reg{" ['] >Aline c" A{" ['] >Bline c" B{" ['] >Cline c" C{" ['] >Dline c" D{" ['] >title c" {{" ['] >comment c" [[" ; create headers headers$ , , , , , , , , , , , , , , , , , , 0 , \ --------------------------------------------------------- \ Hier wird die Partitur Zeile für Zeile interpretiert. \ Flag f ist gewöhnlich true, bei '.' oder vergeblichem \ read_a_line false. Called by main-loop. : interpreter ( a u -- a' u' f) dup ( a u u) BEGIN drop ( a u) period_ IF ." period " false EXIT THEN BEGIN _leading dup 0= ( elapsed?) ( a u f) WHILE 2drop sheetfileID_ ( id) pad pad-size read-a-line ( a u f) linecount incr ( a u f) not IF false EXIT THEN ( a u 0) REPEAT over c@ (.) = ( a u f) IF true to period_ false ( a u) ELSE headers extract-token ( a u token f) not IF |INVALID-HDR| THEN ( a u token) PERFORM ( f) ( a u f=-1|1) THEN dup 1 <> ( a u f f') UNTIL ; ( a u f) \ --------------------------------------------------------- \ Wenn der Generator keinen Nachschub findet, wird die \ Generatorschleife verlassen und der Interpreter \ aktiviert. : generator ( --) BEGIN loader ( f) \ load latch, success? WHILE waver \ load accu, build wave REPEAT \ anything to load? ; \ back to partiture \ --------------------------------------------------------- \ H a u p t s c h l e i f e : main-loop ( --) pad 0 ( a 0) BEGIN interpreter ( f) ( a u f) WHILE generator ( a u) REPEAT ; \ --------------------------------------------------------- \end{verbatim} \end{footnotesize} Damit soll es für diesmal genügen. Ein paar aufscheinende Funktionen (z.\,B.\@ \texttt{fetch-habit}, \texttt{load-fifo} etc.) wurden nicht besprochen, das würde im Moment zu weit führen. Elegantes Forth sieht sicherlich anders aus, das ist mir klar, denn das Programm ist nie der Bastelphase entstiegen. Viel Versuch und Irrtum. Aber wenn's dann klingt wie es soll, dann ist die Freude groß. Eine Reihe wichtiger Effekte wird noch vermisst, aber die kommen noch (und erfordern, den Generator aufzubohren). Die Registrierung ist ein Riesen--Thema, das beim nächsten Mal angegangen werden soll. Das wird wieder gehörig Raum für die Aufgabenstellung brauchen. Das gesamte Programm, soweit es am Laufen ist, kann wegen seines Umfangs unmöglich abgedruckt werden. Auch bin ich mit dringender Kosmetik noch gut beschäftigt, ehe fremdes Auge auf das Machwerk fällt. \hrulefill \section{Link} Eine Hörprobe nebst Partitur und Notenblatt findet sich im Dateibereich der Website der Forth--Gesellschaft unter\\ \url{http://www.forth-ev.de/filemgmt/viewcat.php?cid=54} \end{multicols} \vfill \begin{figure} \centering \includegraphics[width=\textwidth]{2011-03/TopOne_01_NotenZeile} \caption{Top One--Notenzeile} \end{figure} \end{document}