% Content-encoding: UTF-8 \documentclass[ngerman]{article} \usepackage[utf8]{inputenc} \usepackage{multicol,babel} \usepackage{xspace} \setcounter{secnumdepth}{0} \setcounter{tocdepth}{0} \renewcommand{\reftextbefore}{auf der vor\-herigen Seite} \renewcommand{\reftextfacebefore}{auf der gegen\-über\-lie\-genden Seite} \renewcommand{\reftextafter}{auf der näch\-sten Seite} \renewcommand{\reftextfaceafter}{auf der gegen\-über\-lie\-genden Seite} \renewcommand{\reftextfaraway}[1]{auf Seite~\pageref{#1}} %\newcommand{\code}[1]{\texttt{#1}} \newcommand{\jackd}{\emph{jackd}\xspace} \newcommand{\libjack}{\emph{libjack}\xspace} \begin{document} \title{fJACK – Echtzeit-Audio in Forth} %\ifx\shorttitle\undefined\else %\shorttitle{} %\fi \author{Hanno Schwalm} % – \mail{hanno@schwalm-bremen.de} \maketitle \label{fjack} Die Zeiten, in denen ein dezent--nervendes Piepen aus dem Inneren des PCs als \emph{Sound--Erfolg} gelten konnte, sind für immer vorbei. Heute haben wir es mit einer unüberschaubaren Vielfalt von Hardware und mindestens 3 Betriebssystemen zu tun und ein hochqualitatives Audioerlebnis wird von allen Benutzern erwartet. \begin{multicols}{2} Aber was hat das denn mit Forth zu tun? Ich habe doch keine HiFi--Zeitung sondern ein Magazin für Forth--Anwender in der Hand --- ein kleiner Blick auf den Umschlag wird dies bestätigen. Im Frühjahr 2009 habe ich auf der Tagung der %deutschen Forth--Gesellschaft einen Vortrag über ein Echtzeit--Audio--System in Forth – iJACK – gehalten. Inzwischen ist iJACK gründlich renoviert und auch erwachsen geworden, es nennt sich jetzt fJACK und läuft unter allen aktuellen iFORTH--Systemen sowie VFX Forth von MPE. Ulli Hoffmann fragte mich damals, ob es denn spezielle Gründe für die Verwendung von Forth gab --- jetzt würde ich sagen: Ich kenne kein anderes System zur Audio--Echtzeit--Verarbeitung, mit dem sich interaktiv besser spielen lässt. Ich höre mit Forth Musik von der Konserve, vom Radio oder auch aus dem Live--Mischpult, schreibe an den Audio--processing Plugins und kann sie unmittelbar testen und wieder aus dem System entfernen. Also FORTH pur! \section{Was solls denn können?} Schon auf meinem guten alten Acorn--Rechner habe ich mich mit Audio--Programmierung beschäftigt, damals zu großen Teilen in Assembler und als Interruptroutine, die Entwicklungsumgebung war natürlich Forthmacs. Heute ist das komplizierter und gleichzeitig einfacher: Interrupts sind in allen Betriebssystemen für Anwendungen quasi tabu und heutige Compiler sollten Assemblercode eigentlich überflüssig machen – und das ist in der Intel--Welt auch gut so. Vor dem iForth--3.0--Release hatten Marcel (Hendrix) und ich länger über unsere Unzufriedenheit mit dem Audiosystem diskutiert. Es gab für 3 Betriebssysteme (Linux, Windows und Mac OS X) jeweils 32-- und 64--Bit--spezifischen Code, zum Teil in Forth, zum Teil in C geschrieben. Die Wartung war gelinde gesagt schwierig. Außerdem waren wir mit der Leistung des vorhandenen Systems unzufrieden: eine viel zu hohe CPU--Last, kein wirklicher Duplexbetrieb, schlechtes Zeitverhalten, keine Schnittstelle zu anderen Programmen \ldots Also eine unhaltbare Situation. Ein völlig neu entwickeltes Audio--System war erforderlich. Als Vorgaben hatten wir festgelegt: \begin{enumerate} \item Alle iFORTH--Versionen sollten unterstützt werden können (Also 3*2) \item Niedrige CPU--Last und damit Performance wie andere erstklassige Systeme, Echtzeitfähigkeit und niedrige Input--Output--Latenz $\rightarrow$ Also kein Polling in threads sondern ein interruptgesteuertes System war gefordert \item \emph{Pflege} der Hardwarebasis durch andere fleißige Menschen :-) \item Hohe Stabilität und damit auch für Bühne, Musikstudio und Langzeitbetrieb nutzbar \item Gut definierte und gut dokumentierte Schnittstelle $\rightarrow$ weniger Arbeit für mich \item .wav--Dateien/Daten in allen Sampleraten müssen unabhängig von der Hardware abgespielt und aufgezeichnet werden können, und das ohne Knistern oder Aussetzer. \item Weiterleitung von Audiodaten zwischen verschiedenen Programmen muss vollständig unterstützt sein \end{enumerate} Ganz schön happig, oder? Da auch ich nicht gerne Räder neu erfinde, habe ich nach vorhandenen Projekten gesucht und durch den Vorgaben--Filter geschickt. Unten fiel eigentlich nur ein Projekt raus: das JACK--Audiosystem mit dem JACK--Sound--Demon \jackd und der Bibliothek \libjack. Es ist für alle wichtigen Betriebssysteme verfügbar, es wird aktiv dran gearbeitet und es gibt eine relativ große und aktive Entwicklergruppe. Ich habe dann die \libjack--Dokumentation durchgelesen und mich in den einschlägigen Internet--Foren umgehört, was Benutzer anderer Programme dazu sagen. Zumindest für Linux wurde klar: Es gibt für anspruchsvolle Umgebungen keine wirkliche Alternative, damit war die Entscheidung gefallen. Eine Forth--Audio--Umgebung sieht in etwa so aus: \begin{itemize} \item Ein JACK--System (\url{http://jackaudio.org}) mit dem Sound--Demon \jackd und der Bibliothek \libjack ist installiert. Bei korrekter Installation wird der Demon von anderen Programmen automatisch gestartet, ansonsten gibt es auch graphische Frontends wie \emph{qjackctl}\/. \item Die komplette Audiohardware wird unter allen Betriebssystemen vom \jackd kontrolliert und alle Audioanwendungen benutzen die \libjack. Audiodaten liegen intern und in den Schnittstellen immer im gleichen Datenformat vor (sfloats). \item Forth hat eine Schnittstelle zur \libjack, das Forth--System fJACK ist aus der Sicht des \jackd ein ganz normaler Client inclusive Callbacks und Datenstrukturen. Forth kann Audiodaten des \jackd aufnehmen, verarbeiten oder liefern. \item Alle Funktionen des fJACK werden ganz normal in Forth programmiert und können von allen Forth--Programmen genutzt werden. \end{itemize} Im Folgenden werde ich zeigen, wie fJACK implementiert wurde – ohne dabei mit endlosen Listings Papier und Geduld übermäßig zu strapazieren. In dem nächsten Artikel werde ich den Signalfluss der Audiodaten und die fJACK--Forth--Bibliothek vorstellen und zeigen, wie eine ganz einfache Audioanwendung aussehen könnte. Ein abschließender Artikel stellt dann fJACK--Plugins im Detail vor, wir werden dann gemeinsam ein Echtzeit--Echo/Hall--System entwickeln und benutzen. IForth 4.0 gibt’s jetzt auch in einer Evaluation--Version und diese lege ich hiermit allen Lesern ausdrücklich ans Herz. Zwischen Installation und erstem Audioerlebnis werden kaum 10 Minuten verstreichen! Für diejenigen, die Anleitungen nicht lesen: iForth starten und auf der Kommandozeile \texttt{in fjack} eingeben. Ein kleiner Hinweis an dieser Stelle: Wer ein technisches Programmer's Reference Manual sucht, wird unter \emph{includes/fjack/fjack.html} fündig werden. \section{Das Forth--System} Es kommt nur ein stabiles ANS--Forth--System mit hochoptimierendem Compiler für Integer und Floating--Point--Daten in Frage. Relativ komplexer Code mit massenweise Berechnungen in Floating Point wird z.\,B.\ 50/sec in einem Callback ausgeführt – daraus ergibt sich der Bedarf nach einem möglichst guten Compiler. iForth und VFX sind für diesen Anwendungsfall die mit Abstand schnellsten Systeme und werden beide unterstützt. Für andere Forth--Systeme wäre mit schweren Performance--Einbußen zu rechnen. In den letzten fJACK--Versionen wird außerdem unter iForth auf entsprechenden CPUs auf optimierte 64--Bit--SSE2--Filter--Routinen umgeschaltet, die Leistung verdoppelt sich erwartungsgemäß nahezu. Zusätzlich sind eine Reihe von Forth--Erweiterungen essentiell: \begin{enumerate} \item Einbinden von Funktionen aus beliebigen dynamischen Bibliotheken \item Spezielle Datentypen mit \texttt{TO} \texttt{+TO} müssen machbar sein \item Eine Schnittstelle zur Ausführung von Forth als Callback aus externen Programmen \item Threading--Unterstützung \end{enumerate} \section{Funktionen in dynamischen Bibliotheken} Die \libjack Funktionen werden über die aus VFX bekannte Syntax eingebunden, also \begin{footnotesize} \begin{verbatim} Library: libjack.so.0 Extern: int "C" jack_client_close ( jack_client_t * client); Extern: float "C" jack_cpu_load ( jack_client_t * client); \end{verbatim} \end{footnotesize} Beide Funktionen erwarten die ID des Clients auf dem Stack, \verb|jack_client_close| beendet den Client und \verb|jack_cpu_load| liefert die relative Last. Auf diese Weise werden alle benötigten \libjack Funktionen verfügbar gemacht\footnote{Selbstverständlich unterstützten \texttt{Extern:} definierte Worte auch \emph{turnkey Anwendungen}\/.}. Der Aufwand und die nötige Cleverness für einen solchen Funktionsaufruf ist erheblich: \begin{itemize} \item die Forth--internen Register der CPU werden gesichert \item die Parameter werden typ--abhängig von den Forth--Stacks in die Parameter des C--Interface \emph{übersetzt}. Dazu werden primär diverse CPU--Register und sekundär der Prozessor--Stack verwendet, die Zuordnung und Sortierung der Register ist jedoch abhängig vom OS und 32/64--Bit--Version. Da wird viel Historie herumgeschleppt --- so ein Interface korrekt zu implementieren, ist wirklich aufwendig. \item Der Prozessor--Stack muss bei manchen Betriebssystemen zur Laufzeit \emph{aligned} werden \item Die jeweilige C--Funktion muss in den Bibliotheken gesucht, gefunden und zuletzt aufgerufen werden. Falls die ganze Bibliothek oder auch nur die Funktion nicht vorhanden ist, sollte das System nicht im Nirwana enden. \item Die Forth--internen Register werden aus den gesicherten Werten wiederhergestellt. \item Zuletzt wird das Ergebnis je nach Typ aus dem wiederum OS--spezifischen Register auf den jeweiligen Forth--Stack gelegt. \item Und das Ganze muss auch noch reentrant--fähig sein \end{itemize} Es hat für iForth eine ganze Weile gedauert, bis das Ganze rund und stabil lief. Jetzt stellt das \emph{dynlibs}--Modul die \texttt{Extern:}--Syntax zur Verfügung, \emph{fjack/jacklib.frt} enthält alle benötigten Funktionen. \section{Spezielle Datentypen} Im fJACK--Client werden eine Reihe von speziellen Datentypen und Strukturen benutzt, dieses führt wie üblich zur besseren Lesbarkeit, kompakterem Code und weniger Bugs. Wir alle wissen inzwischen durch A.\ Ertls wiederholte Vorträge und unzählige Postings in c.l.f: \emph{State smart is evil!} Auch ich bin schon in vorhandene Fallen getappt und habe stundenlang gegrübelt, warum interaktiv \emph{alles gut} war und im fertigen Programm nicht mehr. Offenbar muss jeder mal durch diese Lektion :-) Auf jeden Fall findet sich in \emph{fjack/types.frt} ein Beispiel, wie um diese Hürde herum gearbeitet werden muss, da der Standard da noch zu wenig präzise ist. \section{Die Callbacks} Callbacks sind sozusagen eine Umkehrung der Funktionsaufrufe; aus beliebigen Funktionen heraus werden die Parameter in die Forth--Umgebung übersetzt, ein Forth--Wort wird aufgerufen und das Ergebnis wieder zurückgegeben. Dummerweise kann dazu aber nicht das normale Forth--System genutzt werden. Es muss eine neue Forth--Umgebung mit zumindest eigener \texttt{USER}--Area und privaten Stacks verwendet werden. Die Callbacks aus dem \jackd werden letztendlich interrupt--kontrolliert gestartet und müssen innerhalb kürzestmöglicher Zeit – also im µsec--Bereich – die Daten zurückliefern, da ja die Audio--Hardware darauf wartet. Also: langsame Speicherverwaltung, Dateizugriff, Semaphore, Bildschirmzugriff und ähnliche Aktionen mit nicht vorhersehbarer Laufzeit sind für \jackd--Callbacks absolut tabu! Glücklicherweise sind diese Details für den Benutzer verborgen – ob eine Applikation stabil auf einem Rechner läuft, kann an den XRUNs des \jackd abgelesen werden, aus der schon oben erwähnten Last geschlossen werden oder der Klang fängt heftig an zu stottern. Ein Callback wird folgendermaßen definiert, hier das Beispiel der XRUN--Zählerei: \begin{small} \begin{verbatim} : XT-COUNT-XRUNS ( parameter -- 0 ) DROP 1 +TO JACK-XRUNS FALSE ; ' XT-COUNT-XRUNS CB( _int )CB-int COUNT-XRUNS fJACK-ID COUNT-XRUNS 0 jack_set_xrun_callback ABORT" Can't set xruns callback" \end{verbatim} \end{small} \verb|( xt ) CB( … )CB-int| definiert letztendlich die Callback--Adresse. \verb|_int| sagt, dass zur Laufzeit ein Integer als Parameter übergeben wird, \verb|)CB-int| legt den Rückgabewert des Callback--Aufrufs als einen Integer fest. Die Adresse sowie die ID bekommt dann zuletzt \libjack mitgeteilt. \section{Threading} nicht im forthigen (in)direct--threaded--code Sinn sondern wie in multithreading wird im Forth--Standard nicht ausreichend gewürdigt. Fast alle modernen Prozessoren enthalten seit ein paar Jahren mindestens zwei Kerne und diese Tendenz wird sicher weiter zunehmen. Die Taktrate des Prozessors bleibt quasi konstant und die Zahl der Kerne nimmt zu. Im Standard gibt es für den ganzen Bereich des \emph{parallel processing}\/s quasi keine Unterstützung, die aktuellen großen PC--Forth--Systeme lassen einen da aber alle nicht ganz allein im Regen stehen. Die relativ umfangreiche Unterstützung im iFORTH--threads--Modul wird in diesem Projekt allerdings gar nicht benutzt, lediglich das Starten eines Forth--Wortes als thread wird vorausgesetzt. \section{Der fJACK--Client} wird schon beim Laden des Moduls per \verb|include fjack.frt| oder \verb|in fjack| gestartet und ist danach permanent im Hintergrund aktiv. Die Audiodaten werden permanent vom \jackd an alle verbundenen Programme per Callback weitergereicht, darin werden dazu Zeiger auf interne Speicherbereiche erfragt. Pro Callback werden dann z.\,B.\ 512 Audiosamples \emph{präsentiert}. Diese liegen jeweils für einen Kanal als einzelne 32--Bit--sfloats vor. Jeder Client muss in einem Callback \begin{enumerate} \item Für jeden verbundenen Kanal alle einzelnen Audiosamples einlesen und eventuell bearbeiten \item die verarbeiteten Audiodaten in Ausgabepuffer schreiben. \end{enumerate} Der Callback ist in Wirklichkeit noch deutlich komplexer, da auch andere Daten parallel verarbeitet werden. Es können beliebig große Blöcke von Audiodaten in diversen .wav--Formaten in beliebiger Geschwindigkeit in den Audiodatenstrom eingemischt oder auch aufgezeichnet werden. Dies geschieht so präzise, dass nicht ein einzelnes Sample verloren geht. Einfache Funktionen zum Aufzeichnen oder Abspielen kompletter Wav--Dateien sind vorhanden z.\,B.: \begin{small} \begin{verbatim} .WAV ( c-addr u -- ) RECORD-WAV ( time name len -- ) .SOUND ( samplerate channels 8/16/32/-32 c-addr u -- ) \end{verbatim} \end{small} \verb|.WAV| spielt eine Standard--WAV--Datei ab und sieht im Header der Datei nach, wieviel Kanäle, welche Samplerate usw.\ benutzt werden sollen, \texttt{RECORD-WAV} zeichnet den laufenden Audiodatenstrom in Stereo und 16--Bit--Format auf. \texttt{.SOUND} bekommt dagegen die Samplerate, die Anzahl der Kanäle, den Datentyp und den Filenamen mitgeteilt. \section{Eine kleine Demo} Unter Linux--iForth lässt sich eine kleine Demo starten, es werden eine Reihe von kurzen Audiodateien in verschiedenen Formaten und Abtastraten abgespielt und in einer graphischen Umgebung gezeigt. Doppelklicks in der Filterleiste (knapp oberhalb des Analysefensters) setzen Filter – links setzt den Hochpass, re einen Tiefpass und mid einen Notch. Im Analysefenster können die Skalen per \emph{Drag} verschoben werden. Abbildung \vref{fjack:client} zeigt einen Screen--Shot. Einfach ausprobieren \ldots \end{multicols} \hfill{Fortsetzung folgt\ldots} %\vfill %\begin{figure} %\begin{center} %\includegraphics[width=0.8\textwidth]{2009-04/fJACK-Client}\\ %\caption{\label{fjack:client}Ein Blick auf den fJACK--Client} %\end{center} %\end{figure} %\vfill \end{document}