% 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 (2)} \ifx\shorttitle\undefined\else %\shorttitle{WE(2)} % der Titel ist kurz genug! \fi \author{Hannes Teich} \begin{document} \maketitle \begin{abstract} Weiter geht's mit der virtuellen „viermanualigen“ PC-Orgel, die mit Hilfe eines Programms in Gforth eine ASCII-Partitur in eine Wave-Datei wandelt. In der letzten Folge wurden vorgestellt: die Grundfunktion des Sinus-Generators, der Aufbau der Wave-Datei sowie die Interpretation von Notenzeilen. Diesmal wenden wir uns der Registrierung und dem Datentransport zu. Zunächst eine grobe Übersicht „über alles“. \end{abstract} %Abbildung 1: Vereinfachtes Schema der „Wave Engine“ \begin{figure*}[b] \begin{center} \includegraphics[width=0.8\textwidth]{2012-01/Teich-Bild1WE2} %\includegraphics{2012-01/Teich-Bild1WE2} \caption{\label{bild1}Vereinfachtes Schema der Wave Engine} \end{center} \end{figure*} \begin{multicols}{2} Die Beschickung des Sinus-Generators ist, wie \textbf{Abb. \ref{bild1}} zeigt, ziemlich üppig. Ursprünglich hatte ich mit der \textit{Wave Engine} (Kosename: \textit{Waver}) nur ein Werkzeug zum Experimentieren mit reiner Stimmung im Sinn. Der Riesen-Aufwand wäre hierfür nicht nötig gewesen, aber ich konnte ihn mir einfach nicht verkneifen. :-) Es sind zwar noch nicht alle Funktionen am Laufen, aber das Konzept steht soweit und wird sich – durch diese Artikelserie gedopt – hoffentlich bald akustisch in voller Pracht präsentieren können. Die Regeln für die Partitur können jedenfalls jetzt schon beschrieben werden. Was bislang akustisch geht, zeigen die Links am Ende. In der Partitur stecken die Vorgaben für alle Aktionen, als da sind: Feste und veränderbare Voreinstellungen, die Registrierung, die Musiknoten sowie Kommentare, die teilweise in die Wave-Datei übernommen werden. Die Registrierung kann direkt erfolgen oder in Records abgelegt werden, von wo die Inhalte bei Bedarf durch \textit{Habitus}-Marken abgeholt werden, die ins Notenbild eingestreut sind, ohne dieses unnötig aufzublähen. Hinter solchen Marken kann sich sehr viel Registrierung verbergen. Die Notenzeilen für die vier virtuellen \textit{Manuale} werden nacheinander gelesen, sollen aber zugleich erklingen. Das macht einen \textit{FIFO}-Pufferspeicher nötig, aus dem der \textit{Loader} jeweils das zum \textit{Generator} schickt, was dieser gerade benötigt. Der dem Interpreter nachgeschaltete Collector sorgt dafür, dass die Daten im rechten Format an den Pufferspeicher übergeben werden. \section{Die Funktionen im Einzelnen, grob beleuchtet} \begin{itemize} \item \textbf{tuning} – die Stimmungshöhe, normalerweise 440 Hertz für das „eingestrichene A“. \item \textbf{pitch} und \textbf{duration} – Höhe und Dauer eines Tons. \item \textbf{habitus} – Marken, durch welche die „Records“ angezapft werden. \item \textbf{tempo} – Anzahl Viertelnoten pro Minute. \item \textbf{ritard} – langsamer werden (ritardando). Negativ: schneller. \item \textbf{volume} – Partialtöne einzeln und alle gemeinsam. \item \textbf{accent} – Lautstärke für einen oder mehrere Töne erhöhen/erniedrigen. \item \textbf{crescendo} – anschwellende Lautstärke. Negativ: abschwellen. \item \textbf{fading}, \textbf{sustain}, \textbf{staccato} – Abklingen, Nachklingen, Zwischenpausen. \item \textbf{mellow} – weicher Toneinsatz. \item \textbf{transpo} – Transponierung in andere Tonarten. \item \textbf{drift} – Verstimmung, z. B. zur Erzeugung der Naturseptime. \item \textbf{bender} – Ziehen der Tonhöhe. \item \textbf{angle} – Richtung der Tonquelle (stereo). \item \textbf{vibrato}, \textbf{tremolo}, \textbf{beat} – Frequenz-, Amplituden-, Phasenvibrato. \item \textbf{echo} – leiser werdendes Flatterecho. (Etwas dürftiger Ersatz für Hall; braucht viel Speicher.) \item \textbf{arpeggio} – gebrochene Akkorde. \item \textbf{formants} – von Tonhöhe unabhängige Vokale. Durch Berechnung der Obertöne erzeugt. Manche Vokale haben zwei Maxima, deshalb „doppelt“. \item \textbf{wahwah} – wechselnde Klangfarbe, etwa wie Trompete mit vorgehaltenem Hütchen. (Durch Überblendung zwischen zwei Vokalen erzeugt.) \end{itemize} Was fehlt noch? Außer Hall wüsste ich nichts mehr. Hall zu berechnen übersteigt meine Fähigkeiten. Immerhin kann man das Wave-Ergebnis nachträglich durch eine Hallmaschine laufen lassen. Oder man begnügt sich mit Flatterecho. (\textit{Les Paul} hat davon viel Gebrauch gemacht.) \textbf{vibrato} und \textbf{tremolo} unterstütze ich nur halbherzig, die Funktion \textbf{beat} reicht mir eigentlich. Sie klingt recht gut, etwa wie beim Vibraphon von \textit{Milt Jackson}. Bei einer Pfeifenorgel werden die Registerknöpfe gezogen, ehe Manuale und Pedal mani- und pedipuliert werden. Beim \textit{Waver} wird die Registrierung durch die Partitur vorgenommen. In beiden Fällen muss eine „Apparatur“ hinter den Kulissen für die Verwirklichung sorgen. Folgende Funktionen aus obiger Palette waren für das Tonbeispiel \textit{Top One} im Einsatz: \textbf{tuning, pitch, duration, tempo, volume, fading, sustain, staccato}. Schmerzlich vermisst wurden die Funktionen \textbf{mellow} und \textbf{bender}. \section{Weicher Toneinsatz im Prinzip} %Abbildung 2: Bildung des weichen Toneinsatzes \begin{figure} \begin{center} \includegraphics[width=1.0\textwidth]{2012-01/Teich-Bild2WE2} %\includegraphics{2012-01/Teich-Bild2WE2} \caption{\label{bild2}Bildung des weichen Toneinsatzes} \end{center} \end{figure} Die \textbf{Abb.\ref{bild2}} zeigt, wie ich mir die Generierung des weichen Toneinsatzes (\textbf{mellow}) vorstelle. Oben ist die Hüllkurve eines „gezupften“, abklingenden Tons zu sehen, wie er beim Thema \textit{Generator} beschrieben worden ist: Die Amplitude wird laufend mit einem Faktor „kleiner Eins“ multipliziert, was zu konstanten Halbwertszeiten führt. Die Kurve darunter entsteht auf gleiche Weise, nur mit stärkerer Dämpfung und mit vorgegebener Anfangsamplitude von (sagen wir) 100 Einheiten. Die dritte ist die auf den Kopf gestellte zweite Kurve, was durch Subtraktion von 100 bewerkstelligt wird. Außerdem wird durch 100 dividiert, damit sie als Dämpfungsfaktor dienen kann. Mit diesen Faktor-Werten wird die Ausgangskurve multipliziert, und – voila! – das Resultat kann sich sehen (und hören) lassen. \section{Implementieren des weichen Toneinsatzes} Wir betrachten nur \textit{Manual A}, denn die vier \textit{Manuale} sind gleich und selbständig. Jedes \textit{Manual} hat sechs Stimmen, jede Stimme hat drei Teiltonreihen, und jede dieser Reihen hat 16 Teiltöne. Man könnte den gewünschten Effekt jeder Stimme zuordnen (eine Einstellung für alle Stimmen), aber ich meine, die Teiltonreihen sollten separat behandelt werden können (also drei Werte für alle sechs Stimmen). \begin{verbatim} regA{ mellow=50,50,50; } regA{ *1 mellow=50,50,50; } \end{verbatim} Hier sind zwei Varianten einer \textbf{mellow}-Registeranweisung zu sehen: Für die drei Teiltonreihen des \textit{Manuals} A ist ein Dämpfungswert von 50 angegeben. Die erste Variante kann überall zwischen Notenzeilen auftauchen. Die zweite speichert die Vorgabe als \textit{Habitus} in einem Records-Pool, von wo sie durch Angabe von \texttt{*1} innerhalb einer Notenzeile geholt werden kann, um sogleich zu wirken. In solch einer Register-Zeile können mehrere (oder alle) Registeranweisungen enthalten sein. Auf die Interpretation von Partiturzeilen will ich an dieser Stelle aber nicht eingehen (die Syntax gibt’s paar Seiten weiter unten), sondern die \textbf{mellow}-Funktion als solche betrachten. Die Dämpfungskurve in \textbf{Abb. 2} reiht sich zwanglos in bereits vorhandene Dämpfungen (\textbf{fading}, \textbf{sustain}) ein. Einst waren die Vorgaben noch nicht in der Partitur, sondern Teil des Programms, was sehr hinderlich war. Der Anschaulichkeit halber greife ich aber gern auf diesen Zustand zurück. \begin{verbatim} : adjust ( u - r) 0 max 3000 min ?dup IF drop 3000 THEN draft_ IF 4 * THEN 1e ( u) s>f 1e5 f/ 1e f+ f/ ; 5 adjust 0 :fade-A* f! \ fading 18 adjust 1 :fade-A* f! 12 adjust 2 :fade-A* f! 50 adjust 0 :sust-A* f! \ sustain 50 adjust 1 :sust-A* f! 50 adjust 2 :sust-A* f! 50 adjust 0 :mellow-A* f! \ mellow 50 adjust 1 :mellow-A* f! 50 adjust 2 :mellow-A* f! \end{verbatim} Die Funktion \texttt{adjust} ist nötig, um für die Vorgaben handliche Werte zu kriegen. Sie macht das durch die Formel: \texttt{r = 1/(1+u/100000)}. Beispiele: \begin{verbatim} 1 adjust f. 0.99999 ok 5 adjust f. 0.99995 ok 10 adjust f. 0.99990 ok 50 adjust f. 0.99950 ok 100 adjust f. 0.99900 ok 500 adjust f. 0.99502 ok 1000 adjust f. 0.99010 ok 3000 adjust f. 0.97087 ok 0 adjust f. 0.97087 ok \end{verbatim} Wert 1 liefert die geringste Dämpfung. Für den weichen Toneinsatz heißt das, dass der Ton lange braucht, um seinen Nennwert zu erreichen. Dagegen unterdrückt das Maximum 3000 (oder auch 0) den \textbf{mellow}-Effekt hinreichend und lässt den Toneinsatz „knallen“ (wie in \textit{Top One}). \texttt{:mellow-A*} bezeichnet ein Array-Element, wie es in der letzten Artikelfolge beschrieben worden ist: \begin{verbatim} create mellow-A*| 3 8 * allot ( 3 cells) : :mellow-A* ( cell# -- addr) 8 * mellow-A*| + ; \end{verbatim} Hierbei handelt es sich um die Speicherung der Vorgaben. Nun brauchen wir noch die Arrays für die beiden Steuerkurven aus \textbf{Abb. \ref{bild2}}. Diese müssen für jede der sechs Stimmen vorhanden sein, also für 18 Teiltonreihen: \begin{verbatim} create dflat-A*| 18 8 * allot ( 18 cells) : :dflat-A* ( cell# -- addr) 8 * dflat-A*| + ; create mel-A*| 18 8 * allot ( 18 cells) : :mel-A* ( cell# -- addr) 8 * mel-A*| + ; \end{verbatim} Immer wenn ein neuer Ton beginnt, werden (in der Routine \texttt{latch>gen-A}, hier nicht dargestellt) drei Array-Elemente mit \texttt{100e} geladen: \begin{verbatim} 3 0 DO 100e i :dflat-A* f! LOOP \end{verbatim} Dann geht’s weiter zur Berechnung der Faktorkurve: \begin{verbatim} : mellow-A ( --) r/m_ :dflat-A* f@ \ deflate 100 -> zero r/v_ :mellow-A* f@ f* \ damping value r/m_ :dflat-A* fdup f! \ update dflat 100e fswap f- \ upside down 100e f/ \ make it <1 r/m_ :mel-A* f! ; \ mellow factor \end{verbatim} Aufgerufen wird dieses Wort durch folgende bereits bekannte, nun aber aufgebohrte Routine: \begin{verbatim} \ <<< TONGENERATOR (A) >>> : waver-A ( --) empty-accu-A \ AKKU LEEREN 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 \ r/m \ 18 p-rows per manual \ p/m \ 288 partials per manual v/m_ r/v * r/v_ + to r/m_ v/m_ p/v * r/v_ p/r * + p/r_ + to p/m_ sinus-A \ KURVENBERECHNUNG fadings-A \ ABKLINGVORGÄNGE LOOP mellow-A \ WEICHER EINSATZ LOOP phase-incr-A \ PHASE INKREMENT LOOP timer-decr-A \ TIMER DEKREMENT store-accu-A ; \ AKKU UPDATE \end{verbatim} Man sieht: Kurvenberechnung und Abklingvorgänge werden für jeden der 288 Teiltöne des \textit{Manuals} aufgerufen, der weiche Toneinsatz dagegen nur für die 18 Teiltonreihen. Die Abklingvorgänge sind von der Erweiterung nicht betroffen, wohl aber die Kurvenberechnung. Darin taucht nun \texttt{:mel-A*} als Faktor auf und verkleinert somit die Amplitude während des Tonanfangs: \begin{verbatim} \ <<< KURVENBERECHNUNG (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 r/m_ :mel-A* f@ f* \ multiply by mellow 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 r/m_ :mel-A* f@ f* \ multiply by mellow accu-AR* f+! \ update accu (R) THEN THEN ; \end{verbatim} Mit diesen Ergänzungen sind bei passender Vorwahl sanfte Einschwinger zu hören: %Abbildung 3: harte und weiche Einschwingvorgänge \begin{figure} \begin{center} \includegraphics[width=1.0\textwidth]{2012-01/Teich-Bild3WE2} %\includegraphics{2012-01/Teich-Bild3WE2} \caption{\label{bild3}Harte und weiche Einschwingvorgänge} \end{center} \end{figure} Wird dann noch das Fading abgeschaltet, fehlt für ordentliche Orgeltöne nur noch das Anblasgeräusch. \section{Ziehen der Tonhöhe im Prinzip} Der zweite unerfüllte Wunsch beim Verfassen von \textit{Top One} war die \textbf{bender}-Funktion, das Ziehen der Tonhöhe. (\textit{Les Paul} ist ohne diesen Effekt gar nicht vorstellbar.) Ich werde mich für diesmal auf die schematischen Zeichnungen beschränken, denn ich möchte noch ein weiteres Thema unterbringen, damit das Gesamtbild der Wave Engine Gestalt annimmt. Die \textbf{bender}-Funktion ist aufwändiger als der weiche Toneinsatz. Ein kurzes Hochziehen zur Nenn-Tonhöhe reicht nicht, denn es ist auch so etwas wie „in die Knie gehen“ erwünscht, also ein Absenken und langsame Rückkehr zur Nenn-Tonhöhe. %Abbildung 4: „Magische Punkte“ à la Bézier \begin{figure} \begin{center} \includegraphics[width=1.0\textwidth]{2012-01/Teich-Bild4WE2} %\includegraphics{2012-01/Teich-Bild4WE2} \caption{\label{bild4}„Magische Punkte“ à la Bézier} \end{center} \end{figure} \textbf{Abb. \ref{bild4} }zeigt eine Bézier-Kurve (zur Konstruktion siehe Wikipedia) mit der gewünschten Form. Leider fehlt hier eine Zeit-Achse; die Kurve müsste zwischengespeichert werden, um sie für den zeitlichen Verlauf zu nutzen, was einen unnötigen Speicherbedarf zur Folge hätte. Es muss auch anders gehen. Die abklingende Kurve aus \textbf{Abb. \ref{bild2}} strebt nach der Nulllinie. Was wir hier brauchen, ist ein Streben nach vorgegebenen Werten (wie die Rechteckkurve in \textbf{Abb. \ref{bild5}}). %Abbildung 5: Kurve folgt (träge) den Vorgaben \begin{figure} \begin{center} \includegraphics[width=1.0\textwidth]{2012-01/Teich-Bild5WE2} %\includegraphics{2012-01/Teich-Bild5WE2} \caption{\label{bild5}Kurve folgt (träge) den Vorgaben} \end{center} \end{figure} \textbf{Abb. \ref{bild5}} zeigt die Funktion: Die Kurve bemüht sich mit der ihr eigenen Trägheit, den Vorgaben zu folgen. Mit geeigneteren Vorgaben verschwinden die Knicke, \textbf{Abb. \ref{bild6}}: %Abbildung 6: Die gewünschte Tonhöhen-Kurve \begin{figure} \begin{center} \includegraphics[width=1.0\textwidth]{2012-01/Teich-Bild6WE2} %\includegraphics{2012-01/Teich-Bild5WE2} \caption{\label{bild6}Die gewünschte Tonhöhen-Kurve} \end{center} \end{figure} Diese Kurve muss nur noch dem Sinus-Generator angedient werden. (Dazu später mehr.) \section{Draft-Betriebsart} Man könnte vielleicht einwenden, dass der viele Aufwand den \textit{Waver} unnötig träge werden lässt. Aber da er von vornherein nicht für Echtzeit konzipiert war, also keinen Klaviatur-Anschluss vorsieht, geht es allenfalls um das Warten aufs Ergebnis nach Programmstart. Hier kann durch eine Draft-Betriebsart viel Zeit eingespart werden. Wie schon aus der Wave-Datei ersichtlich, kann die Auflösung von 44 auf 11 Kiloframes pro Sekunde gedrosselt werden. Weiter kann auf die Berechnung höherer Teiltöne verzichtet werden sowie auf eine Reihe von Funktionen wie \textbf{mellow} und anderes. Es geht in aller Regel hauptsächlich darum, Fehler in der Abfolge der Noten aufzudecken. Rhythmisch heikle Stellen können zudem leicht für sich getestet werden, was nur wenig Zeit kostet. \end{multicols} \section{Register-Syntax und Erkennung} \begin{verbatim} \ === Partiturzeilen-Typen === \ fix{ ... } ` Vorgaben (fest) \ set{ ... } ` Einstellungen (veränderbar) \ regA{ ... } ` Registrierung Manual A (ebenso B, C, D) \ A{ ... } ` Notenzeile für Manual A (ebenso B, C, D) \ [< ... >] ` Titelzeile: wird in die Wave-Datei übernommen \ [[ ... ]] ` Kommentar (auch mehrzeilig), blendet Code aus \ [[[ ... ]]] ` Kommentar (kann [[...]] beinhalten) \ \ === Feste Vorgaben === \ fix{ dra=... } draft fix{ ste=... } stereo \ \ === Veränderbare Einstellungen === \ set{ man=... } manuals set{ tem=... } tempo \ set{ mic=... } micro set{ tun=... } tuning \ \ === Registrier-Anweisungen (alphabetisch) === \ regA{ acc=... } accent (2) regA{ pa1=... } partials1 (16) \ regA{ ang=... } angle (1) regA{ pa2=... } partials2 (16) \ regA{ arp=... } arpeggio (1) regA{ pa3=... } partials3 (16) \ regA{ bea=... } beat (1) regA{ pre=... } preset (1) \ regA{ ben=... } bender (12) regA{ rit=... } ritard (2) \ regA{ cre=... } cescendo (2) regA{ sta=... } staccato (2) \ regA{ dri=... } drift (2) regA{ sus=... } sustain (3) \ regA{ ech=... } echo (2) regA{ tre=... } tremolo (2) \ regA{ fad=... } fade (3) regA{ tra=... } transpo (1) \ regA{ fo1=... } formant1 (6) regA{ vib=... } vibrato (2) \ regA{ fo2=... } formant2 (6) regA{ vol=... } volume (1) \ regA{ mel=... } mellow (3) regA{ wah=... } wahwah (4) \ \ === Aufbau der Records === \ 0 pa1 --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- \ 16 pa2 --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- \ 32 pa3 --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- \ 48 fad --- --- sus --- --- mel --- --- sta --- tra acc --- cre --- \ 64 fo1 --- --- --- --- --- fo2 --- --- --- --- --- wah --- --- --- \ 80 ben --- --- --- --- --- --- --- --- --- --- --- ech --- dri --- \ 96 vib --- tre --- bea ang arp rit --- vol pre \ === Offsets für den Zugriff in den Records === 00 constant pa1 16 constant pa2 32 constant pa3 48 constant fad 51 constant sus 54 constant mel 57 constant sta 59 constant tra 60 constant acc 62 constant cre 64 constant fo1 70 constant fo2 76 constant wah 80 constant ben 92 constant ech 94 constant dri 96 constant vib 98 constant tre 100 constant bea 101 constant ang 102 constant arp 103 constant rit 105 constant vol 106 constant pre \ Beispiel: Aktion für Register-Kommando "echo=" (noch nicht implementiert) : ech: ( a u -- a' u' f) cr ." ### echo= ###" true ; \ === Zulässige Register-Kommandos === \ Hinweis: Hier reicht die voreingestellte Stacktiefe nicht ganz aus. : regsym$ ( regular) ( short) ['] pa1: c" partials=" ['] pa1: c" par=" ['] pa1: c" partials1=" ['] pa1: c" pa1=" ['] pa2: c" partials2=" ['] pa2: c" pa2=" ['] pa3: c" partials3=" ['] pa3: c" pa3=" ['] fad: c" fading=" ['] fad: c" fad=" ['] sus: c" sustain=" ['] sus: c" sus=" ['] mel: c" mellow=" ['] mel: c" mel=" ['] sta: c" staccato=" ['] sta: c" sta=" ['] tra: c" transpo=" ['] tra: c" tra=" ['] acc: c" accent=" ['] acc: c" acc=" ['] cre: c" crescendo=" ['] cre: c" cre=" ['] fo1: c" formants=" ['] fo1: c" for=" ['] fo1: c" formant1=" ['] fo1: c" fo1=" ['] fo2: c" formant2=" ['] fo2: c" fo2=" ['] wah: c" wahwah=" ['] wah: c" wah=" ['] ben: c" bender=" ['] ben: c" ben=" ['] ech: c" echo=" ['] ech: c" ech=" ['] dri: c" drift=" ['] dri: c" dri=" ['] vib: c" vibrato=" ['] vib: c" vib=" ['] tre: c" tremolo=" ['] tre: c" tre=" ['] bea: c" beat=" ['] bea: c" bea=" ['] ang: c" angle=" ['] ang: c" ang=" ['] arp: c" arpeggio=" ['] arp: c" arp=" ['] rit: c" ritard=" ['] rit: c" rit=" ['] vol: c" volume=" ['] vol: c" vol=" ['] pre: c" pre=" ['] pre: c" preset=" ['] reg_dummybar c" |" ['] rem: c" `" ( skip line) ['] reg_brace-right c" }" ; create regsym regsym$ , , , , , , , , , , , , , , , , , , , , ( 10) , , , , , , , , , , , , , , , , , , , , ( 20) , , , , , , , , , , , , , , , , , , , , ( 30) , , , , , , , , , , , , , , , , , , , , ( 40) , , , , , , , , , , , , , , , , , , , , ( 50) , , , , , , , , , , 0 , ( sentinel) \ Beispiel für den Zugriff: \ s" beat=123" regsym extract-token ( string count token flag) \ .s <4> 161889205 7 3054668 -1 ok \ "3054668 PERFORM" führt die gewünschte "beat"-Handlung aus. \ "161889205 7 type" gibt die restliche Zeichenkette ("123") aus. \ Wird kein Symbol gefunden (flag=0), wird die ganze Kette ausgegeben. \ === Partitur: Komplette Registrierung für Record 1 === \ Auswahl und Reihenfolge beliebig. \ Alle Null-Angaben durch "preset=0" überflüssig. regA{ *1 partials= (1:5000 2: 0 3: 0 4: 0 5: 0 6: 0 7: 0 8: 0 9: 0 10: 0 11: 0 12: 0 13: 0 14: 0 15: 0 16: 0), (1:3000 2:1000 3: 500 4: 300 5: 200 5: 100 7: 0 8: 0 9: 0 10: 0 11: 0 12: 0 13: 0 14: 0 15: 0 16: 0), (1: 0 2: 0 3:1200 4: 500 5: 0 6: 400 7: 0 8: 200 9: 0 10: 0 11: 0 12: 0 13: 0 14: 0 15: 0 16: 0); fading=8,20,40; ` 3 x Abklingen sustain=400,400,400; ` 3 x Nachklingen mellow=0,0,0; ` 3 x weicher Einsatz staccato=1000,100; ` Dämpfung, Vorlaufzeit transpo=0; ` Tonart-Transponierung accent=115,3; ` Lautstärke in Prozent, Töneanzahl crescendo=0,0; ` Zeit, Wert formant1=0,0,0,0,0,0; ` 2 x Frequenz, Amplitude, Breite formant2=0,0,0,0 0 0; ` 2 x Frequenz, Amplitude, Breite wahwah=0,0,0,0; ` 1|2, Affinität, Steilheit, Dots bender=0,0,0,0,0,0,0,0,0,0,0,0 ` Anfangswert, Affinität, xy-Paare echo=0,0; ` Zeit, Wert drift=0,0; ` Verstimmung des Manuals vibrato=0,0; ` Frequenzvibrato tremolo=0,0; ` Amplitudenvibrato beat=0; ` Phasenvibrato (links-reechts) angle=0; ` Stereo-Position arpeggio=0; ` Akkordbrechung (Dauer) ritard=0,0; ` langsamer werden (ritardando) volume=0; ` Lautstärke-Korrektur (0=neutral) preset=0; ` Voreinstellung (zuerst gelesen) } \ === Partitur: Registrierung der Teiltöne wie oben in Kurzschreibweise === regA{ *2 preset=0; par=(5000),(3000 1000 500 300 200 100),(3:1200 500 6:400 8:200); } \end{verbatim} \begin{multicols}{2} \section{Von der Partitur zum Generator} Der Weg von den Musiknoten bis zur Generierung der Töne ist nicht ganz trivial. Um für diesmal bis zum \textit{Generator} vorzudringen, gehe ich in der Beschreibung vom Ergebnis des Partitur-Interpreters aus, sonst verheddern wir uns in den Interpunktionen. Ich spreche jetzt nicht von den zahlreichen Einstellungen und Registrierungen, sondern nur von \textit{Tonhöhe}, \textit{Tondauer} und \textit{Habitus}. Das sind die drei Datentypen, die gepuffert werden müssen, weil sie im \textit{Generator} für vier \textit{Manuale} zugleich bereit stehen müssen, obgleich sie in der Partitur über mehrere Zeilen verteilt sind. Der dem Interpreter nachgeschaltete \textit{Collector} packt die Daten in das Format, in dem sie durch den Pufferspeicher transportiert werden. Eine Speicherebene enthält 8 Halbzellen. Ich habe ein (nicht standardisiertes) 16-bit-Format gewählt. Mit geringerem Speicherbedarf kann ich das kaum begründen. In meiner beruflichen „Karriere“ aber war er fast immer ein Thema, und das hängt mir halt noch nach. \section{Den \textit{FIFO}-Speicher laden} Mit den Befehlen\texttt{ habit!}, \texttt{durat!} und \texttt{pitch!} werden die Daten im \textit{Collector} gesammelt und mit \texttt{coll>fifo} zum \textit{FIFO} gesandt. \textit{FIFO} („first in – first out“) bedeutet, dass es um einen Ringspeicher geht, der ständig bereit ist, Daten anzunehmen und abzugeben, so lange es nicht zu overflow oder underflow kommt. \begin{verbatim} 0 value coll-N_ \ address of actual collector \ Load actual collector with habit : habit! ( n --) dup 31 > IF |HABIT!| THEN /error $001F and ( 5 bit) coll-N_ w@ $FFE0 and or coll-N_ w! ; \ Load actual collector with durat : durat! ( n --) coll-N_ 2 + w! ; \ Load actual collector with pitch : pitch! ( n --) bracketcount @ 0= \ [accord]? IF coll-N_ cell+ 12 0 fill \ no 1 voice# ! \ choose voc #1 ELSE voice# @ 5 > \ [accord ] IF |VOICE| THEN \ too many vocs THEN coll-N_ cell+ \ skip habit & durat voice# @ 1 2* + w! \ write pitch bracketcount @ \ [accord]? IF voice# incr \ next voice THEN ; \ Find counter address (given fifo address) : >cnt-in ( fifo-a -- in-a ) ; : >cnt-lock ( fifo-a -- lock-a) 2 + ; : >cnt-out ( fifo-a -- out-a ) 4 + ; \ Find plain address due to in-counter : >man-in ( fifo-a -- plain-a) dup >cnt-in @ no-of-plains mod ( a plain#) swap man-plain ; ( plain-a) \ Transfer data from coll-A to fifo-A : coll>fifo-A ( --) fifo-A >cnt-in w@ fifo-A >cnt-lock w@ - nr-of-plains >= IF |FIFO-OFL| THEN coll-A fifo-A >man-in 16 cmove \ transfer fifo-A >cnt-in incr ; \end{verbatim} \section{Adressierung des FIFO-Ringspeichers} Im Kopf jedes der vier (den \textit{Manualen} zugeordneten) Pufferspeicher sind drei Zähler untergebracht (\textbf{Abb. \ref{bild8}}), welche die Adressierung der aktuellen 16-bit-Pufferebenen übernehmen.\texttt{ cnt-in} dient als Zeiger auf die zuletzt beschriebene Ebene, \texttt{cnt-out }als Zeiger auf die nächst zu lesende Ebene, und \texttt{cnt-lock} verhindert, dass im Falle von Wiederholungsschleifen noch gebrauchte Daten überschrieben werden. Dabei ist zu beachten: \texttt{cnt-out} darf \texttt{cnt-in} nicht überholen (underflow), \texttt{cnt-in} darf \texttt{cnt-lock} nicht überholen (overflow); \texttt{cnt-lock} folgt normalerweise \texttt{cnt-out}, bleibt jedoch bei Schleifenbildung am Schleifenanfang hängen. Das mag soweit plausibel klingen, aber wer überholt in einem Ringpuffer wen? Wer ist vorn und wer ist hinten? Um hier klare Verhältnisse zu bekommen, laufen die Zähler linear von 0 bis maximal 65535 und werden erst „modulo Puffertiefe“ zu Zeigern. Gezählt werden „Töne“, und dazu dürfte die Zählerkapazität reichen. \textbf{Abb. \ref{bild7}} stellt beispielhaft einen Ringpuffer mit 10 Ebenen Tiefe dar. Gezählt wird zwar fortlaufend, aber adressiert werden nur die 10 Ebenen. Hier ist ersichtlich, dass \texttt{cnt-in} auf der Position von \texttt{cnt-lock} angekommen ist: Overflow! Man sieht: \texttt{cnt-in} darf \texttt{cnt-lock} nur maximal um die Puffertiefe vorauseilen, und obiger Satz muss heißen: \texttt{cnt-in} darf \texttt{cnt-lock} \textit{plus Puffertiefe} nicht überholen. \begin{center} \begin{verbatim} x-- "in" kleiner "lock" + Puffertiefe --x x---- "out" kleiner "in" ---x |lock |out |in |---|---|---|---|---|---|---|---|---|---|---|---| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | 1 | |---|---|---|---|---|---|---|---|---|---|---|---| x------- Puffertiefe: 10 Ebenen --------x \end{verbatim} \caption{\label{bild7}Overflow! „in“ hat „lock“ eingeholt.} \end{center} \section{Der Weg durch den Pufferspeicher} \textbf{Abb. \ref{bild8}} zeigt den Weg der Daten vom \textit{Collector} über den Pufferspeicher zum \textit{Loader} (siehe \textbf{Abb. \ref{bild1}}). Letzterer (für jedes \textit{Manual} separat vorhanden) versorgt den \textit{Generator} direkt, so dass dieser auf die Daten aller beteiligten \textit{Manuale} zugleich zugreifen kann. Dem \textit{Loader} ist ein \textit{Latch} (Zwischenpuffer) vorgeschaltet. Das ist erforderlich, damit während der aktuellen Generierung der Töne getestet werden kann, ob als nächstes ein Ton (Akkord) folgt oder eine Pause. Das entscheidet über die Art der Dämpfung (\textit{Staccato} oder \textit{Sustain}). Bei \textit{Staccato} wird der aktuelle Ton verkürzt. (\textbf{Abb. \ref{bild8}} zeigt nur das \textit{Latch}; der eigentliche \textit{Loader} ist von gleicher Art und nachgeschaltet zu denken.) \begin{center} \begin{verbatim} FIFO .----------. | cnt-in | | cnt-lock | Loader Collector | cnt-out | (Latch) .--------. |----------| .--------. habit! | habit | -> | habit | -> | habit | durat! | durat | -> | durat | -> | durat | pitch! | pitch1 | -> | pitch1 | -> | pitch1 | | pitch2 | -> | pitch2 | -> | pitch2 | | pitch3 | -> | pitch3 | -> | pitch3 | | pitch4 | -> | pitch4 | -> | pitch4 | | pitch5 | -> | pitch5 | -> | pitch5 | | pitch6 | -> | pitch6 | -> | pitch6 | '--------' |----------| '--------' width=16bit | habit | width=16bit | durat | | pitch1 | \end{verbatim} \caption{\label{bild8}Daten passieren den \textit{FIFO}-Speicher} \end{center} Aus \textbf{Abb. \ref{bild9}} ist die Anordnung der transportierten Daten in acht 16-bit-Halbzellen ersichtlich. Die sechs Werte für die \textit{Tonhöhe} (pitch1 bis pitch6) bewegen sich im Bereich 1 bis 478 (plus 0 für Pause und -1 für Tonverlängerung). Die \textit{Tondauer} (durat) einer Viertelnote hat den Wert 720, der Maximalwert von 64800 entspricht 90 Viertelnoten oder 22,5 Vierviertel- oder 30 Dreivierteltakten. \begin{verbatim} byte 1 byte 0 (loop count) (end) (habit) |-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ habit: | 2...255 | >>> | 0...31 | |-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ |-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ durat: | 1...64800 | |-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ |-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ pitch1: |s| -1...478 | |-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ : : : : : : : : : : : : : : : : |-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ pitch6: |s| -1...478 | |-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ \end{verbatim} \caption{\label{bild9}Eine \textit{{FIFO}-Ebene im Detail}} Die 5 Bit rechts oben sind dem \textit{Habitus} vorbehalten, also den maximal 32 \textit{Habitus}-Marken, die vordefinierte Registrierungen aus den Records abrufen und in Gang setzen. \section{Wiederholungsschleifen} Kurze Wiederholungen werden dem \textit{Generator} aufgebürdet, um den Pufferspeicher zu entlasten, da dieser ganze Partiturzeilen fassen muss. Zeichen für lange Wiederholungen wie in der üblichen Notenschrift gibt es hier nicht, denn ganze Partiturzeilen lassen sich per Editor bequem vervielfältigen. Der Schleifenzähler (dessen Wert in der habit-Zelle mitgeschleppt wird) zählt die Anzahl der Wiederholungen (maximal 255) zurück bis Null. Die Schleifen sind 7-fach schachtelbar. Das erfordert einen \textit{Chevron}-Stack, und damit ist klar, dass innere Schleifen keine äußeren überdauern. Allerdings können bis zu 7 Schleifen zugleich beendet werden – dafür sorgt das \verb!>>>!-Feld (\textbf{Abb. \ref{bild9}}), das die Anzahl der zu schließenden Schleifen angibt. Der Schleifenanfang wird durch \texttt{cnt-lock} bestimmt; das Schleifenende sitzt irgendwo zwischen \texttt{cnt-lock} und \texttt{cnt-in}. Der Zähler \texttt{cnt-out} bezeichnet die Pufferzelle, aus der gerade gelesen wird. Bei Wiederholungen wird er auf die Position von \texttt{cnt-lock} zurückgesetzt. Wenn bei \texttt{cnt-lock} ein älteres Schleifenende anzutreffen wäre, gäbe es ein Problem. Damit dies nicht geschieht, müssen schließende \textit{Chevrons} dem vorher notierten Ton mitgegeben werden. Deshalb werden „Töne“ aus dem \textit{Collector} erst mit einem folgenden Partiturzeichen – z. B. auch dem Zeilenende (\}), jedoch mit Ausnahme schließender \textit{Chevrons} (\verb!> >> >>>! etc.) – weitertransportiert. \section{Der Loader und die gap-Marke} Noch sind wir nicht beim \textit{Generator} angekommen, darum noch dies: Der \textit{Loader} übernimmt Daten aus dem \textit{FIFO}-Puffer und stellt sie dem \textit{Generator} zur Verfügung. Im \textit{Generator} läuft für die Dauer des aktuellen Tons ein Countdown-Zähler (Timer). Bei Null wird der nächste Ton angefordert. Wegen der \textit{Staccato}-Funktion werden die Daten in einem Auffangregister (\textit{Latch}) zwischengespeichert, ehe sie zum \textit{Generator} gelangen. Im \textit{Latch} kann erkannt werden, ob ein nächster Ton oder eine Pause ansteht. Im Fall eines folgenden Tons wird der aktuelle Ton vorzeitig abgedämpft. Die gap-Marke des \textit{Timers} gibt die Größe des Vorlaufs an. Erreicht der \textit{Timer} eines der beteiligten Kanäle die gap-Marke, so werden neue Daten aus dem \textit{FIFO}-Puffer ins \textit{Latch} übertragen und das gap-Flag gesetzt. Folgt (im \textit{Latch}) eine Pause, so dauert der aktuelle Ton bis zum Zählerstand 0 an, worauf der Nachklang (\textit{Sustain}) einsetzt. Folgt (im \textit{Latch}) ein Ton, so werden bei Zählerstand 0 Daten vom \textit{Latch} in den \textit{Generator} übertragen. Das gap-Flag wird rückgesetzt. Das gap-Flag ist erfordenlich, damit die \textit{FIFO}-Daten nur einmal (auch noch nach Unterschreiten der gap-Marke) ausgelesen werden. Am Anfang wird das gap-Flag gesetzt (weil gap-Marker unterschritten) und Daten aus dem \textit{FIFO} ins \textit{Latch} übertragen. Von dort gelangen sie sofort (weil Zählerstand Null) in den \textit{Generator} und der Zähler wird neu gesetzt. Mit Erreichen der gap-Marke gelangen nachfolgende Daten ins \textit{Latch}, etc. Zunächst wird ein\textit{ okay-Flag} = true gesetzt und die beteiligten Kanäle aufgerufen. Wenn einer der Kanäle die gap-Marke erreicht und keinen Nachschub im \textit{FIFO}-Puffer vorfindet, wird das \textit{okay-Flag} = false gesetzt und dadurch der \textit{Generator} veranlasst, die Kontrolle an den Partitur-Scanner zurückzugeben. \begin{verbatim} : loader-A ( f – f') requ-A_ \ request? IF false to requ-A_ \ request off false to gap-A_ \ gap-Flag off THEN gap-A_ not \ gap-Flag off? IF timer-A @ gaplim-A_ <= \ sentinel? IF true to gap-A_ \ begin gap fifo>latch-A and \ update flag THEN THEN timer-A @ 0= \ timer=0? IF false to gap-A_ \ end gap latch>gen-A and \ ld generator THEN ; \end{verbatim} \section{Symbole extrahieren} Und ich sollte noch \texttt{extract-token} zeigen, das im Abschnitt Register-Syntax Verwendung fand: \begin{verbatim} \ Extract a symbol from a string : extract-token ( $a u tab-a -- $a' u' token f) >r _leading r> BEGIN >r over r@ @ count ( a u a $a $u) tuck str= not ( a u f) 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) rot over + -rot - ( a" u") r> cell+ true ; ( a" u" a' t) \ Beispiel für den Zugriff: : do-A ." " ; : do-B ." " ; : do-C ." " ; : tabel$ ['] do-A c" A#" ['] do-B c" B#" ['] do-C c" C#" ; create tabel tabel$ , , , , , , 0 , : do-it ( a u --) tabel extract-token IF PERFORM ELSE drop ." ???" THEN ." | " type ; \ s" A#" do-it => | ok \ s" B#C#" do-it => | C# ok \ s" B# C# " do-it => | C# ok \ s" A#B#C#" do-it => | B#C# ok \ s" X#B#C#" do-it => ??? | X#B#C# ok \end{verbatim} Und damit genug für diesmal. Ich hoffe, dass das Gesamtkonzept ein bisschen deutlicher geworden ist. Der Umfang des \textit{Waver}-Programms lässt nicht zu, in alle Details (der funktionierenden Teile) zu gehen. Aber bislang hat die Beschreibung dem Projekt recht gut getan, denn es geht deutlich flotter voran. Das letzte Heft (2011/4) füllte sich schneller als erwartet, so dass ich leider zu spät kam. Das soll mir nicht wieder passieren. \hspace*{\fill} \begin{center} Beachten Sie bitte den Dateibereich der Website der Forth-Gesellschaft unter\\ http://www.forth-ev.de/filemgmt/viewcat.php?cid=54\\ sowie die Site des Autors unter\\ http://www.stocket.de/WE\\ \end{center} \end{multicols} \end{document}