Copyright © 1999-2001 Stefan Westerfeld & Jeff Tranter
Es ist erlaubt, dieses Dokument zu kopieren, zu vertreiben und/oder zu ändern gemäß den Bedingungen der GNU Free Documentation Licence, Version 1.1 oder irgend einer späteren Version, wie sie von der Free Software Foundation veröffentlicht wurde; ohne die invarianten Abschnitte, ohne Texte auf der vorderen Umschlagseite, und ohne Texte auf der hinteren Umschlagseite. Eine Kopie der Lizenz findet sich im Abschnitt "GNU Free Documentation License".
Dieses Handbuch beschreibt aRts, den analogen Echtzeit-Synthesizer.
Inhaltsverzeichnis
Der analoge Echtzeit-Synthesizer (aRts) ist ein modulares System zur Synthetisierung von Klängen und Musik auf einem Computer. Mit Hilfe von kleinen Bausteinen namens Modulen kann der Benutzer leicht komplexe Audio-Werkzeuge zusammenstellen. Module stellen typische Funktionen wie Hüllkurvengeneratoren, Filter, Audioeffekte, Mixer und Wiedergabe digitaler Klänge in verschiedenen Dateiformaten zur Verfügung.
Der artsd Soundserver mixt Klänge aus unterschiedlichen Quellen in Echtzeit. Damit können sich verschiedene Anwendungen transparent die zur Verfügung stehende Hardware teilen.
Durch Verwendung von MCOP, dem Multimedia-Kommunikationsprotokoll, können Multimediaprogramme netzwerktransparent (aus Sicherheitsgründen mit Authentifizierung) und plattformübergreifend durch die Verwendung von Schnittstellen programmiert werden, die sprachunabhängig mit Hilfe von IDL festgelegt werden können. Es gibt auch eine Unterstützung für sonstige und älter Anwendungen, die nicht aRts verwenden. aRts stellt einen Kernbestandteil der KDE 2-Umgebung dar und ist die Basis der KDE-Multimedia-Architektur. aRts wird zukünftig noch weitere Medien unterstützen, unter anderem Video. Wie auch KDE, kann aRts auf einigen Betriebssystemen, einschließlich Linux® und BSD-Varianten eingesetzt werden. aRts kann auch unabhängig von KDE verwendet werden.
Dieses Dokument ist eine umfassende Dokumentation für Benutzer unterschiedlicher Vorkenntnisse für aRts. Falls Sie ein gelegentlicher Nutzer von Multimediaprogrammen sind, sollten Sie einen anderen Weg durch dieses Dokument wählen als ein Entwickler von aRts-Anwendungen.
Sie sollten zuerst den Abschnitt Herunterladen und Kompilieren von aRts lesen, falls Sie aRts installieren möchten. Sollten Sie bereits ein funktionierendes System haben (häufig als Teil ihrer Distribution) können Sie diesen Abschnitt überspringen.
Als nächstes sollten Sie die Abschnitte unter aRts-Werkzeuge, besonders artsd, artscontrol, artsshell und artsdsp lesen. Diese Abschnitte helfen Ihnen bei der optimalen Nutzung von aRts.
Wenn Sie weitergehende Arbeiten mit aRts planen, lesen Sie das Kapitel über aRts-builder und überfliegen Sie die Schritt-für-Schritt-Einführung. Das sollte Ihnen einen Eindruck der großen Möglichkeiten von aRts und den ohne Programmierung nutzbaren Modulen geben.
Wenn Sie mehr über die internen Details von aRts wissen möchten, weil Sie Multimedia-Anwendungen programmieren oder aRts erweitern wollen, lesen Sie einiges oder alles aus dem Kapitel aRts Details. Das sollte Ihnen ein Verständnis der Konzepte von aRts ermöglichen, die für eine erfolgreiche Programmentwicklung notwendig sind.
Wenn Sie besonders an den MIDI-Fähigkeiten von aRts interessiert sind, sollten Sie das Kapitel MIDI lesen.
Falls Sie planen aRts-kompatible Multimedia-Anwendungen zu programmieren, lesen Sie das Kapitel aRts Anwendungsprogramm-Schnittstellen, das die verschiedenen APIsdetailiert beschreibt.
Wenn Sie aRts durch neue Module erweitern wollen, lesen Sie das Kapitel aRts-Module.
Falls Sie eine Anwendung ändern wollen, sodass Sie mit aRts zusammenarbeitet, lesen Sie das Kapiteln Anwendungen für aRts portieren.
Wie Sie Beiträge zu aRts leisten können, erfahren Sie im Kapitel Zu aRts beitragen. Die zukünftige Planung für aRts erfahren Sie im Kapitel Zukünftige Arbeiten. Dort finden Sie auch Verknüpfungen zu weiteren Informationen.
Wir haben das Hanbuch mit einigem Zusatzmaterial abgerundet: Das sind im Einzelnen Antworten auf häufig gestellte Fragen, eine Liste der Mitarbeiter, detailierte Informationen zu Copyright und Lizenz von aRts und einige Hintergrundinformationen zu digitalem Audio und MIDI. Weiterhin ist ein Glossar der verwendeten Begriffe enthalten.
Dieses Handbuch ist noch keineswegs vollständig. Falls Sie Teile hinzufügen möchten, sind Sie sehr willkommen. Kontaktieren Sie in diesem Fall zuerstt Jeff Tranter (tranter AT kde.org)
oder Stefan Westerfeld (stefan AT space.twc.de)
, um Dopplungen zu vermeiden.
Spät im Jahre 1997 begann Stefan Westerfeld mit der Arbeit an einem modularen Echtzeit-System zur Klang-Synthese. Das Programm war ursprünglich für einen Power-PC unter AIX® bestimmt. Die erste Version war einfach, unterstützte aber ein vollständiges Flusssystem, das solche Aufgaben wie die Wiedergabe einer MP3-Datei oder das Durchleiten eines Audio-Datenstroms durch Effektmodule durchführen konnte.
Der nächste Schritt war die Implementation eines GUI, so dass die Modules grafisch bearbeitet werden konnten. Stefan hatte einige Erfahrung mit KDE und diese Umgebung wurde daher als GUI-System (in dem Bewußtsein, das eine GNOME/Gtk+-Version ebenfalls erforderlich sein könnte) benutzt. Später führte das dazu, das Linux® die Hauptentwicklungsplattform wurde. Vom ursprünglichen Namen ksynth wurde das Projekt in aRts umbenannt und die Entwicklungsgeschwindigkeit nahm zu. Das Projekt war zu dieser Zeit mit einem CORBA-basierten Protokoll, dutzenden von Modulen, einem grafischen Moduleditor, C- und C++-API-Dokumentation, Hilfsprogrammen und einer Mailingsliste und Internetseite mit einer kleinen Entwicklergruppe bereits recht vollständig. Diese ganze Entwicklung fand in der sehr kurzen Zeit von nur einem Jahr statt.
Bei der Planung von KDE 2.0 wurde es dem KDE-Team klar, das KDE eine bessere Klanginfrastruktur und Unterstützung für Klänge und andere Stream-Medien benötigte. Man entschied sich, aRts für diese Zwecke anzupassen, da bereits eine fundierte Architektur vorhanden war. Es wurde große Mühe in eine neue Version von aRts investiert, die umfangreichtse Entwicklung war die Ersetzung des CORBA-Protokolls durch ein völlig neues Subsystem mit dem Namen MCOP, das für Multimedia optimiert ist. Die Version 0.4 von aRts wurde Bestandteil der Version 2.0 von KDE.
Die Arbeit an aRts geht weiter. Die Arbeitsgeschwindigkeit wird verbessert und neue Funktionen werden hinzugefügt. Obwohl aRts ein Kernbestandteil von KDE ist, kann es auch ohne KDE verwendet werden. Es findet auch Verwendung in Anwendungen, die über das traditionelle Multimedia hinausgehen. Das Projekt hat auch beim GNOME-Team einiges Interesse hervorgerufen, so dass die Möglichkeit besteht, das es eines Tages die Standard-Multimedia-Architektur für UNIX®-Arbeitsplatzrechner wird.
Mit aRts werden einige Hilfs- und Steuerprogramme ausgeliefert. Sie müssen sich mit diesen Programmen vertraut machen, wenn Sie aRts effektiv nutzen wollen. Dieser Abschnitt beschreibt jedes dieser Programme und ihre Aufrufparameter.
Wenn Sie aRts unter KDE einsetzen, stellt KDE-Kontrollzentrum einige Einstellmöglichkeiten in dem Abschnitt Klänge bereit. Einige dieser Einstellungen werden von aRts verwendet. Weiterhin können Sie Klänge mit verschiedenen Systemnachrichten des Fenstermanagers oder von KDE im Abschnitt Erscheinungsbild+Systemnachrichten verknüpfen. Das Handbuch zu KControl gibt genauere Informationen über diese Einstellungen.
Der Zugriff auf die Klangressourcen wird durch artsd, den aRts-Dämon kontrolliert. Das ermöglicht es verschiedenen Anwendungen, gleichzeitig Anfragen an den Soundserver zu senden, der sie dann mixt und zusammen wiedergibt. Ohne einen zentralen Soundserver kann immer nur eine Anwendung zur Zeit die Klangressourcen verwenden.
Zur Verwendung von aRts darf nur eine Instanz von artsd aktiv sein. Sie wird normalerweise beim Start von KDE gestartet, wenn der zugehörige Eintrag in KControl im Abschnitt Soundserver aktiviert ist.
Das Programm akzeptiert die folgenden Aufrufparameter:
artsd [-n
-p
-N
-W
] [n
-a
audiomethod
-r
sampling rate
-b
bits
-d
-D
devicename
-F
fragments
-S
size
-s
seconds
-m
] [appName
-h
-A
-v
-l
]level
-r Sampling-Rate
Setzt die zu verwendende Sampling-Rate.
-h
Verwendungshinweise anzeigen.
-n
Netzwerk-Transparenz aktivieren.
-p Port
Legt den zu verwendenden TCP-Port fest (setzt -n
voraus).
-u
Öffentlich, keine Authentifizierung (unsicher).
-d
Volle Duplex-Fähigkeit aktivieren.
-D Gerätename
Audiogerät festlegen (normalerweise /dev/dsp
).
-F Fragmente
Anzahl der Fragmente festlegen.
-S Größe
Legt die Fragmentgröße in Byte fest.
-s Sekunden
Legt die Zeit bis zum automatischen Aussetzen in Sekunden fest. Der Wert Null deaktiviert das automatische Aussetzen.
-m appName
Gibt den Namen der Anwendung an, die für die Ausgabe von Fehler- , Warn- und Informationsmeldungen verwendet wird. Wenn Sie KDE verwenden, können Sie das Hilfsprogramm artsmessage verwenden.
-N
Erhöht die Größe der Netzwerkpuffer auf einen Wert, der für ein 10 mbps LAN erforderlich ist. Diese Einstellung ist äquivalent zur Option -w 5 (weiter unten).
-w n
Wenn Sie artsd über eine Netzwerkverbindung zu einem anderen Rechner betreiben, sollten Sie die Puffer vergrößern, um Aussetzer zu vermeiden. aRts stellt Anwendungen eine Standardpuffergröße bereit. Ohne diese Option basiert die Größe auf Abschnittgröße * Abschnittanzahl. Durch diese Option können Sie diese Größe vom Standardwert um einen Faktor von n
erhöhen.
-l Ebene
Legt die Informationsebene fest - 3 (keine), 2 (Warnungen), 2 (Informationen), 0 (Debug).
-v
Versionsnummer anzeigen.
In den meisten Fällen reicht das Kommando artsd zum Start.
Für ein gutes Echtzeit-Antwortverhalten, sollte artsd normalerweise als Echtzeit-Prozess (auf Systemen, die solche Prozesse unterstützen) gestartet werden. Das erfordert root-Rechte, daher kann artsd aus Sicherheitsgründen durch ein kleines Startprogramm namens artswrapper gestartet werden, das Echtzeitpriorität setzt (es arbeitet als root
) und dann artsd als Nicht-root startet.
Falls man artswrapper als SUID root
ausführt, wird die Qualität der Wiedergabe aufgrund weniger Unterbrechungen der Musik erhöht. Allerdings erhöht sich dadurch auch das Risiko einer Beschädigung des Rechners durch einen Fehler oder einen böswilligen Benutzer. Außerdem kann die Wiedergabe einer hochwertigen Musikaufnahme in einem Mehr-Benutzersystem negative Auswirkungen auf die Geschwindigkeit anderer Prozesse und Benutzer haben, die „produktiv“ arbeiten wollen.
Der Befehl artsshell ist ein Hilfsprogramm, das verschiedene Funktionen im Zusammenhang mit dem Soundserver ausführt. Das Programm wird in der Zukunft vermutlich mit weiteren Funktionen ausgestattet (einen Überblick geben die Kommentare im Quelltext).
Die Befehlszeile zum Start hat das folgende Format:
artsshell [[suspend] | [status] | [terminate] | [autosuspend secs
] | [networkbuffers n
] | [volume [volume
]] | [stereoeffect options
]] [-h
-q
]
artsshell [options] Befehl
[Befehlsoptionen
]
Folgende Optionen werden unterstützt:
-q
Ausgabe unterdrücken.
-h
Verwendungshinweise anzeigen.
Folgende Befehle werden unterstützt:
suspend
Der Soundserver schaltet sich aus.
status
Statusinformationen des Soundservers anzeigen.
terminate
Den Soundserver beenden. Das kann Programme, die den Soundserver verwenden, zum Absturz bringen.
autosuspend
Sekunden
Setzt die Zeit bis zum Aussetzen auf die angegebene Anzahl von Sekunden. Der Soundserver setzt automatisch aus, wenn er die angegebene Zeit unbeschäftigt ist. Der Wert Null deaktiviert das automatische Aussetzen.
networkbuffers
n
Setzt die Größe der Netzwerkpuffer auf das n
-fache der Standardgröße.
volume
[Lautstärke
]Legt die Lautstärkeskalierung für die Soundserver Audioausgabe fest. Das Argument Lautstärke
ist eine Kommazahl. Bei Aufruf ohne Argument wird der aktuelle Wert angezeigt.
Liste der Stereoeffekte
Liste aller verfügbaren Stereoeffektmodule
stereoeffect insert [top|bottom]
Name
Fügt einen Stereoeffekt in einen Stereoeffektstapel ein. Gibt einen Namen zurück, der für ein späteres Löschen erforderlich ist. Der Effekt kann oben oder unten (Standardeinstellung) hinzugefügt werden.
stereoeffect remove
Id
Löscht den Stereoeffekt mit dem Namen Id
vom Effektstapel.
Der Befehl artsplay ist ein einfaches Hilfsprogramm zum Abspielen einer Klangdatei. Der Befehl hat ein einziges Argument, nämlich den Namen der an den Soundserver zu schickenden Datei. Die Klangdatei kann zu jedem der üblichen Typen gehören, also wav
oder au
. Mit diesem Befehl kann man testen, ob der Soundserver funktioniert. Indem man zwei Befehl parallel oder in schneller Folge gibt, kann man demonstrieren, wie der Soundserver mehrere Ausgaben mixen kann.
Der Soundserver unterstützt nur Anwendungen, die aRts-aktiviert sind. Viele herkömmliche Anwendungen wollen auf die Klanggeräte direkt zugreifen. Das Programm artsdsp ist eine Übergangslösung, die es vielen dieser Anwendungen erlaubt, ohne Änderung zu funktionieren.
Wenn ein Programm unter artsdsp ausgeführt wird, werden alle Zugriffe auf das Audiogerät /dev/dsp
abgefangen und in aRts API-Aufrufe umgewandelt. Diese Emulation ist nicht perfekt, aber die meisten Anwendungen funktionieren auf diese Weise mit einer kleinen Einbuße an Geschwindigkeit und Antwortverhalten.
Der Befehl artsdsp hat das folgende Format:
artsdsp [Optionen
] Anwendung Parameter
Folgende Optionen werden erkannt:
-h
, --help
Zeigt eine kurze Hilfe.
-n
--name
= Name
Verwendet Name
um den Spieler gegenüber artsd.zu identifizieren.
-m
--mmap
Speicher-Mapping emulieren (z. B. für Quake).
-v
--verbose
Zeigt Parameter an.
Ein typischer Aufruf ist:
artsdsp
-v
-m
realplay
Lied.mp3
Einige Anwendungen funktionieren mit der Einstellung --mmap
besser. Es werden nicht alle Funktionen des Klanggerätes voll emuliert, aber die meisten Anwendungen sollten funktionieren. Falls eine nicht funktionieren sollte, senden Sie eine detailierte Benachrichtigung ein. So können die Entwickler möglicherweise eine Lösung finden. Bedenken Sie, dass es sich um eine Übergangslösung handelt. Die beste Lösung ist sicherlich, Unterstützung für aRts zu der Anwendung hinzuzufügen. Wenn Ihre Lieblingsanwendung aRts nicht unterstützt, bitten Sie die Entwickler diese Unterstützung nachzurüsten.
Dieses kleine Hilfsprogramm kann Audion-Rohdaten zum Soundserver schicken. Sie müssen das Datenformat (Samplingrate, Samplegröße und Anzahl der Kanäle) angeben. Es ist ein Programm, das Sie vermutlich nicht oft benötigen, das aber für Testzwecke recht praktisch ist. Die Aufrufsyntax ist:
artscat [ Optionen
] [ Dateiname
]
Wenn Sie keinen Dateinamen angeben, wird von der Standardeingabe gelesen. Folgende Optionen werden unterstützt:
-v
--verbose
Setzt die zu verwendende Sampling-Rate.
-b
Bits
Setzt die Sample-Größe fest (8 oder 16).
-c
Kanäle
Setzt die Anzahl der Kanäle fest (1 oder 2).
-h
Nur Verwendungshinweise anzeigen.
Dies ist ein graphisches Programm für die Durchführung einer Anzahl Aufgaben im Zusammenhang mit dem Soundserver. Das Hauptfenster hat zwei Lautstärkeanzeigen und einen Schieberegler für die Gesamtausgabelautstärke. Aus dem Menü können Sie weitere Funktionen auswählen:
Öffnet ein Fenster mit einer Echtzeit-Spektrumanalysator-Anzeige.
Zeigt die aktiven Klangquellen an und erlaubt die Zuordnung zu einem der verfügbaren Busse.
Zeigt an, ob der Soundserver läuft und Echtzeitpriorität besitzt. Außerdem wird angezeigt, wann der Soundserver automatisch aussetzt. Durch einen Knopf kann er auch sofort ausgeschaltet werden.
Zeigt aktive MIDI-Ein- und Ausgabegeräte an und erlaubt die Herstellung von Verknüpfungen [TODO: Funktioniert vermutlich noch nicht! Mehr Details benötigt].
Verbindet einen FreeVerb-Echoeffekt mit dem Stapel von aRts Ausgabeeffekten und erlaubt eine graphische Kontrolle der Effektparameter.
Ändert die Lautstärkeanzeige des Hauptfensters auf eine farbige LED-Anzeige anstatt Fortschrittsbalken.
Dieses Hilfsprogramm unterstützt Entwickler bei der Verwendung des aRts C-API. Es gibt die geeignete Compiler- und Linker-Optionen aus, die zur Kompilierung und zum Linken von Programmen mit aRts benötigt werden. Es ist gedacht zur Verwendung innerhalb von make-Dateien zur Unterstützung von Portabilität. Das Programm kennt drei Optionen:
--cflags
Zeigt die Kompiler-Flags an, die zur Kompilierung mit dem aRts C-API benötigt werden.
--libs
Zeigt die Linker-Flags an, die zum Linken mit dem aRts C-API benötigt werden.
Zeigt die Versionsnummer des artsc-config Befehles an.
Eine typische Ausgabe dieses Befehls sieht folgendermaßen aus:
%
artsc-config
--cflags
-I/usr/local/kde2/include/artsc
%
artsc-config
--libs
-L/usr/local/kde2/lib -ldl -lartsc -DPIC -fPIC -lpthread
%
artsc-config
--version
0.9.5
Sie können dieses Programm in einer Make-Datei z.B. in einer solchen Regel verwenden:
artsc: artsc.c gcc `artsc-config --cflags` -o artsc artsc.c `artsc-config --libs`
Der Befehl mcopidl ist der IDL-Datei Kompiler für MCOP, das Multimedia Communication Protokol, das von aRts verwendet wird. Interfaces sind in aRts in IDL, einer sprachunabhängigen Interface Definition Language, geschrieben. Das Programm mcopidl akzeptiert ein IDL-Datei als Eingabe und generiert C++-Header und Quelldateien für eine Klasse, die das Interface implementiert. Der Befehl hat die folgende Syntax:
mcopidl [ Optionen
] Dateiname
Folgende Optionen sind möglich:
-I
Verzeichnis
Suche in Verzeichnis
nach Include-Dateien.
-e
Name
Schließe die Struktur, das Interface oder den Aufzählungstyp Name
von der Erzeugung aus.
-t
Erzeuge zusätzlich die Dateien .mcoptype
/ .mcopclass
, die Informationen für die IDL-Datei enthalten.
Weitere Informationen über MCOP und IDL finden Sie im Abschnitt Interfaces und IDL.
Wenn Sie aRts-builder verwenden wollen, sollten Sie zuerste den Klangserver (artsd) starten. Normalerweise ist er bereits gestartet, wenn Sie KDE 2.1 oder höher verwenden. Ansonsten können Sie ihn in KControl unter Sound & Multimedia+Sound-System zum automatischen Start einrichten.
Wenn Sie aRts verwenden, startet es kleine Module. aRts-builder ist ein Werkzeug zur Erstellung neuer Strukturen von kleinen verbundenen Modulen. Sie können die Module einfach innerhalb des Gitters anordnen. Wählen Sie dazu aus dem Menü aus und klicken Sie dann irgendwo im grün-grauen Bereich.
Module habe üblicherweise Kanäle (durch die Audiosignale hinein und hinaus gelangen). Um zwei Kanäle zu verbinden, klicken Sie auf den Ersten (dadurch wird er orange) und dann auf den Zweiten. Sie können einen Eingabekanal (auf der oberen Modulseite) nur mit einem Ausgabekanal (auf der unteren Modulseite) verbinden. Wenn Sie einem Kanal einen festen Werte geben wollen (oder einen Kanal trennen wollen) so doppelklicken Sie auf diesen.
Starten Sie aRts-builder.
Um die Ausgabe zu hören, benötigen Sie ein Synth_AMAN_PLAY-Modul. Sie erstellen ein solches Modul, indem Sie ->->-> auswählen und auf einen freien Platz im Modulbereich klicken. Platzieren Sie das Modul unterhalb der fünften Linie, da wir noch einige Module oberhalb einfügen werden.
Das Modul hat die Parameter title
und autoRestoreID
(in der Nähe des linken Kanals) zur Identifikation. Um diese auszufüllen, doppelklicken Sie auf diese Kanäle, wählen Sie konstanter Wert und tippen Sie tutorial
in das Eingabefeld. Klicken Sie auf zur Bestätigung.
Klicken Sie auf ->. Sie hören bisher nichts. Das Abspielmodul benötigt irgendetwas als Eingabe. Wenn Sie der Stille eine Weile gelauscht haben, klicken Sie auf und gehen Sie zu Schritt 2
Erstellen Sie ein Synth_WAVE_SIN-Modul (im Menü unter ->->) und fügen Sie dieses Modul oberhalb von Synth_AMAN_PLAY ein (lassen Sie eine Zeile Platz dazwischen).
Wie Sie sehen, produziert dieses Modul eine Ausgabe, erfordert aber eine Position pos als Eingabe. Verbinden Sie zuerst die Ausgabe mit den Lautsprechern. Klicken Sie auf den Kanal out des Synth_WAVE_SIN-Modules und dann auf den Kanal left des Synth_AMAN_PLAY-Modules. Damit sind diese zwei Module verbunden.
Keiner der Oszillatoren in aRts benötigt eine Frequenz als Eingabe, sondern nur eine Position innerhalb der Welle. Die Position muss zwischen 0 und 1 liegen. Das wird für ein Standard-Synth_WAVE_SIN-Modul auf den Bereich 0 bis 2*Pi umgerechnet. Um eine bestimmte Frequenz zu erzeugen, benötigen Sie ein Synth_FREQUENCY-Modul.
Erstellen Sie ein Synth_FREQUENCY-Modul (unter ++) und verbinden Sie den „pos“-Ausgang mit dem „pos“-Eingang des Synth_WAVE_SIN-Modules. Legen Sie den Frequenzeingang des Frequenzgenerators auf den konstanten Wert 440.
Wählen Sie ->. Sie sollten einen Sinuston von 440 Hz in einem von Ihren Lautsprechern hören. Wenn Sie genug zugehört haben, klicken Sie auf und gehen Sie zu Schritt 3.
Es würde sich besser anhören, wenn der Sinuston aus beiden Lautsprechern zu hören wäre. Verbinden Sie den rechten Eingang von Synth_PLAY auch mit dem Ausgang von Synth_WAVE_SIN.
Erstellen Sie ein Synth_SEQUENCE-Objekt (durch ->->). Es sollte am oberen Rand platziert werden. Wenn Sie mehr Platz benötigen, können Sie die anderen Module verschieben, indem Sie sie auswählen (um mehrere auszuwählen, verwenden Sie Umschalt) und mit der Maus bewegen.
Nun verbinden Sie den Frequenzausgaben von Synth_SEQUENCE mit dem Frequenzeingang des Synth_FREQUENCY-Moduls. Stellen Sie die Geschwindigkeit der Sequenz auf den konstanten Wert 0.13 (der Geschwindigkeitseingang ist der linke).
Geben Sie nun für den rechten Eingang (Sequenz) von Synth_SEQUENCE als konstanten Wert A-3;C-4;E-4;C-4
ein. Das legt eine Sequenz fest. Mehr dazu finden Sie im Abschnitt Modulreferenz.
Synth_SEQUENCE benötigt unbedingt eine Sequenz und eine Geschwindigkeit. Ohne diese Angaben wird das Programm vermutlich abstürzen.
Wählen Sie ->. Sie sollten nun eine nette Sequenz hören. Klicken Sie auf und gehen Sie zu Schritt 4.
Erstellen Sie ein Synth_PSCALE-Modul (durch ->->). Trennen Sie den Ausgang der SIN-Welle durch doppelklicken und auswählen von nicht verbunden. Verbinden Sie
den SIN-Ausgang mit dem Eingang (inval) von PSCALE
Den Ausgang von PSCALE mit dem linken Eingang von AMAN_PLAY
den Ausgang von PSCALE mit dem rechten Eingang von AMAN_PLAY
den SEQUENCE-Ausgang (pos) mit dem PSCAL-Eingang (pos).
Setzen Sie schließlich den Eingang top von PSCALE auf einen konstanten Wert, z.B. 0.1.
Das funktioniert folgendermaßen: Das Modul Synth_SEQUENCE gibt zusätzliche Informationen über die Position der gerade erklingenden Note, wobei 0 gerade gestartet und 1 beendet bedeutet. Das Modul Synth_PSCALE skaliert die Lautstärke des Audiostroms von 0 (Ruhe) über 1 (Originallautstärke) zurück zu 0 (Ruhe) abhängig von der Position. Die Position, an der die Maximallautstärke erklingen soll, kann als Positionswert (pos) angegeben werden. 0.1 bedeutet, das nach 10% der Note die Lautstärke ihren Maximalwert erreicht und danach der Ausklingvorgang startet.
Wählen Sie ->. Sie sollten nun eine nette Sequenz hören. Klicken Sie auf und gehen Sie zu Schritt 4.
Starten Sie aRts-builder ein zweites Mal
Erstellen Sie ein Synth_AMAN_PLAY-Modul und benennen Sie es sinnvoll. Erstellen Sie ein Synth_BUS_DOWNLINK-Modul und:
benennen Sie den Synth_BUS_DOWNLINK-Bus mit dem Namen Audio (das ist nur ein Name, man könnte auch jeden anderen Name verwenden)
Verbinden Sie den linken Ausgang von Synth_BUS_DOWNLINKmit dem linken Eingang von Synth_AMAN_PLAY
Verbinden Sie den rechten Ausgang von Synth_BUS_DOWNLINK mit dem rechten Eingang von Synth_AMAN_PLAY
Wenn Sie die Struktur jetzt ausführen, hören Sie noch nichts.
Gehen Sie zurück zur ersten Struktur in der ersten Instanz von aRts-builder mit dem Synth_WAVE_SIN-Modul und ersetzen Sie das Modul Synth_AMAN_PLAY durch ein Synth_BUS_UPLINK,-Modul und benennen Sie es Audio (oder den Namen, den Sie für die entsprechende Komponente in der zweiten Instanz von aRts-builder verwendet haben). Um ein Modul zu löschen, wählen Sie es aus und wählen Sie -> aus dem Menü ( oder drücken Sie die Entfernen-Taste).
Wählen Sie +Struktur ausführen. Sie hören die Notensequenz wiedergegeben über die Bus-Verbindung.
Wenn Sie herausfinden wollen, wozu eine solche Funktion nützlich ist, klicken Sie auf ( in der Instanz, die das Synth_SEQUENCE-Modul enthält, die andere Struktur wird nicht verändert) und gehen Sie zu Schritt 6.
Wählen Sie -> in der Instanz, die das Synth_SEQUENCE-Modul enthält und benennen Sie die Struktur Anleitung. Bestätigen Sie mit .
Wählen Sie ->
Starten Sie eine weitere Instanz von aRts-builder und wählen Sie -> und laden Sie die Struktur Anleitung.
Nun wählen Sie im Menü -> in beiden Instanzen. Sie hören nun die gleiche Struktur zweimal. Abhängig von der Zeitverschiebung wird es mehr oder weniger glücklich klingen.
An dieser Stelle können Sie noch folgendes tun: Starten Sie Noatun und spielen Sie einige mp3
-Dateien ab. Starten Sie artscontrol und wählen Sie ->. Es wird Noatun und ihre Struktur „Anleitung“ angezeigt. Klicken Sie doppelt auf Noatun. Daraufhin wird eine Liste der Ausgabegeräte angezeigt. Auch die Struktur wird mit aufgeführt. Sie können die Ausgabe von Noatun über den Audio-Bus durch ihre Wiedergabestruktur leiten.
Jetzt wollen wir den Sinusgenerator in ein wirkliches Musikinstrument verwandeln. Dazu benötigen Sie ein Gerät, das MIDI-Ereignisse an aRts senden kann. Sie können ein externes Keyboard (wie im folgenden beschrieben wird) aber auch einen Sequenzer, der den Midi-bus unterstützt, wie Brahms verwenden.
Beenden Sie zuerst alle überflüssigen Instanzen von aRts-builder. Sie benötigen lediglich die Instanz mit dem Sinusgenerator. Wählen Sie dreimal -> und dreimal -> und platzieren Sie die Module geeignet.
Wählen Sie +Positionen/Namen ändern und benennen Sie die Kanäle um in frequency(Frequenz), velocity(Lautstärke), pressed(gedrückt), left(links), right(rechts) und done(stopp) um.
Sie können nun das Modul Synth_SEQUENCE löschen und stattdessen den Frequenzeingangskanal mit dem Modul Synth_FREQUENCY-Eingang verbinden. Was soll nun mit dem pos-Eingang passieren?
Dieser Eingang bleibt unbesetzt, da es keinen Algorithmus der Welt gibt, der vorausberechnen kann, wann ein Spieler die Taste des Keyboards, die er gerade gedrückt hat, wieder loslassen wird. Daher haben wir nur einen Parameter gedrückt stattdessen, der anzeigt, ob der Spieler die Taste noch gedrückt hält (gedrückt=1: Taste immer noch heruntergehalten; gedrückt=0: Taste losgelassen)
Das Synth_PSCALE-Objekt muss nun auch ersetzt werden. Ersetzen Sie es durch ein Synth_ENVELOPE_ADSR-Modul (durch ->->). Verbinden Sie:
den Struktureingang mit dem Ausgang active von ADSR
den SIN-Ausgang mit dem Eingang (inval) von ADSR
den Ausgang (outvalue) von ADSR mit dem linken Strukturausgang
den ADSR-Ausgang (outvalue) mit dem rechten Strukturausgang
Setzen Sie die Parameter attack auf 0.1, decay auf 0.2, sustain auf 0.7 und release auf 0.1.
Weiterhin müssen wir daran denken, das die Instrumentenstruktur wissen muss, wenn der Spieler mit spielen fertig ist, da sonst der letzte Klang nicht beendet wird, auch wenn die letzte Taste losgelassen worden ist. Glücklicherweise weiß die ADSR-Hüllkurve (envelope), wann nichs mehr zu hören ist, da das Signal nach dem Loslassen der letzten Taste irgendwann auf Null reduziert wird.
Das wird erreicht, indem der Ausgang done auf 1 gesetzt wird. Verbinden Sie diesen Ausgang mit dem Ausgangskanal stopp. Damit wird die Struktur beendet, sobald dieser Ausgang auf 1 wechselt.
Benennen Sie die Struktur in Instrument_Anleitung um (durch ->). Speichern Sie die Struktur nun (der vorgegebene Name sollte jetzt Instrument_Anleitung sein).
Starten Sie nun artscontrol, wählen Sie -> und wählen Sie->. Hier sollten Sie in der Lage sein, ihr Instrument (Anleitung) auszuwählen.
Wenn Sie jetzt ein Terminal öffnen und midisend
eintippen, sollte midisend und das Instrument im aRts MIDI-Manager angezeigt werden. Wählen Sie beides aus und klicken Sie auf . Damit sind die Vorbereitungen abgeschlossen. Nehmen Sie nun ihr Keyboard und beginnen Sie zu spielen (selbstverständlich nachdem Sie es mit dem Computer verbunden haben).
Sie sind nun in der Lage, aRts zu verwenden. Hier sind noch einige Tipps, die den Umgang mit Strukturen verbessern können:
Versuchen Sie andere Module anstelle von SIN. Wenn Sie eine TRI-Wellenform verwenden, werden Sie vermutlich feststellen, das diese Wellenform nicht besonders hübsch klingt. Hängen Sie einen SHELVE_CUTOFF-Filter an das TRI-Modul, um alle Frequenzen oberhalb einer bestimmten Grenzfrequenz (versuchen Sie etwa 1000 Hz oder besser noch die doppelte Eingabefrequenz +200 Hz oder einen ähnlichen Wert).
Verwenden Sie mehrere Oszillatoren zusammen. Synth_XFADE kann zum kreuzweisen mixen (cross fade) von zwei Signalen verwendet werden, Synth_ADD zum Addieren von zwei Signalen.
Verstimmen Sie die Oszillatoren geringfügig gegeneinander. Das erzeugt nette Schwebungen.
Experimentieren Sie mit mehreren Hüllkurven (envelopes) gleichzeitig.
Stellen Sie Instrumente zusammen, die verschiedene Signale auf den linken und rechten Ausgang legen.
Verarbeiten Sie das Signal, das aus dem Downlink-Bus kommt, weiter. Sie können für einen Echo-Effekt das ursprüngliche Signal etwas verzögert dazumischen.
Verwenden Sie die Lautstärkeeinstellung (die Stärke, mit der die Taste gedrückt worden ist). Ein besonderer Effekt entsteht, wenn der Lautstärkewert nicht nur die Ausgabelautstärke sondern auch den Klang des Instrumentes verändert (zum Beispiel die Grenzfrequenz).
...
Wenn Sie eine besondere Struktur konstruiert haben, schicken Sie sie an die aRts-Internetseite. Sie kann dann der nächsten Version beigelegt werden.
Die Beispiele, mit denen aRts-builder verteilt wird, können Sie unter -> finden. Einige befinden sich im angezeigten Verzeichnis, einige (die in der aktuellen Version aus irgendwelchen Gründen nicht funktionieren) befinden sich im todo-Verzeichnis.
Die Beispiele können in mehrere Kategorien eingeteilt werden:
Modulbeispiele demonstrieren jeweils eines der in arts enthaltenen Modules (example_*.arts
benannt). Sie senden üblicherweise irgendwelche Ausgaben an die Soundkarte.
Instrumente (mit Namen instrument_*.arts
) sind aus den grundlegenden arts-Modulen zusammengesetzt. Sie haben standardisierte Ein- und Ausgabekanäle, so dass sie mit dem MIDI-Manager aus artscontrol verwendet werden können.
Vorlagen ( mit Namen template_*.arts
) zur Erstellung neuer Module.
Effekte (mit Namen effect_*.arts
) können als Bausteine verwendet werden [momentan alle im todo-Verzeichnis]
Mixer-Elemente (mit Namen mixer_element_*.arts
) können zur Erstellung von Mixern mit graphischen Kontrollelementen verwendet werden [momentan alle im todo-Verzeichnis ]
Verschiedene Module, die in keine der angegebenen Kategorien passen.
Detailierte Beschreibung der einzelnen Module:
example_stereo_beep.arts
Sendet einen 440Hz-Sinuston an den linken und einen 880Hz-Sinuston an den rechten Kanal der Soundkarte. Dieses Modul wird in der aRts-Dokumentation erwähnt.
example_sine.arts
Erzeugt einen 440Hz-Sinuston.
example_pulse.arts
Erzeugt einen 440Hz-Impulston mit 20%-Arbeitswiederholung (duty cycle).
example_softsaw.arts
Erzeugt eine 440Hz-Sägezahnschwingung.
example_square.arts
Erzeugt eine 440Hz-Rechteckschwingung.
example_tri.arts
Erzeugt eine 440Hz-Dreieckschwingung.
example_noise.arts
Erzeugt weißen Lärm.
example_dtmf1.arts
Erzeugt einen Doppelton aus einer 697Hz- und 1209Hz-Sinusschwingung, die mit 0.5 skaliert und addiert werden. Es entsteht der DTMF-Ton für die Ziffer "1" einer Telefontastatur.
example_atan_saturate.arts
Eine Dreieckschwingung wird mit einem atan-Sättigungsfilter verändert.
example_autopanner.arts
Verwendet ein Autopan-Modul, um einen 400Hz-Sinuston mit einer Frequenz von 2 Hz zwischen dem linken und rechten Lautsprecher hin- und herzubewegen.
example_brickwall.arts
Skaliert eine Sinusschwingung mit dem Faktor 5 und verändert sie mit einem brickwall-Begrenzer.
example_bus.arts
Vom Bus mit dem Namen „Bus“ wird zum Bus „out_soundcard“ eine Verbindung mit vertauschten Kanälen hergestellt.
example_cdelay.arts
Verbindet von einem Bus namens „Delay“ zum rechten Ausgangskanal mit einer Verzögerung von 0.5 Sekunden (cdelay), während der linke Kanal unverändert bleibt. Mit artscontrol können Sie diesen Effekt mit einem Abspieler verbinden. Das Resultat ist hörenswert.
example_delay.arts
Das gleiche Beispiel wie example_cdelay.arts
, mit dem Unterschied, dass der delay-Effekt anstelle von cdelay verwendet wird.
example_capture_wav.arts
Mit dem Modul Synth_CAPTURE_WAV wird ein 400Hz-Sinuston als wav-Datei gespeichert. Lassen Sie das Modul für 2 Sekunden laufen und untersuchen Sie die in /tmp
erzeugte Datei. Sie können Sie mit einem Spieler wie kaiman abspielen.
example_data.arts
Mit einem Data-Modul wird ein konstanter Strom mit dem Wert „3“ erzeugt und für die periodische Anzeige an ein Debug-Modul gesendet. Das Beispiel enthält weiterhin ein Nil-Modul, das demonstriert, wie man eine Struktur erzeugt, die gar nichts tut.
example_adsr.arts
Demonstriert, wie man mit dem Envelope-Adsr-Modul einen einfachen Instrumentenklang erzeugt, der durch eine Rechteckschwingung geschaltet wird.
example_fm.arts
Ein FM-Quellmodul erzeugt einen 440Hz-Sinuston, der dann mit 5 Hz frequenzmoduliert wird.
example_freeverb.arts
Verbindet den Freeverb-Effekt von einem Bus downlink zu einem Bus outlink. Sie können mit artscontrol diesen Effekt mit einem Spieler verbinden und sich das Resultat anhören.
example_flanger.arts
Implementiert einen einfachen Flanger-Effekt (scheint bisher nicht zu funktionieren).
example_moog.arts
Diese Struktur kombiniert zwei Kanäle von einem Bus, schickt das Signal durch einen Moog-VCF-Filter und das Ergebnis auf den out_soundcard-Bus.
example_pitch_shift.arts
Diese Struktur schickt den linken Kanal von Soundkartendaten durch einen Höhenverschiebungseffekt (pitch shift). Mit dem Speed-Parameter kann der Effekt modifiziert werden.
example_rc.arts
Diese Struktur sendet weißen Lärm durch einen RC-Filter und dann an die Soundkarte. Betrachten Sie das Ergebnis in der FFT-Anzeige von artscontrol, um den Unterschied zu ungefiltertem Lärm zu sehen.
example_sequence.arts
Demonstriert die Verwendung des Sequencer-Moduls durch das Abspielen einer Notensequenz.
example_shelve_cutoff.arts
Diese Struktur schickt weißen Lärm durch einen Shelve-Cutoff-Filter und dann an die Soundkarte. Das Ergebnis können Sie in der FFT-Anzeige von artscontrol betrachten.
example_equalizer.arts
Demonstriert das Std_Equalizer-Modul. Es hebt die Höhen und Tiefen um 6dB an.
example_tremolo.arts
Demonstriert den Tremolo-Effekt. Der rechte und linke Kanal werden mit einem 10Hz-Tremolo moduliert.
example_xfade.arts
Dieses Beispiel mixt einen 440Hz- und einen 880Hz-Sinuston mit einem "cross fader". Verändern Sie die Prozentanzeige von -1 bis 1, um die Mischung der zwei Signale zu beeinflussen.
example_pscale.arts
Demonstriert das Pscale-Modul (ich zweifle, dass dieses Beipiel aussagekräftig ist).
example_play_wav.arts
Illustriert das Play_Wave-Modul. Sie müssen den kompletten Pfad zur wav
-Datei als Parameter filename angeben.
Zeigt, wie das Multi_Add-Modul eine beliebige Anzahl Eingangssignale aufsummiert. Drei Eingänge mit den Werten 1,2 und 3 erzeugen den Ausgangswert 6.
aRts beruht auf einem Synthesemodell, bei dem aus kleinen Modulen, die jedes für sich eine spezialisierte Aufgabe haben, komplexe Strukturen aufgebaut werden. Die Module haben normalerweise Eingänge, über die Signale und Parameter übergeben werden, und Ausgänge, an denen die Ergebnissignale anliegen.
Das Modul Synth_ADD zum Beispiel addiert die zwei Eingangssignal zu einem Summensignal, das als Ausgangssignal verfügbar ist. Die Stellen, mit denen die Ein-/Ausgangssignale verbunden werden heißen Kanäle (ports).
Eine Struktur besteht aus mehreren verbundenen Modulen, bei denen einige Kanäle feste Parameter haben, andere untereinander verbunden sind während einige Kanäle vielleicht gar nicht verbunden sind.
aRts-builder dient zur Beschreibung dieser Strukturen. Sie beschreiben, welche Module in welcher Weise verbunden werden sollen. Wenn Sie damit fertig sind, können Sie die Beschreibung speichern oder aRts zu der Erzeugung der Struktur veranlassen (Struktur ausführen).
Als Ergebnis hören Sie wahrscheinlich einige Klänge, falls nichts schiefgegangen ist.
Angenommen Sie haben ein Programm mit Namen „Mausklick“, das ein „Klicken“ von sich geben soll, wenn Sie eine Maustaste betätigen. Die Verzögerungszeit ist die Zeit zwischen dem Betätigen der Maustaste und dem Ertönen des Klicken. Die Einstellung der Verzögerungszeit besteht aus mehreren Verzögerungszeiten, die unterschiedliche Ursachen haben.
In dieser einfachen Anwendung werden an folgenden Stellen Verzögerungen verursacht:
Zeit, die der Betriebssystemkern benötigt, um dem X11-Server den Mausklick mitzuteilen.
Zeit, die der X11-Server benötigt, um der Anwendung den Mausklick mitzuteilen.
Zeit, die die Anwendung benötigt, um aufgrund des Mausklicks einen Klick-Ton auszulösen.
Zeit, die die Anwendung benötigt, um dem Soundserver den Befehl zum Klick-Ton zu geben.
Zeit, die der Klick-Ton benötigt (den der Soundserver sofort in den Ausgabestrom einmischt), um den Datenpuffer zu passieren, bis er die Stelle erreicht, an der die Soundkarte gerade Daten wiedergibt.
Zeit, die der Klick-Ton von den Lautsprechern bis zu Ihrem Ohr benötigt.
Die ersten drei Verzögerungszeiten sind extern für aRts. Sie sind wichtig, aber nicht Gegenstand dieser Dokumentation. Sie sollten sich dennoch dieser Verzögerungen bewusst sein, denn selbst wenn Sie alle anderen Verzögerungen sehr gering halten, erhalten Sie vielleicht dennoch nicht exakt die erwarteten Resultate.
Ein Spielbefehl an den Server besteht normalerweise aus einem MCOP-Funktionsaufruf. Es gibt Geschwindigkeitstests,die belegen, dass ein solcher Befehl auf einem Rechner bei der jetzigen Implementation etwa 9000 mal pro Sekunde ausgeführt werden kann. Ich denke, das meiste der Zeit wird Kernel-Overhead für die Umschaltung zwischen verschiedenen Anwendungen sein. Natürlich hängen diese Werte von den exakten Parametertypen des Aufrufs ab. Die Übergabe eines vollständigen Bildes in einem Aufruf dauert länger als die Übergabe eines einzigen Werte. Das Gleiche gilt für den Rückgabewert. Für normale Zeichenketten (wie der Dateiname der zu spielenden wav
-Datei) sollte das aber kein Problem darstellen.
Das bedeutet, diese Zeit kann mit 1/9000 Sekunde abgeschätzt werden. Das ist weniger als 0,15 ms. Sie werden sehen, das diese Zeitspanne unwichtig ist.
Die nächste Verzögerungszeit ist die Zeit vom Starten des Soundservers und der Ankunft dieses Beginns auf der Soundkarte. Der Server muss einen Puffer verwenden, damit man keine Aussetzer hört, wenn eine andere Anwendung wie der X11-Server oder das „Mausklick“-Programm aktiv sind. Das wird unter Linux® verwirklicht, indem eine Anzahl Bruchstücke einer bestimmte Größe erzeugt werden. Der Server füllt die Bruchstücke und die Soundkarte spielt die Bruchstücke ab.
Angenommen es gibt drei Bruchstücke. Der Server füllt das Erste, die Soundkarte beginnt mit dem Abspielen. Der Server füllt das Zweite. Der Server füllt das Dritte. Der Server ist fertig, andere Anwendungen können nun aktiviert werden.
Nach dem ersten Bruchstück spielt die Soundkarte das Zweite ab und der Server füllt das erste Bruchstück wieder. Das geht immer so weiter.
Damit ergibt sich eine maximale Verzögerungszeit von (Anzahl der Bruchstücke)*(Größe eines Bruchstückes)/(Samplingrate * (Größe eines Samples)). Bei 44kHz Stereo und 7 Bruchstücken von je 1024 Byte Größe (die aktuellen Standardwerte von aRts) entspricht das einer Verzögerungszeit von 40 ms.
Diese Werte können Sie Ihren Anforderungen anpassen. Allerdings steigt die CPU-Belastung mit kleineren Verzögerungszeiten, da der Soundserver die Puffer häufiger und in kleineren Bruchstücken füllen muss. Es ist außerdem meistens unmöglich, bessere Werte zu erreichen, ohne das Aussetzer zu hören sind, es sei denn, sie versehen den Soundserver mit Echtzeit-Priorität.
Dennoch ist eine Einstellung von 3 Bruchstücken mit je 256 Bytes, die einer Verzögerung von 4,4 ms entsprechen, realistisch. Mit 4,4 ms Verzögerungszeit belastet aRts die CPU im Ruhezustand mit 7,5%. Mit einer Verzögerung von 40 ms beträgt die Belastung etwa 3% (bei einem PII-350, diese Werte können abhängig von der Soundkarte, Kernel-Version und anderen Faktoren variieren).
Jetzt zu der Zeit, die der Klick-Ton von den Lautsprechern bis zum Ohr benötigt. Bei einer angenommenen Distanz von 2 Metern ergibt sich bei einer Schallgeschwindigkeit von 330 Meter pro Sekunde eine Verzögerung von etwa 6 ms.
Streaming-Anwendungen produzieren ihre Klänge selbst. Angenommen, ein Spiel, das einen konstanten Strom von Samples erzeugt, soll nun für die Wiedergabe durch aRts verwendet werden. Als Beispiel: Wenn ich eine Taste drücke, hüpft die Spielfigur und es ertönt ein Boing-Klang.
Als Erstes muss man wissen, wie aRts Streaming realisiert. Der Ablauf ist ähnlich wie bei der Ausgabe auf einer Soundkarte. Das Spiel sendet einige Pakete mit Samples zum Soundserver. Angenommen, es sinc drei Pakete. Sobald der Soundserver das erste Paket wiedergegeben hat, schickt er eine Bestätigung zurück zum Spiel.
Das Spiel erzeugt ein neues Paket und schickt es zum Server. Währenddessen verarbeitet der Server das zweite Paket und so weiter. Die Verzögerungszeiten hier sind ähnlich wie bei dem einfachen Beispiel:
Zeit, bis der Betriebssystemkern dem X11-Server den Tastendruck mitgeteilt hat.
Zeit, bis der X11-Server dem Spiel den Tastendruck mitgeteilt hat.
Zeit, bis das Spiel entschieden hat, das aufgrund des Tastendrucks ein Boing-Ton auszugeben ist.
Zeit, bis das Paket mit dem Anfang des Boing-Tons den Soundserver erreicht hat.
Zeit, bis der Boing-Ton (den der Soundserver sofort in die Ausgabe einmischt) den Datenpuffer passiert hat bis zu der Stelle, an der die Soundkarte gerade wiedergibt.
Zeit, die der Boing-Ton von den Lautsprechern bis zum Ohr benötigt.
Die externen Verzögerungen sind gehen wiederum über den Inhalt dieses Dokumentes hinaus.
Offensichtlich hängt die Streaming-Verzögerung von der Zeit ab, die alle Pakete benötigen, einmal wiedergegeben zu werden. Also ist diese Zeit (Anzahl der Pakete)*(Größe eines Paketes)/(Samplingrate * (Größe eines Samples))
Wie man sieht, ist das die gleiche Formel, wie sie bei den Bruchstücken verwandt wird. Für Spiele sind solch geringer Verzögerungszeiten wie oben überflüssig. Eine realistische Festlegung für Spiekle wären 2046 Bytes pro Paket bei drei Paketen. Die resultierende Verzögerung ist dann etwa 35 ms.
Diese Berechnung basiert auf folgenden Annahmen: das Spiel rendert 25 Bilder pro Sekunde (für die Anzeige). Einen Verschiebung zwischen Ton und Film von einem Bild nimmt man nicht wahr. Daher ist eine Verzögerung von 1/25 Sekunde für Streaming akzeptabel, was wiederum bedeutet, das 40ms in Ordnung sind.
Außerdem werden die meisten Leute ihre Spiele nicht mit Echtzeit-Priorität spielen und daher ist die Gefahr von Aussetzern nicht zu vernachlässigen. Bei 3 Paketen je 256 Bytes ist Streaming möglich (nach eigenen Tests) - es verbraucht aber eine Menge CPU-Zeit.
Für die Serververzögerungszeiten können Sie genau wie oben rechnen.
Es gibt eine Menge Faktoren, die in komplexen Situationen mit einigen Streaming-Anwendungen und einigen Anderen sowie einigen Plugins auf dem Server einen Einfluß auf die CPU-Zeit haben. Um einige zu nennen:
CPU-Zeit für die notwendigen Berechnungen.
aRts interner Scheduler-Aufwand - wie aRts entscheidet, wann welches Modul was berechnen soll.
Aufwand zur Konvertierung von Ganzzahlen in Kommazahlen.
MCOP-Protokoll-Aufwand.
Kernel: Prozeß-/Kontextumschaltung.
Kernel: Kommunikationsaufwand.
Für die CPU-Berechnungszeit bei zwei gleichzeitig abgespielten Datenströmen muss man Additionen durchführen. Falls man einen Filter verwendet, sind einige weitere Berechnungen notwendig. Ein einfaches Beispiel mit zwei Strömen mit vier CPU-Zyklen pro Addition führen auf einem 350MHz-Prozessor zu 44100*2*4/350000000 = 0,1% CPU-Auslastung.
aRts internes Scheduling: aRts muss bestimmen, welches Plugin wann was berechnet. Das benötigt Zeit. Wenn man Genaueres wissen will, sollte man einen Profiler zu Rate ziehen. Generell lässt sich Folgendes sagen: je weniger Wert man auf Echtzeit legt (also je größer die zu berechnenden Blöcke sind) um so wenig Scheduling Zeit wird benötigt. Bei mehr als 128 Samples in einem Stück (also einer Bruchstückgröße von 512 Bytes) ist der Scheduling-Aufwand wahrscheinlich nicht einmal nennenswert.
Aufwand zur Konvertierung von Ganzzahlen in Kommazahlen: aRts verwendet intern Kommazahlen als Datenformat. Sie sind einfach zu verarbeiten und auf zeitgemäßen Prozessoren kaum langsamer als Ganzzahlen. Wenn aber Programme ihre Daten nicht in Kommazahlen abspielen (wie ein Spiel, das seine Känge über aRts ausgibt), müssen diese Daten konvertiert werden. Das Gleiche gilt für die Ausgabe auf einer Soundkarte. Soundkarten benötigen Ganzzahlen, also muss eine Konvertierung durchgeführt werden.
Die folgenden Zahlen gehören zu einem Celeron, ungefähre ticks pro Sample, mit -O2 +egcs 2.91.66 (berechnet von Eugene Smith (hamster AT null.ru)
). Diese Zahlen sind naturgemäß sehr prozessorabhängig.
convert_mono_8_float: 14 convert_stereo_i8_2float: 28 convert_mono_16le_float: 40 interpolate_mono_16le_float: 200 convert_stereo_i16le_2float: 80 convert_mono_float_16le: 80
Das bedeutet 1% CPU-Zeit für die Konvertierung und 5% für die Interpolation auf diesem 350 MHz-Prozessor.
MCOP Protokoll-Aufwand: MCOP verträgt ungefähr 9000 Aufrufe pro Sekunde. Das hat wenige mit MCOP als mit den zwei Kernelgründen zu tun, die weiter unten beschrieben werden. Dennoch gibt das einen Anhaltspunkt für die Berechnung der Kosten von Streaming.
Jedes übertragene Datenpaket erfordert einen MCOP-Aufruf. Natürlich ist das nur ein Richtwert, da große Pakete langsamer als 9000 Pakete/s sind.
Angenommen man verwendet als Paketgröße 1024 Bytes. Für einen Stream mit 44kHz Stereo muss man also 44100*4/1024 = 172 Pakete pro Sekunde übertragen. Weiter angenommen, 9000 Pakete pro Sekunde entspricht einer CPU-Auslastung von 100%, dann erhält man (172*100)/9000 = 2% CPU-Auslastung bei einem Streaming mit 1024 Bytes pro Paket.
Das sind nur Näherungen. Sie zeigen aber deutlich, dass man viel besser dran ist (wenn man die Verzögerungszeit tolerieren kann) mit einer Paketgröße von 4096 Bytes. Wir können eine kompakte Rechnung durchführen, wenn wir die Paketgröße für 100% CPU-Auslastung berechnen mit 44100*4/9000 = 19,6 Samples; daraus erhalten wir folgende Formel:
Streaming CPU-Auslastung in Prozent = 1960/(Paketgröße)
das ergibt 0,5% CPU-Auslastung bei Streaming mit einer Paketgröße von 4096 Bytes.
Kernel Prozeß-/Kontextumschaltung: dieser Aufwand ist ein Teil des MCOP-Protokolls. Die Umschaltung zwischen zwei Prozessen benötigt Zeit. Es wird der Speicher neu aufgeteilt, der Cache wird ungültig, und vieles mehr (falls ein Kernel-Experte dieses liest - teilen Sie mir bitte die genauen Gründe mit). Das bedeutet: es benötigt Zeit.
Ich weiß nicht wie viele Kontextwechsel Linux verträgt, aber sicher nur eine endliche Zahl. Daher nehme ich an, das ein erheblicher Teil des MCOP Protokollaufwandes durch Kontextumschaltung zu Stande kommt. Ich führte einige Tests mit MCOP-Kommunikation innerhalb eines Prozesses durch. Sie war wesentlich schneller (etwa viermal so schnell).
Kernel: Kommunikationsaufwand: Das ist ebenfalls ein Bestandteil des MCOP Protokollaufwandes. Die Datenübertragung zwischen Prozessen wird momentan mittels Sockets durchgeführt. Das ist praktisch, da die üblichen select()-Methoden verwendet werden können, um die Ankunft einer Nachricht zu überprüfen. Das kann leicht mit anderen Ein-/Ausgabequellen wie Audio Ein-/Ausgabe, X11-Server-Kommunikation und einigem Anderen kombiniert werden.
Diese Lese- und Schreibaufrufe kosten mit Sicherheit Prozessorzeit. Kleine Aufrufe (um ein Midi-Ereignis zu übertragen) sind vermutlich nicht so schlimm, große Aufrufe (z.B. um ein Videobild mit einigen Megabytes zu übertragen) stellen sicherlich ein Problem dar.
In MCOP sollte daher möglichst gemeinsam genutzter Speicher verwendet werden. Das sollte aber keine Änderungen für die Anwendungsprogrammierer mit sich bringen.
Durch einen Profiler kann man herausbekommen, in welchem Maße genau heutiges Audio Streaming unter der Nichtverwendung von gemeinsam genutztem Speicher leidet. Da Audio Streaming mit artsd und artscat mit 6% CPU-Auslastung (Wiedergabe von mp3) durchgeführt werden kann (und 5% für den mp3-Dekoder), kann es nicht so schlimm sein. Das enthält alles einschließlich der notwendigen Berechnungen und dem Socket-Aufwand, daher vermute ich, durch gemeinsam genutzten Speicher könnten vielleicht 1% CPU-Auslastung gespart werden.
Diese Zahlen wurden mit der aktuellen CVS-Version ermittelt. Außerdem wollte ich Grenzfälle testen, also sind das Fälle, die in normalen Anwendungen nicht auftreten sollten.
Ich habe ein Programm namens streamsound geschrieben, das Streaming Daten an aRts sendet. Die Anwendung läuft hier mit Echtzeit-Priorität (ohne Probleme) und einem kleinen Plugin (für Lauststärkeskalierung und Clipping) auf seiten des Servers:
4974 stefan 20 0 2360 2360 1784 S 0 17.7 1.8 0:21 artsd 5016 stefan 20 0 2208 2208 1684 S 0 7.2 1.7 0:02 streamsound 5002 stefan 20 0 2208 2208 1684 S 0 6.8 1.7 0:07 streamsound 4997 stefan 20 0 2208 2208 1684 S 0 6.6 1.7 0:07 streamsound
Das Streaming wird jeweils mit 3 Bruchstücken je 1024 Bytes (18 ms) durchgeführt. Drei Programme laufen gleichzeitig. Das scheint ein bisschen zu viel zu sein; mit einem Profiler kann man versuchen, den Aufwand zu verbessern.
Das ist ein nicht realistisches Testbeispiel. Als weiteren Test wurde versucht, die minimal mögliche Verzögerungszeit herauszufinden. Resultat: man kann Streaming ohne Unterbrechungen mit einem Anwendungsprogramm bei zwei Bruchstücken je 128 Bytes zwischen aRts und der Soundkarte sowie zwischen der Anwendung und aRts durchführen. Das entspricht einer maximalen Verzögerung von 128*4/44100*4 = 3 ms, wobei 1,5 ms von der Soundkarte und 1,5 ms durch die Kommunikation mit aRts entstehen. Beide Anwendungen benötigen Echtzeit-Priorität.
Das benötigt allerdings einen erheblichen Anteil der CPU. Das Beispiel liegt bei etwa 45% auf meinem P-II/350. Es wird ein Klicken hörbar, wenn man Fenster in X11 bewegt oder Festplattenaktivität verursacht. Das hängt mit dem Kernel zusammen. Das Problem ist, das zwei Echtzeitanwendungen einen erheblichen Aufwand verursachen, mehr noch, wenn Sie auch noch miteinander kommunizieren.
Schließlich ein realistischeres Beispiel. Es besteht aus aRts mit artsd und einem artscat (ein Streaming Programm) bei 16 Bruchstücken mit je 4096 Bytes:
5548 stefan 12 0 2364 2364 1752 R 0 4.9 1.8 0:03 artsd 5554 stefan 3 0 752 752 572 R 0 0.7 0.5 0:00 top 5550 stefan 2 0 2280 2280 1696 S 0 0.5 1.7 0:00 artscat
Busse sind dynamisch erzeugte Verbindungen zum Transport von Audiosignalen. Grundlegend werden alle Signale von Uplinks addiert und an die Downlinks weitergeleitet.
Busse existieren momentan nur in Stereo. Wenn Sie Mono-Daten transportieren wollen, senden Sie sie über einen Kanal und setzen Sie den anderen auf Null. Zur Verwendung erstellen Sie einen oder mehrere Synth_BUS_UPLINK-Module und benennen Sie sie mit dem Busnamen, auf dem Sie senden sollen (z.B. „audio“ oder „drums“). Dann senden Sie die Daten an diese Uplinks.
Auf der anderen Seite benötigen Sie einen oder mehrere Synth_BUS_DOWNLINK-Module, denen Sie die gleichen Busnamen geben (also „audio“ oder „drums“ ... gleiche Busnamen werden verbunden). Über diese Busenden erscheinen die gesendeten Signale wieder in einer Struktur.
Die Uplinks und Downlinks können zu unterschiedlichen Strukturen gehören. Sie können sogar zwei Instanzen von aRts-builder verwenden und in einer davon einen Uplink und in der Anderen den passenden Downlink haben.
Eine besondere Eigenschaft ist die Dynamik dieser Verbindungen. Teile können sich jederzeit ein- oder ausklinken; das sollte kein Klicken oder andere Geräusche verursachen.
Natürlich sollte kein Teil ausgeklinkt werden, während sein Signalpegel höher als Null ist, da sonst natürlich ein Klicken zu hören sein wird.
aRts/MCOP basiert sehr auf der Aufteilung von Aufgaben in kleine Komponenten. Das System wird dadurch sehr flexibel, da man das System auf einfache Weise durch das Hinzufügen neuer Komponenten, die neue Effekte, Dateiformate, Oszillatoren, GUI-Elemente, ... bereitstellen, erweitert werden kann. Da beinahe alles als Komponente programmiert ist, kann beinahe alles einfach erweitert werden, ohne die existierenden Quellen zu verändern. Neue Komponenten, die das System erweitern, werden einfach dynamisch geladen.
Für einen reibungslosen Ablauf sind zwei Dinge erforderlich:
Komponenten müssen sich bekannt machen - sie müssen ihre Funktionen beschreiben, damit andere Programme sie verwenden können.
Anwendungen müssen aktiv nach verwendbaren Komponenten suchen, anstatt immer die gleichen DInge für eine Aufgabe zu verwenden.
Die Kombination davon: Komponenten, die sagen: „Hier bin ich, verwende mich“, und Anwendungen (oder, wenn man so will, andere Komponenten), die nach verwendbaren Komponenten suchen, um eine Aufgabe zu erledigen, nennt man Handel.
In aRts beschreiben Komponenten sich selbst, indem sie Werte festlegen, die sie als Eigenschaften „unterstützen“. Eine typische Eigenschaft einer Komponente zum Laden einer Datei könnte die Dateinamenerweiterung sein, die sie verarbeiten kann. Typische Werte könnten wav
, aiff
oder mp3
sein.
Jede Komponente kann viele verschiedene Werte für eine Eigenschaft anbieten. Eine einzige Komponente kann somit sowohl das Einlesen von wav
als auch aiff
-Dateien anbieten, indem sie diese Werte für die Eigenschaft „Extension“ angibt.
Um das durchzuführen, muss die Komponente eine .mcopclass
-Datei an geeigneter Stelle platzieren, in der sie die unterstützten Eigenschaften festlegt. Eine solche Datei könnte folgendermaßen aussehen (und würde in
installiert): componentdir
/Arts/WavPlayObject.mcopclass
Interface=Arts::WavPlayObject,Arts::PlayObject,Arts::SynthModule,Arts::Object Author="Stefan Westerfeld <stefan@space.twc.de>" URL="http://www.arts-project.org" Extension=wav,aiff MimeType=audio/x-wav,audio/x-aiff
It is important that the filename of the .mcopclass
-file also says what the interface of the component is called like. The trader doesn't look at the contents at all, if the file (like here) is called Arts/WavPlayObject.mcopclass
, the component interface is called Arts::WavPlayObject
(modules map to directories).
To look for components, there are two interfaces (which are defined in core.idl
, so you have them in every application), called Arts::TraderQuery
and Arts::TraderOffer
. You to go on a „shopping tour“ for components like this:
Create a query object:
Arts::TraderQuery query;
Specify what you want. As you saw above, components describe themselves using properties, for which they offer certain values. So specifying what you want is done by selecting components that support a certain value for a property. This is done using the supports method of a TraderQuery:
query.supports("Interface","Arts::PlayObject"); query.supports("Extension","wav");
Finally, perform the query using the query method. Then, you'll (hopefully) get some offers:
vector<Arts::TraderOffer> *offers = query.query();
Now you can examine what you found. Important is the interfaceName method of TraderOffer, which will tell you the name of the component, that matched the query. You can also find out further properties by getProperty. The following code will simply iterate through all components, print their interface names (which could be used for creation), and delete the results of the query again:
vector<Arts::TraderOffer>::iterator i; for(i = offers->begin(); i != offers->end(); i++) cout << i->interfaceName() << endl; delete offers;
For this kind of trading service to be useful, it is important to somehow agree on what kinds of properties components should usually define. It is essential that more or less all components in a certain area use the same set of properties to describe themselves (and the same set of values where applicable), so that applications (or other components) will be able to find them.
Author (type string, optional): This can be used to ultimately let the world know that you wrote something. You can write anything you like in here, e-mail adress is of course helpful.
Buildable (type boolean, recommended): This indicates whether the component is usable with RAD tools (such as aRts-builder) which use components by assigning properties and connecting ports. It is recommended to set this value to true for almost any signal processing component (such as filters, effects, oscillators, ...), and for all other things which can be used in RAD like fashion, but not for internal stuff like for instance Arts::InterfaceRepo
.
Extension (type string, used where relevant): Everything dealing with files should consider using this. You should put the lowercase version of the file extension without the „.“ here, so something like wav
should be fine.
Interface (type string, required): This should include the full list of (useful) interfaces your components supports, probably including Arts::Object
and if applicable Arts::SynthModule
.
Language (type string, recommended): If you want your component to be dynamically loaded, you need to specify the language here. Currently, the only allowed value is C++
, which means the component was written using the normal C++ API. If you do so, you'll also need to set the „Library“ property below.
Library (type string, used where relevant): Components written in C++ can be dynamically loaded. To do so, you have to compile them into a dynamically loadable libtool (.la
) module. Here, you can specify the name of the .la
-File that contains your component. Remember to use REGISTER_IMPLEMENTATION (as always).
MimeType (type string, used where relevant): Everything dealing with files should consider using this. You should put the lowercase version of the standard mimetype here, for instance audio/x-wav
.
URL (type string, optional): If you like to let people know where they can find a new version of the component (or a homepage or anything), you can do it here. This should be standard HTTP or FTP URL.
Each namespace declaration corresponds to a „module“ declaration in the MCOP IDL.
// mcop idl module M { interface A { } }; interface B;
In this case, the generated C++ code for the IDL snippet would look like this:
// C++ header namespace M { /* declaration of A_base/A_skel/A_stub and similar */ class A { // Smartwrapped reference class /* [...] */ }; } /* declaration of B_base/B_skel/B_stub and similar */ class B { /* [...] */ };
So when referring the classes from the above example in your C++ code, you would have to write M::A
, but only B. However, you can of course use „using M“ somewhere - like with any namespace in C++.
There is one global namespace called „Arts“, which all programs and libraries that belong to aRts itself use to put their declarations in. This means, that when writing C++ code that depends on aRts, you normally have to prefix every class you use with Arts::
, like this:
int main(int argc, char **argv) { Arts::Dispatcher dispatcher; Arts::SimpleSoundServer server(Arts::Reference("global:Arts_SimpleSoundServer")); server.play("/var/foo/somefile.wav");
The other alternative is to write a using once, like this:
using namespace Arts; int main(int argc, char **argv) { Dispatcher dispatcher; SimpleSoundServer server(Reference("global:Arts_SimpleSoundServer")); server.play("/var/foo/somefile.wav"); [...]
In IDL files, you don't exactly have a choice. If you are writing code that belongs to aRts itself, you'll have to put it into module aRts.
// IDL File for aRts code: #include <artsflow.idl> module Arts { // put it into the Arts namespace interface Synth_TWEAK : SynthModule { in audio stream invalue; out audio stream outvalue; attribute float tweakFactor; }; };
If you write code that doesn't belong to aRts itself, you should not put it into the „Arts“ namespace. However, you can make an own namespace if you like. In any case, you'll have to prefix classes you use from aRts.
// IDL File for code which doesn't belong to aRts: #include <artsflow.idl> // either write without module declaration, then the generated classes will // not use a namespace: interface Synth_TWEAK2 : Arts::SynthModule { in audio stream invalue; out audio stream outvalue; attribute float tweakFactor; }; // however, you can also choose your own namespace, if you like, so if you // write an application "PowerRadio", you could for instance do it like this: module PowerRadio { struct Station { string name; float frequency; }; interface Tuner : Arts::SynthModule { attribute Station station; // no need to prefix Station, same module out audio stream left, right; }; };
Often, in interfaces, casts, method signatures and similar, MCOP needs to refer to names of types or interfaces. These are represented as string in the common MCOP datastructures, while the namespace is always fully represented in the C++ style. This means the strings would contain „M::A“ and „B“, following the example above.
Note this even applies if inside the IDL text the namespace qualifiers were not given, since the context made clear which namespace the interface A
was meant to be used in.
Using threads isn't possible on all platforms. This is why aRts was originally written without using threading at all. For almost all problems, for each threaded solution to the problem, there is a non-threaded solution that does the same.
For instance, instead of putting audio output in a seperate thread, and make it blocking, aRts uses non-blocking audio output, and figures out when to write the next chunk of data using select()
.
However, aRts (in very recent versions) at least provides support for people who do want to implement their objects using threads. For instance, if you already have code for an mp3
player, and the code expects the mp3
decoder to run in a seperate thread, it's usally the easiest thing to do to keep this design.
The aRts/MCOP implementation is built along sharing state between seperate objects in obvious and non-obvious ways. A small list of shared state includes:
The Dispatcher object which does MCOP communication.
The Reference counting (Smartwrappers).
The IOManager which does timer and fd watches.
The ObjectManager which creates objects and dynamically loads plugins.
The FlowSystem which calls calculateBlock in the appropriate situations.
All of the above objects don't expect to be used concurrently (d. h. called from seperate threads at the same time). Generally there are two ways of solving this:
Require the caller of any functions on this objects to acquire a lock before using them.
Making these objects really threadsafe and/or create per-thread instances of them.
aRts follows the first approach: you will need a lock whenever you talk to any of these objects. The second approach is harder to do. A hack which tries to achieve this is available at http://space.twc.de/~stefan/kde/download/arts-mt.tar.gz, but for the current point in time, a minimalistic approach will probably work better, and cause less problems with existing applications.
You can get/release the lock with the two functions:
Generally, you don't need to acquire the lock (and you shouldn't try to do so), if it is already held. A list of conditions when this is the case is:
You receive a callback from the IOManager (timer or fd).
You get call due to some MCOP request.
You are called from the NotificationManager.
You are called from the FlowSystem (calculateBlock)
There are also some exceptions of functions. which you can only call in the main thread, and for that reason you will never need a lock to call them:
Constructor/destructor of Dispatcher/IOManager.
Dispatcher::run()
/ IOManager::run()
IOManager::processOneEvent()
But that is it. For everything else that is somehow related to aRts, you will need to get the lock, and release it again when done. Always. Here is a simple example:
class SuspendTimeThread : Arts::Thread { public: void run() { /* * you need this lock because: * - constructing a reference needs a lock (as global: will go to * the object manager, which might in turn need the GlobalComm * object to look up where to connect to) * - assigning a smartwrapper needs a lock * - constructing an object from reference needs a lock (because it * might need to connect a server) */ Arts::Dispatcher::lock(); Arts::SoundServer server = Arts::Reference("global:Arts_SoundServer"); Arts::Dispatcher::unlock(); for(;;) { /* * you need a lock here, because * - dereferencing a smartwrapper needs a lock (because it might * do lazy creation) * - doing an MCOP invocation needs a lock */ Arts::Dispatcher::lock(); long seconds = server.secondsUntilSuspend(); Arts::Dispatcher::unlock(); printf("seconds until suspend = %d",seconds); sleep(1); } } }
The following threading related classes are currently available:
Arts::Thread
- which encapsulates a thread.
Arts::Mutex
- which encapsulates a mutex.
Arts::ThreadCondition
- which provides support to wake up threads which are waiting for a certain condition to become true.
Arts::SystemThreads
- which encapsulates the operating system threading layer (which offers a few helpful functions to application programmers).
See the links for documentation.
MCOP references are one of the most central concepts in MCOP programming. This section will try to describe how exactly references are used, and will especially also try to cover cases of failure (server crashes).
An MCOP reference is not an object, but a reference to an object: Even though the following declaration
Arts::Synth_PLAY p;looks like a definition of an object, it only declares a reference to an object. As C++ programmer, you might also think of it as Synth_PLAY *, a kind of pointer to a Synth_PLAY object. This especially means, that p can be the same thing as a NULL pointer.
You can create a NULL reference by assigning it explicitly
Arts::Synth_PLAY p = Arts::Synth_PLAY::null();
Invoking things on a NULL reference leads to a core dump
Arts::Synth_PLAY p = Arts::Synth_PLAY::null(); string s = p.toString();
will lead to a core dump. Comparing this to a pointer, it is essentially the same as
QWindow* w = 0; w->show();which every C++ programmer would know to avoid.
Uninitialized objects try to lazy-create themselves upon first use
Arts::Synth_PLAY p; string s = p.toString();
is something different than dereferencing a NULL pointer. You didn't tell the object at all what it is, and now you try to use it. The guess here is that you want to have a new local instance of a Arts::Synth_PLAY object. Of course you might have wanted something else (like creating the object somewhere else, or using an existing remote object). However, it is a convenient short cut to creating objects. Lazy creation will not work once you assigned something else (like a null reference).
The equivalent C++ terms would be
QWidget* w; w->show();which obviously in C++ just plain segfaults. So this is different here. This lazy creation is tricky especially as not necessarily an implementation exists for your interface.
For instance, consider an abstract thing like a Arts::PlayObject. There are certainly concrete PlayObjects like those for playing mp3s or wavs, but
Arts::PlayObject po; po.play();will certainly fail. The problem is that although lazy creation kicks in, and tries to create a PlayObject, it fails, because there are only things like Arts::WavPlayObject and similar. Thus, use lazy creation only when you are sure that an implementation exists.
References may point to the same object
Arts::SimpleSoundServer s = Arts::Reference("global:Arts_SimpleSoundServer"); Arts::SimpleSoundServer s2 = s;
creates two references referring to the same object. It doesn't copy any value, and doesn't create two objects.
All objects are reference counted So once an object isn't referred any longer by any references, it gets deleted. There is no way to explicitely delete an object, however, you can use something like this
Arts::Synth_PLAY p; p.start(); [...] p = Arts::Synth_PLAY::null();to make the Synth_PLAY object go away in the end. Especially, it should never be necessary to use new and delete in conjunction with references.
As references can point to remote objects, the servers containing these objects can crash. What happens then?
A crash doesn't change whether a reference is a null reference. This means that if foo.isNull()
was true before a server crash then it is also true after a server crash (which is clear). It also means that if foo.isNull()
was false before a server crash (foo referred to an object) then it is also false after the server crash.
Invoking methods on a valid reference stays safe Suppose the server containing the object calc crashed. Still calling things like
int k = calc.subtract(i,j)are safe. Obviously subtract has to return something here, which it can't because the remote object no longer exists. In this case (k == 0) would be true. Generally, operations try to return something „neutral“ as result, such as 0.0, a null reference for objects or empty strings, when the object no longer exists.
Checking error()
reveals whether something worked.
In the above case,
int k = calc.subtract(i,j) if(k.error()) { printf("k is not i-j!\n"); }would print out
k is not i-j
whenever the remote invocation didn't work. Otherwise k
is really the result of the subtract operation as performed by the remote object (no server crash). However, for methods doing things like deleting a file, you can't know for sure whether it really happened. Of course it happened if .error()
is false. However, if .error()
is true, there are two possibilities: The file got deleted, and the server crashed just after deleting it, but before transferring the result.
The server crashed before beeing able to delete the file.
Using nested invocations is dangerous in crash resistent programs
Using something like
window.titlebar().setTitle("foo");is not a good idea. Suppose you know that window contains a valid Window reference. Suppose you know that
window.titlebar()
will return a Titlebar reference because the Window object is implemented properly. However, still the above statement isn't safe. What could happen is that the server containing the Window object has crashed. Then, regardless of how good the Window implementation is, you will get a null reference as result of the window.titlebar() operation. And then of course invoking setTitle on that null reference will lead to a crash as well.
So a safe variant of this would be
Titlebar titlebar = window.titlebar(); if(!window.error()) titlebar.setTitle("foo");add the appropriate error handling if you like. If you don't trust the Window implementation, you might as well use
Titlebar titlebar = window.titlebar(); if(!titlebar.isNull()) titlebar.setTitle("foo");which are both safe.
There are other conditions of failure, such as network disconnection (suppose you remove the cable between your server and client while your application runs). However their effect is the same like a server crash.
Overall, it is of course a consideration of policy how strictly you try to trap communcation errors throughout your application. You might follow the „if the server crashes, we need to debug the server until it never crashes again“ method, which would mean you need not bother about all these problems.
An object, to exist, must be owned by someone. If it isn't, it will cease to exist (more or less) immediately. Internally, ownership is indicated by calling _copy()
, which increments an reference count, and given back by calling _release()
. As soon as the reference count drops to zero, a delete will be done.
As a variation of the theme, remote usage is indicated by _useRemote()
, and dissolved by _releaseRemote()
. These functions lead a list which server has invoked them (and thus owns the object). This is used in case this server disconnects (d. h. crash, network failure), to remove the references that are still on the objects. This is done in _disconnectRemote()
.
Now there is one problem. Consider a return value. Usually, the return value object will not be owned by the calling function any longer. It will however also not be owned by the caller, until the message holding the object is received. So there is a time of „ownershipless“ objects.
Now, when sending an object, one can be reasonable sure that as soon as it is received, it will be owned by somebody again, unless, again, the receiver dies. However this means that special care needs to be taken about object at least while sending, probably also while receiving, so that it doesn't die at once.
The way MCOP does this is by „tagging“ objects that are in process of being copied across the wire. Before such a copy is started, _copyRemote
is called. This prevents the object from being freed for a while (5 seconds). Once the receiver calls _useRemote()
, the tag is removed again. So all objects that are send over wire are tagged before transfer.
If the receiver receives an object which is on his server, of course he will not _useRemote()
it. For this special case, _cancelCopyRemote()
exists to remove the tag manually. Other than that, there is also timer based tag removal, if tagging was done, but the receiver didn't really get the object (due to crash, network failure). This is done by the ReferenceClean
class.
GUI-Elemente sind augenblicklich in einem experimentellen Stadium. Dieser Abschnitt beschreibt also, wie aRts später einmal mit GUI-Elementen umgehen soll. Außerdem ist ein gewisser Teil an Programmzeilen bereits vorhanden.
GUI-Elemente dienen der Interaktion eines Benutzers mit synthetisierten Strukturen. Im einfachsten Fall soll der Benutzer in der Lage sein, einige Parameter der Struktur direkt zu verändern (z.B. einen Verstärkungsfaktor, der vor dem Abspielmodul verwendet wird).
In einem komplexeren Fall wäre vorstellbar, das ein Benutzer Parameter einer ganzen Gruppe von Strukturen oder noch nicht ausgeführten Strukturen ändert, wie z.B. die ADSR-Hüllkurve (envelope) des aktiven MIDI-Instrumentes. Eine andere denkbare Einstellung wäre der Dateiname eines Instrumentes, das auf einem Sample basiert.
Auf der anderen Seite könnte der Benutzer überwachen wollen, was innerhalb des Synthesizers passiert. Dazu könnte es Oszilloskope, Spektrumanalysatoren, Lautstärkeanzeigen und „Experimente“ geben, um z.B. in der Lage zu sein, den Frequenzgang eines bestimmten Filtermodules zu analysieren.
Schließlich sollte es möglich sein, mit GUI-Elementen die gesamte Struktur zu kontrollieren, die in aRts ausgeführt wird. Der Benutzer sollte in der Lage sein, Instrumente MIDI-Kanälen zuzuordnen, neue Effekte zu starten und zu seinem Hauptmischpult (das ebenfalls aus aRts-Strukturen besteht) einen weiteren Kanal hinzuzufügen und eine neue Strategie für seine Equalizer einzustellen.
Die GUI-Elemente bieten dem Benutzer damit Zugriff auf alle Möglichkeiten, die das virtuelle Studio aRts bietet. Natürlich sollen die GUI-Elemente auch mit MIDI-Eingaben interagieren können (ein Schieberegler soll sich selbstständig in eine neue Position bewegen, wenn ein Midi-Ereignis gerade diesen Parameter geändert hat), weiterhin sollen die Elemente selbst Ereignisse generieren können, um die Aufnahme der Benutzereingaben zu ermöglichen.
Technisch gesprochen benötigt man eine IDL-Basisklasse für alle Kontrollelemente (Arts::Widget
) und kann von dieser eine Anzahl häufig verwendeter Kontrollelemente (wie Arts::Poti
, Arts::Panel
, Arts::Window
, ...) ableiten.
Diese Kontrollelemente sollten mit einer Bibliothek wie Qt™ oder Gtk implementiert werden. Schließlich sollten die GUIs der Effekte aus diesen Elementen gebildet werden. Ein Freeverb-Effekt könnte z.B. aus fünf Arts::Poti
- und einem Arts::Window
-Element gebildet werden. Wenn es also eine Qt™-Implementation dieser grundlegenden Elemente gibt, kann der Effekt sich mit Hilfe von Qt™ darstellen. Wenn es eine Gtk-Implementation gibt, funktioniert das für Gtk ebenfalls (die Funktionsweise sollte mehr oder weniger gleichwertig sein).
Da wir IDL benutzen, sollte aRts-builder (oder andere Programme) in der Lage sein, GUIs optisch zusammenzuführen oder mit einigen Parametern basierend auf den Schnittstellen selbst zusammenzustellen. Es sollte relativ einfach sein, eine „GUI nach Beschreibung erstellen“-Klasse zu programmieren, die eine GUI-Beschreibung (in der Form Parameter und Kontrollelement) in ein GUI-Objekt umsetzt.
Basierend auf IDL und dem aRts/MCOP-Komponentenmodell sollte es genau so leicht sein, ein neues GUI-Objekt zu erstellen, wie es ist, ein neues Plugin für aRts zu schreiben, das einen weiteren Filter bereitstellt.
Die MIDI-Unterstützung in aRts hat verschiedene Aufgaben. Erstens ermöglicht sie die Kommunikation von verschiedenen Programmteilen, die MIDI-Ereignisse erzeugen oder verarbeiten. Wenn Sie z.B. über einen Sequenzer und einen Sampler verfügen, die beide aRts unterstützen, kann aRts MIDI-Ereignisse vom Sequenzer zum Sampler senden.
Auf der anderen Seite kann aRts für ein Programm die Interaktion mit Geräten übernehmen. Wenn ein Programm (z.B. ein Sampler) aRts unterstützt, kann es genau so gut MIDI-Ereignisse von einem externen MIDI-Keyboard empfangen.
Schließlich ist aRts ein hervorragender modularer Synthesizer. Er ist genau dafür entworfen worden. Sie können mit artsbuilder aus den kleinen Modulen Instrumente zusammenstellen und diese Instrumente dann für Kompositionen oder zum Abspielen von Musik verwenden. Synthese heißt nicht notwendigerweise reine Synthese, es gibt Module, die Sie zum Abspielen von Samples verwenden können. Also kann aRts ein Sampler, ein Synthesizer und mehr sein; aRts ist vollständig modular, also leicht zum Erweitern und Experimentieren geeignet, mächtig und flexibel.
Die zentrale Komponente für Midi-Ereignisse innerhalb von aRts ist der Midi-Manager. Er kontrolliert, welche Anwendungen verbunden sind und wie Midi-Ereignisse zwischen ihnen übertragen und verarbeitet werden sollen. Der Midi-Manager wird durch artscontrol gesteuert. Wählen Sie dazu Ansicht+Midi-Manager im Menü.
Auf der linken Seite sehen Sieh Midi-Eingänge. Hier werden alle Geräte aufgelistet, die MIDI-Ereignisse produzieren. Das können externe MIDI-Kanäle sein, die mit einem externen Keyboard verbunden sind, ein Sequenzer, der ein Musikstück abspielt, oder eine andere MIDI-Quelle. Auf der rechten Seite sehen Sie MIDI-Ausgänge. Alle Geräte oder Programme, die MIDI-Ereignisse verarbeiten, sind hier aufgelistet. Das können simulierte Sampler (als Programme) oder externe MIDI-Kanäle, an denen ein Hardware-Sampler angeschlossen ist, sein. Neue Programme, wie z.B. Sequenzer, registrieren sich bei dem Midi-Manager, daher kann sich der Listeninhalt von Zeit zu Zeit ändern.
Sie können Ein- und Ausgänge verbinden, indem Sie den gewünschten Eingang auf der linken und den Ausgang auf der rechten Seite markieren und auf Verbinden klicken. Eine Trennung erreichen Sie mit dem Knopf Trennen. Die bestehenden Verbindungen werden durch günne Linien zwischen den Listen angezeigt. Sie können einen Midi-Eingang mit mehreren Midi-Ausgängen verbinden und umgekehrt.
Programme (wie der Sequenzer Brahms) fügen sich beim Start selbstständig zur entsprechenden Liste hinzu und entfernen sich beim Beenden selbstständig. Sie können aber auch von Hand Einträge hinzufügen, indem Sie im Menü Hinzufügen wählen.
Dadurch wird ein neues aRts-Objekt erzeugt, das einen externen Midi-Kanal repräsentiert.
Da externe Midi-Kanäle sowohl senden als auch empfangen können, wird zu beiden Listen ein Eintrag hinzugefügt. Unter Linux® benötigen Sie entweder einen OSS- (OSS/Free liegt Ihrem Linux®-Kernel bei) oder einen ALSA-Treiber für Ihre Soundkarte installiert haben. Sie werden nach dem Gerätenamen gefragt. Er lautet normalerweise /dev/midi
oder /dev/midi00
.
Wenn Sie allerdings mehr als ein MIDI-Gerät oder einen MIDI-Loopback-Treiber installiert haben, ist die Auswahl größer. Informationen über die verfügbaren Midi-Kanäle finden Sie im KDE-Kontrollzentrum unter Information+Klänge.
Fügt einen neuen MIDI-Ausgang mit einem aRts-Synthese-Instrument hinzu. Wenn Sie diesen Menüeintrag wählen, erscheint ein Dialog, der Ihnen die Wahl eines Instrumentes ermöglicht. Mit artsbuilder können Sie neue Instrumente erstellen. Alle .arts
-Dateien, die mit instrument_
beginnen, werden hier aufgelistet.
Zuerst benötigen Sie eine KDE 2.1-taugliche Version von Brahms. Sie finden Sie im kmusic
CVS-Modul. Weitere Informationen zu Brahms finden Sie auf der aRts-Internetseite im Bereich Download.
Wenn man die Anwendung startet, wird zunächst der MIDI-Manager angezeigt. Wenn man Klänge synthetisieren will, fügt man ein MIDI-Synthesizerinstrument über Hinzufügen+aRts Synthese Midi-Ausgang hinzu.
Man wählt ein Instrument (zum Beispiel organ2). Man verbindet das Instrument über den Knopf Verbinden. Danach kann man mit Brahms komponieren und die Ausgabe wird durch aRts synthetisiert.
Man sollte das artscontrol-Fenster geöffnet lassen und die Lautstärkeeinstellung kontrollieren (die Qualität wird schlecht, wenn die Lautstärkeanzeige an die obere Grenze stösst). Nun kann man einneues Demolied für aRts erstellen und es, wenn es fertig ist, unter aRts-project.org veröffentlichen ;-).
midisend ist ein kleines Programm, das MIDI-Ereignisse von der Kommandozeile senden kann. Es registriert sich als MIDI-Eingang wie alle anderen Anwendungen. Um es zu verwenden, geben Sie
ein. Damit wird etwa das gleiche erreicht, wie durch das Hinzufügen eines System-Midikanals in artscontrol (nicht ganz, da midisend nur MIDI-Ereignisse senden aber nicht empfangen kann). Der Unterschied ist, das midisend auf unterschiedlichen Computern gestartet werden kann (und damit Netzwerktransparenz ermöglicht).%
midisend
-f
/dev/midi00
Sie können midisend auch Daten von stdin
senden lassen. Mit dem folgenden Befehl können sie Daten von Anwendungen, die aRts nicht unterstützen, an aRts weiterleiten:
%
applicationwhichproducesmidieventsonstdout
| midisend-f
-
aRts synthetisiert Midi-Klänge auf folgende Weise. Es gibt eine Struktur, die einige Eingabekanäle, aus denen Frequenz und Lautstärke (velocity) und ein Parameter gelesen wird. Der Parameter zeigt an, ob die Taste noch heruntergedrückt ist. Die Struktur soll nun diese eine Note mit dieser Lautstärke und Frequenz erzeugen. Außerdem soll die Struktur auf den Wert des Parameters gedrückt reagieren (dabei bedeutet gedrückt=1 der Benutzer drückt die Taste immer noch herunter und gedrückt=0 die Taste wurde losgelassen).
Eingehende MIDI-Ereignisse veranlassen aRts, neue Strukturen für jede gedrückte Taste zu generieren, ihnen die entsprechenden Parameter mitzugeben und sie wieder zu entfernen, sobald sie nicht mehr benötigt werden.
Eine Struktur wird auf folgende Art erzeugt und verwendet:
Zum Beginn ist es am einfachsten, ein template_Instrument.arts
in aRts-builder zu öffnen.
Wählen Sie dazu -> im Menü und im Dateidialog wählen Sie template_Instrument. Damit haben Sie eine leere Struktur mit den erforderlichen Parametern erzeugt, die Sie nur noch "ausfüllen" müssen.
Für den Parameter pressed(gedrückt) benutzen Sie entweder Synth_ENVELOPE_ADSR oder, für eine Schlagzeug wav-Datei, spielen sie einfach ab und ignorieren den Parameter.
Die Struktur sollte am Ausgang „done“ anzeigen, das Sie nicht mehr benötigt wird. Wenn doneauf 1 gesetzt wird, nimmt aRts an, das er die Struktur löschen kann. Angenehmerweise stellt das ADSR-Hüllkurven-Modul einen Parameter bereit, der anzeigt, wenn das Modul fertig ist. Sie müssen diesen Ausgang lediglich mit dem done-Ausgang Ihrer Struktur verbinden.
Sie sollten die Struktur in die Form instrument_*
wie z.B. instrument_piano.arts
umbenennen - unter diesem Namen sollten Sie die Struktur unter $
speichern (dort speichert artsbuilder normalerweise Strukturen). HOME
/arts/structures
Schließlich, nachdem die Struktur gespeichert ist, können Sie sie im MIDI-Manager von artscontrol verwenden.
Und Sie müssen natürlich die Struktur so einrichten, das Sie ihre Audiodaten an den linken und rechten Ausgangskanal sendet, so dass sie schließlich durch den Audio-Manager (ein Teil von artscontrol) hörbar werden (oder sie mit Effekten weiterverarbeiten).
Um zu lernen, wie Sie selbst ein Instrument erstellen, können Sie ein bereits vorhandenes Instrument analysieren (wählen Sie im Menü ->).
Gemappte Instrumente sind Instrumente, die sich abhängig von der Tonhöhe (pitch), dem Programm, dem Kanal und der Lautstärke (velocity) unterschiedlich verhalten. Sie könnten z.B. einen Klavierklang von 5 Oktaven bilden, indem Sie ein Sample für jede Oktave verwenden (entsprechende Höhenverschiebungen (pitchshifting) vorausgesetzt). Dieser Klang wird besser klingen als ein Sample für alle Oktaven.
Sie können auch eine Schlagzeug-Map bilden, die jeder Taste ein bestimmtes Schlaginstrument zuordnet.
Es ist sehr nützlich, wenn Sie einige unterschiedliche Klänge in ein gemapptes Instrument für verschiedene Programme zusammenfügen. Auf diese Weise können Sie Ihren Sequenzer, externes Keyboard oder andere MIDI-Quelle verwenden, um zwischen den Klängen umzuschalten, ohne das Sie aRts umändern müssen.
Ein gutes Beispiel dafür ist das Instrument arts_all
. Es fügt alle Instrumente in einer Map zusammen. Auf diese Weise müssen Sie lediglich einmal in artscontrol dieses „instrument“ registrieren und schon können Sie ein komplettes Musikstück in einem Sequenzer komponieren, ohne aRts umzuschalten. Wenn Sie einen anderen Klang benötigen, wechseln Sie einfach im Sequenzer das Programm und aRts erzeugt einen anderen Klang.
Solche Maps können Sie auf einfache Weise erstellen. Sie müssen lediglich eine Textdatei anlegen, die einige Regeln enthält:
ON[ Bedingungen ...]
DO structure=irgendeine Struktur
.arts
Die Bedingungen können eine oder mehrere der folgenden sein:
pitch (Tonhöhe)
Beim pitch handelt es sich um die gespielte Tonhöhe. Diese Bedingung verwenden Sie, wenn Sie den Instrumentenklang abhängig von der Tonhöhe aufteilen wollen. Von den Anfangsbeispielen würde ein Piano, das verschiedene Klänge für verschiedene Oktaven verwendet, mit einer solchen Bedingung erstellt. Sie können eine bestimmte Tonhöhe angeben, wie z.B. pitch=62
oder einen Bereich wie pitch=60
-72
. Die möglichen Tonhöhen liegen zwischen 0
und 127
.
program (Programm)
Das Programm, das auf dem Midi-Kanal aktiv ist, auf dem die Note gesendet wird. Üblicherweise kann man bei einem Sequenzer das „Instrument“ über die Programmeinstellung auswählen. Einzelne Programme oder Bereiche sind erlaubt, also
oder program
=3
. Die möglichen Programmeinstellungen reichen von program
=3
-6
0
bis 127
.
channel (Kanal)
Der Kanal, auf dem die Note gesendet wird. Einzelne Kanäle oder Kanalbereiche sind möglich, z.B.
oder channel
=0
. Die möglichen Werte liegen zwischen channel
=0
-8
0
und 15
.
velocity (Lautstärke)
Die Lautstärke, die die Note hat. Einzelne Lautstärken (wofür eigentlich) oder Bereiche sind möglich, also
oder velocity
=127
. Die möglichen Werte liegen zwischen veclocity
=64
-127
0
und 127
.
Ein komplettes Beispiel für eine Map sieht folgendermaßen aus (das Beispiel stammt aus instrument_arts_all.arts-map
):
ON program=0 DO structure=instrument_tri.arts ON program=1 DO structure=instrument_organ2.arts ON program=2 DO structure=instrument_slide1.arts ON program=3 DO structure=instrument_square.arts ON program=4 DO structure=instrument_neworgan.arts ON program=5 DO structure=instrument_nokind.arts ON program=6 DO structure=instrument_full_square.arts ON program=7 DO structure=instrument_simple_sin.arts ON program=8 DO structure=instrument_simple_square.arts ON program=9 DO structure=instrument_simple_tri.arts ON program=10 DO structure=instrument_slide.arts ON program=11 pitch=60 DO structure=instrument_deepdrum.arts ON program=11 pitch=61 DO structure=instrument_chirpdrum.arts
Wie Sie sehen, wird die Struktur abhängig vom Programm ausgewählt, beispielsweise sehen Sie als Programm 11 eine „Schlagzeug-Map“ (mit zwei Einträgen), die eine „tiefe Trommel“ auf C-5 (pitch=60) und eine „Snare-Trommel“ auf C#-5 (pitch=61) spielt.
Um Map-Dateien in artscontrol als Instrumente zur Wahl erscheinen zu lassen, müssen Sie nach der Konvention instrument_
benannt werden und sich entweder unter Ihrem Persönlichen Verzeichnis in irgendetwas
.arts-map$
oder im KDE-Verzeichnis unter HOME
/arts/structures$
befinden. Strukturen, die von dieser Map verwendet werden, können mit einem absoluten Pfad oder relativ zur Position der Map-Datei angegeben werden. KDEDIR
/share/apps/artsbuilder/examples
Es ist eine gute Ide, die arts_all.map zu erweitern oder vielleicht sogar eine General-MIDI-Map für aRts zu erstellen. Das würde die Verwendung von aRts vereinfachen. Bitte denken Sie darüber nach, ob Sie nicht interessante Instrumente für zukünftige Versionen von aRts zur Verfügung stellen können.
MCOP ist der Standard, den aRts für folgende Aufgaben verwendet:
Kommunikation zwischen Objekten.
Netzwerk-Transparenz.
Beschreibung von Objekt-Schnittstellen.
Programmiersprachenunabhängigkeit.
Ein wichtiger Aspekt von MCOP ist die Interface Description Language , IDL, in der viele der Schnittstellen (Interfaces) und APIs von aRts sprachunabhängig definiert worden sind.
IDL-Schnittstellen werden durch den IDL-Ãbersetzer in C++-Quelltexte übersetzt. Wenn Sie eine Schnittstelle implementieren, verwenden Sie als Grundlage das Klassenskelett, das der IDL-Ãbersetzer erstellt hat. Wenn Sie eine Schnittstelle verwenden, benutzen Sie einen Wrapper. Auf diese Weise kann MCOP ein Protokoll verwenden, wenn das Objekt, das Sie verwenden nicht lokal ist - Sie haben also volle Netzwerk-Transparenz.
Dieses Kapitel soll die grundlegenden Möglichkeiten des Objektmodells beschreiben, das durch die Verwendung von MCOP, das Protokoll selbst und seine Verwendung in C++ (Sprachbindung) entsteht. Dieses Kapitel wird nicht übersetzt, da zur Programmierung ohnehin englische Sprachkenntnisse unabdingbar sind.
Many of the services provided by aRts, such as modules and the sound server, are defined in terms of interfaces. Interfaces are specified in a programming language independent format: IDL.
This allows many of the implementation details such as the format of multimedia data streams, network transparency, and programming language dependencies, to be hidden from the specification for the interface. A tool, mcopidl, translates the interface definition into a specific programming language (currently only C++ is supported).
The tool generates a skeleton class with all of the boilerplate code and base functionality. You derive from that class to implement the features you want.
The IDL used by aRts is similar to that used by CORBA and DCOM.
IDL files can contain:
C-style #include directives for other IDL files.
Definitions of enumerated and struct types, as in C/C++.
Definitions of interfaces.
In IDL, interfaces are defined much like a C++ class or C struct, albeit with some restrictions. Like C++, interfaces can subclass other interfaces using inheritance. Interface definitions can include three things: streams, attributes, and methods.
Streams define multimedia data, one of the most important components of a module. Streams are defined in the following format:
[ async ] in|out [ multi ] type
stream name
[ , name
] ;
Streams have a defined direction in reference to the module, as indicated by the required qualifiers in or out. The type argument defines the type of data, which can be any of the types described later for attributes (not all are currently supported). Many modules use the stream type audio, which is an alias for float since that is the internal data format used for audio stream. Multiple streams of the same type can defined in the same definition uisng comma separated names.
Streams are by default synchronous, which means they are continuous flows of data at a constant rate, such as PCM audio. The async qualifier specifies an asynchronous stream, which is used for non-continuous data flows. The most common example of an async stream is MIDI messages.
The multi keyword, only valid for input streams, indicates that the interface supports a variable number of inputs. This is useful for implementing devices such as mixers that can accept any number of input streams.
Attributes are data associated with an instance of an interface. They are declared like member variables in C++, and can can use any of the primitive types boolean, byte, long, string, or float. You can also use user-defined struct or enum types as well as variable sized sequences using the syntax sequence<type>. Attributes can optionally be marked readonly.
As in C++, methods can be defined in interfaces. The method parameters are restricted to the same types as attributes. The keyword oneway indicates a method which returns immediately and is executed asynchronously.
Several standard module interfaces are already defined for you in aRts, such as StereoEffect
, and SimpleSoundServer
.
A simple example of a module taken from aRts is the constant delay module, found in the file kdemultimedia/arts/modules/artsmodules.idl
. The interface definition is listed below.
interface Synth_CDELAY : SynthModule { attribute float time; in audio stream invalue; out audio stream outvalue; };
This modules inherits from SynthModule
. That interface, defined in artsflow.idl
, defines the standard methods implemented in all music synthesizer modules.
The CDELAY effect delays a stereo audio stream by the time value specified as a floating point parameter. The interface definition has an attribute of type float to store the delay value. It defines two input audio streams and two output audio streams (typical of stereo effects). No methods are required other than those it inherits.
This section covers some additional topics related to streams.
There are various requirements for how a module can do streaming. To illustrate this, consider these examples:
Scaling a signal by a factor of two.
Performing sample frequency conversion.
Decompressing a run-length encoded signal.
Reading MIDI events from /dev/midi00
and inserting them into a stream.
The first case is the simplest: upon receiving 200 samples of input the module produces 200 samples of output. It only produces output when it gets input.
The second case produces different numbers of output samples when given 200 input samples. It depends what conversion is performed, but the number is known in advance.
The third case is even worse. From the outset you cannot even guess how much data 200 input bytes will generate (probably a lot more than 200 bytes, but...).
The last case is a module which becomes active by itself, and sometimes produces data.
In aRtss-0.3.4, only streams of the first type were handled, and most things worked nicely. This is probably what you need most when writing modules that process audio. The problem with the other, more complex types of streaming, is that they are hard to program, and that you don't need the features most of the time. That is why we do this with two different stream types: synchronous and asynchronous.
Synchronous streams have these characteristics:
Modules must be able to calculate data of any length, given enough input.
All streams have the same sampling rate.
The calculateBlock()
function will be called when enough data is available, and the module can rely on the pointers pointing to data.
There is no allocation and deallocation to be done.
Asynchronous streams, on the other hand, have this behaviour:
Modules may produce data sometimes, or with varying sampling rate, or only if they have input from some filed escriptor. They are not bound by the rule „must be able to satisfy requests of any size“.
Asynchronous streams of a module may have entirely different sampling rates.
Outgoing streams: there are explicit functions to allocate packets, to send packets - and an optional polling mechanism that will tell you when you should create some more data.
Incoming streams: you get a call when you receive a new packet - you have to say when you are through with processing all data of that packet, which must not happen at once (you can say that anytime later, and if everybody has processed a packet, it will be freed/reused)
When you declare streams, you use the keyword „async“ to indicate you want to make an asynchronous stream. So, for instance, assume you want to convert an asynchronous stream of bytes into a synchronous stream of samples. Your interface could look like this:
interface ByteStreamToAudio : SynthModule { async in byte stream indata; // the asynchonous input sample stream out audio stream left,right; // the synchronous output sample streams };
Suppose you decided to write a module to produce sound asynchronously. Its interface could look like this:
interface SomeModule : SynthModule { async out byte stream outdata; };
How do you send the data? The first method is called „push delivery“. With asynchronous streams you send the data as packets. That means you send individual packets with bytes as in the above example. The actual process is: allocate a packet, fill it, send it.
Here it is in terms of code. First we allocate a packet:
DataPacket<mcopbyte> *packet = outdata.allocPacket(100);
The we fill it:
// cast so that fgets is happy that it has a (char *) pointer char *data = (char *)packet->contents; // as you can see, you can shrink the packet size after allocation // if you like if(fgets(data,100,stdin)) packet->size = strlen(data); else packet->size = 0;
Now we send it:
packet->send();
This is quite simple, but if we want to send packets exactly as fast as the receiver can process them, we need another approach, the „pull delivery“ method. You ask to send packets as fast as the receiver is ready to process them. You start with a certain amount of packets you send. As the receiver processes one packet after another, you start refilling them with fresh data, and send them again.
You start that by calling setPull. For example:
outdata.setPull(8, 1024);
This means that you want to send packets over outdata. You want to start sending 8 packets at once, and as the receiver processes some of them, you want to refill them.
Then, you need to implement a method which fills the packets, which could look like this:
void request_outdata(DataPacket<mcopbyte> *packet) { packet->size = 1024; // shouldn't be more than 1024 for(int i = 0;i < 1024; i++) packet->contents[i] = (mcopbyte)'A'; packet->send(); }
Thats it. When you don't have any data any more, you can start sending packets with zero size, which will stop the pulling.
Note that it is essential to give the method the exact name request_
. streamname
We just discussed sending data. Receiving data is much much simpler. Suppose you have a simple ToLower filter, which simply converts all letters in lowercase:
interface ToLower { async in byte stream indata; async out byte stream outdata; };
This is really simple to implement; here is the whole implementation:
class ToLower_impl : public ToLower_skel { public: void process_indata(DataPacket<mcopbyte> *inpacket) { DataPacket<mcopbyte> *outpacket = outdata.allocPacket(inpacket->size); // convert to lowercase letters char *instring = (char *)inpacket->contents; char *outstring = (char *)outpacket->contents; for(int i=0;i<inpacket->size;i++) outstring[i] = tolower(instring[i]); inpacket->processed(); outpacket->send(); } }; REGISTER_IMPLEMENTATION(ToLower_impl);
Again, it is essential to name the method process_
. streamname
As you see, for each arriving packet you get a call for a function (the process_indata
call in our case). You need to call the processed()
method of a packet to indicate you have processed it.
Here is an implenetation tip: if processing takes longer (d. h. if you need to wait for soundcard output or something like that), don't call processed immediately, but store the whole data packet and call processed only as soon as you really processed that packet. That way, senders have a chance to know how long it really takes to do your work.
As synchronization isn't so nice with asynchronous streams, you should use synchronous streams wherever possible, and asynchronous streams only when necessary.
Suppose you have 2 objects, for example an AudioProducer and an AudioConsumer. The AudioProducer has an output stream and AudioConsumer has an input one. Each time you want to connect them, you will use those 2 streams. The first use of defaulting is to enable you to make the connection without specifying the ports in that case.
Now suppose the teo objects above can handle stereo, and each have a „left“ and „right“ port. You'd still like to connect them as easily as before. But how can the connecting system know which output port to connect to which input port? It has no way to correctly map the streams. Defaulting is then used to specify several streams, with an order. Thus, when you connect an object with 2 default output streams to another one with 2 default input streams, you don't have to specify the ports, and the mapping will be done correctly.
Of course, this is not limited to stereo. Any number of streams can be made default if needed, and the connect function will check that the number of defaults for 2 object match (in the required direction) if you don't specify the ports to use.
The syntax is as follows: in the IDL, you can use the default keyword in the stream declaration, or on a single line. For example:
interface TwoToOneMixer { default in audio stream input1, input2; out audio stream output; };
In this example, the object will expect its two input ports to be connected by default. The order is the one specified on the default line, so an object like this one:
interface DualNoiseGenerator { out audio stream bzzt, couic; default couic, bzzt; };
Will make connections from „couic“ to „input1“, and „bzzt“ to „input2“ automatically. Note that since there is only one output for the mixer, it will be made default in this case (see below). The syntax used in the noise generator is useful to declare a different order than the declaration, or selecting only a few ports as default. The directions of the ports on this line will be looked up by mcopidl, so don't specify them. You can even mix input and output ports in such a line, only the order is important.
There are some rules that are followed when using inheritance:
If a default list is specified in the IDL, then use it. Parent ports can be put in this list as well, whether they were default in the parent or not.
Otherwise, inherit parent's defaults. Ordering is parent1 default1, parent1 default2..., parent2 default1... If there is a common ancestor using 2 parent branches, a „virtual public“-like merging is done at that default's first occurrence in the list.
If there is still no default and a single stream in a direction, use it as default for that direction.
Attribute change notifications are a way to know when an attribute changed. They are a bit comparable with Qt™'s or Gtk's signals and slots. For instance, if you have a GUI element, a slider, which configures a number between 0 and 100, you will usually have an object that does something with that number (for instance, it might be controlling the volume of some audio signal). So you would like that whenever the slider is moved, the object which scales the volume gets notified. A connection between a sender and a receiver.
MCOP deals with that by being able to providing notifications when attributes change. Whatever is declared as „attribute“ in the IDL, can emit such change notifications, and should do so, whenever it is modified. Whatever is declared as „attribute“ can also receive such change notifications. So for instance if you had two IDL interfaces, like these:
interface Slider { attribute long min,max; attribute long position; }; interface VolumeControl : Arts::StereoEffect { attribute long volume; // 0..100 };
You can connect them using change notifications. It works using the normal flowsystem connect operation. In this case, the C++ code to connect two objects would look like this:
#include <connect.h> using namespace Arts; [...] connect(slider,"position_changed",volumeControl,"volume");
As you see, each attribute offers two different streams, one for sending the change notifications, called
, and one for receiving change notifications, called attributename
_changedattributename
.
It is important to know that change notifications and asynchronous streams are compatible. They are also network transparent. So you can connect a change notification of a float attribute of a GUI widget has to an asynchronous stream of a synthesis module running on another computer. This of course also implies that change notifications are not synchronous, this means, that after you have sent the change notification, it may take some time until it really gets received.
When implementing objects that have attributes, you need to send change notifications whereever an attribute changes. The code for doing this looks like this:
void KPoti_impl::value(float newValue) { if(newValue != _value) { _value = newValue; value_changed(newValue); // <- send change notification } }
It is strongly recommended to use code like this for all objects you implement, so that change notifications can be used by other people. You should however void sending notifications too often, so if you are doing signal processing, it is probably the best if you keep track when you sent your last notification, so that you don't send one with every sample you process.
It will be especially useful to use change notifications in conjunction with scopes (things that visualize audio data for instance), gui elements, control widgets, and monitoring. Code using this is in kdelibs/arts/tests
, and in the experimental artsgui implementation, which you can find under kdemultimedia/arts/gui
.
.mcoprc
file The .mcoprc
file (in each user's home directory) can be used to configure MCOP in some ways. Currently, the following is possible:
The name of an interface to be used for global communication. Global communication is used to find other objects and obtain the secret cookie. Multiple MCOP clients/servers that should be able to talk to each other need to have a GlobalComm object which is able to share information between them. Currently, the possible values are „Arts::TmpGlobalComm“ to communicate via /tmp/mcop-
directory (which will only work on the local computer) and „Arts::X11GlobalComm“ to communicate via the root window properties on the X11 server. username
Specifies where to look for trader information. You can list more than one directory here, and separate them with commas, like
Specifies from which directories extensions (in the form of shared libraries) are loaded. Multiple values can be specified comma seperated.
An example which uses all of the above is:
# $HOME/.mcoprc file GlobalComm=Arts::X11GlobalComm # if you are a developer, it might be handy to add a directory in your home # to the trader/extension path to be able to add components without # installing them TraderPath="/opt/kde2/lib/mcop","/home/joe/mcopdevel/mcop" ExtensionPath="/opt/kde2/lib","/home/joe/mcopdevel/lib"
If you have used CORBA before, you will see that MCOP is much the same thing. In fact, aRts prior to version 0.4 used CORBA.
The basic idea of CORBA is the same: you implement objects (components). By using the MCOP features, your objects are not only available as normal classes from the same process (via standard C++ techniques) - they also are available to remote servers transparently. For this to work, the first thing you need to do is to specify the interface of your objects in an IDL file - just like CORBA IDL. There are only a few differences.
In MCOP there are no „in“ and „out“ parameters on method invocations. Parameters are always incoming, the return code is always outgoing, which means that the interface:
// CORBA idl interface Account { void deposit( in long amount ); void withdraw( in long amount ); long balance(); };
is written as
// MCOP idl interface Account { void deposit( long amount ); void withdraw( long amount ); long balance(); };
in MCOP.
There is no exception support. MCOP doesn't have exceptions - it uses something else for error handling.
There are no union types and no typedefs. I don't know if that is a real weakness, something one would desperately need to survive.
There is no support for passing interfaces or object references
You declare sequences as „sequencetype
“ in MCOP. There is no need for a typedef. For example, instead of:
// CORBA idl struct Line { long x1,y1,x2,y2; }; typedef sequence<Line> LineSeq; interface Plotter { void draw(in LineSeq lines); };
you would write
// MCOP idl struct Line { long x1,y1,x2,y2; }; interface Plotter { void draw(sequence<Line> lines); };
You can declare streams, which will then be evaluated by the aRts framework. Streams are declared in a similar manner to attributes. For example:
// MCOP idl interface Synth_ADD : SynthModule { in audio stream signal1,signal2; out audio stream outvalue; };
This says that your object will accept two incoming synchronous audio streams called signal1 and signal2. Synchronous means that these are streams that deliver x samples per second (or other time), so that the scheduler will guarantee to always provide you a balanced amount of input data (z. B. 200 samples of signal1 are there and 200 samples signal2 are there). You guarantee that if your object is called with those 200 samples signal1 + signal2, it is able to produce exactly 200 samples to outvalue.
This differs from CORBA mostly:
Strings use the C++ STL string
class. When stored in sequences, they are stored „plain“, that means they are considered to be a primitive type. Thus, they need copying.
longs are plain long's (expected to be 32 bit).
Sequences use the C++ STL vector
class.
Structures are all derived from the MCOP class Type
, and generated by the MCOP IDL compiler. When stored in sequences, they are not stored „plain“ , but as pointers, as otherwise, too much copying would occur.
After having them passed through the IDL compiler, you need to derive from the _skel
class. For instance, consider you have defined your interface like this:
// MCOP idl: hello.idl interface Hello { void hello(string s); string concat(string s1, string s2); long sum2(long a, long b); };
You pass that through the IDL compiler by calling mcopidl
, which will in turn generate hello.idl
hello.cc
and hello.h
. To implement it, you need to define a C++-class that inherits the skeleton:
// C++ header file - include hello.h somewhere class Hello_impl : virtual public Hello_skel { public: void hello(const string& s); string concat(const string& s1, const string& s2); long sum2(long a, long b); };
Finally, you need to implement the methods as normal C++
// C++ implementation file // as you see string's are passed as const string references void Hello_impl::hello(const string& s) { printf("Hello '%s'!\n",s.c_str()); } // when they are a returncode they are passed as "normal" strings string Hello_impl::concat(const string& s1, const string& s2) { return s1+s2; } long Hello_impl::sum2(long a, long b) { return a+b; }
Once you do that, you have an object which can communicate using MCOP. Just create one (using the normal C++ facilities to create an object):
Hello_impl server;
And as soon as you give somebody the reference
string reference = server._toString(); printf("%s\n",reference.c_str());
and go to the MCOP idle loop
Dispatcher::the()->run();
People can access the thing using
// this code can run anywhere - not necessarily in the same process // (it may also run on a different computer/architecture) Hello *h = Hello::_fromString([the object reference printed above]);
and invoke methods:
if(h) h->hello("test"); else printf("Access failed?\n");
Since MCOP servers will listen on a TCP port, potentially everybody (if you are on the Internet) may try to connect MCOP services. Thus, it is important to authenticate clients. MCOP uses the md5-auth protocol.
The md5-auth protocol does the following to ensure that only selected (trusted) clients may connect to a server:
It assumes you can give every client a secret cookie.
Every time a client connects, it verifies that this client knows that secret cookie, without actually transferring it (not even in a form that somebody listening to the network traffic could find it out).
To give each client the secret cookie, MCOP will (normally) put it in the mcop
directory (under /tmp/mcop-
). Of course, you can copy it to other computers. However, if you do so, use a secure transfer mechanism, such as scp (from ssh). USER
/secret-cookie
The authentication of clients uses the following steps:
[SERVER] generate a new (random) cookie R
[SERVER] send it to the client
[CLIENT] read the "secret cookie" S from a file
[CLIENT] mangle the cookies R and S to a mangled cookie M using the MD5 algorithm
[CLIENT] send M to the server
[SERVER] verify that mangling R and S gives just the same thing as the cookie M received from the client. If yes, authentication is successful.
This algorithm should be secure, given that
The secret cookies and random cookies are „random enough“ and
The MD5 hashing algorithm doesn't allow to find out the „original text“, that is the secret cookie S and the random cookie R (which is known, anyway), from the mangled cookie M.
The MCOP protocol will start every new connection with an authentication process. Basically, it looks like this:
Server sends a ServerHello message, which describes the known authentication protocols.
Client sends a ClientHello message, which includes authentication info.
Server sends an AuthAccept message.
To see that the security actually works, we should look at how messages are processed on unauthenticated connections:
Before the authentication succeeds, the server will not receive other messages from the connection. Instead, if the server for instance expects a „ClientHello“ message, and gets an mcopInvocation message, it will drop the connection.
If the client doesn't send a valid MCOP message at all (no MCOP magic in the message header) in the authentication phase, but something else, the connection is dropped.
If the client tries to send a very very large message (> 4096 bytes in the authentication phase, the message size is truncated to 0 bytes, which will cause that it isn't accepted for authentication) This is to prevent unauthenticated clients from sending z. B. 100 megabytes of message, which would be received and could cause the server to run out of memory.
If the client sends a corrupt ClientHello message (one, for which demarshalling fails), the connection is dropped.
If the client send nothing at all, then a timeout should occur (to be implemented).
It has conceptual similarities to CORBA, but it is intended to extend it in all ways that are required for real time multimedia operations.
It provides a multimedia object model, which can be used for both: communication between components in one adress space (one process), and between components that are in different threads, processes or on different hosts.
All in all, it will be designed for extremely high performance (so everything shall be optimized to be blazingly fast), suitable for very communicative multimedia applications. For instance streaming videos around is one of the applications of MCOP, where most CORBA implementations would go down to their knees.
The interface definitions can handle the following natively:
Continous streams of data (such as audio data).
Event streams of data (such as MIDI events).
Real reference counting.
and the most important CORBA gimmicks, like
Synchronous method invocations.
Asynchronous method invocations.
Constructing user defined data types.
Multiple inheritance.
Passing object references.
Design goals/ideas:
Marshalling should be easy to implement.
Demarshalling requires the receiver to know what type he wants to demarshall.
The receiver is expected to use every information - so skipping is only in the protocol to a degree that:
If you know you are going to receive a block of bytes, you don't need to look at each byte for an end marker.
If you know you are going to receive a string, you don't need to read it until the zero byte to find out it's length while demarshalling, however,
If you know you are going to receive a sequence of strings, you need to look at the length of each of them to find the end of the sequence, as strings have variable length. But if you use the strings for something useful, you'll need to do that anyway, so this is no loss.
As little overhead as possible.
Marshalling of the different types is show in the table below:
Type | Marshalling Process | Result |
---|---|---|
void | void types are marshalled by omitting them, so nothing is written to the stream for them. | |
long | is marshalled as four bytes, the most significant byte first, so the number 10001025 (which is 0x989a81) would be marshalled as: | 0x00 0x98 0x9a 0x81 |
enums | are marshalled like longs | |
byte | is marshalled as a single byte, so the byte 0x42 would be marshalled as: | 0x42 |
string | is marshalled as a long, containing the length of the following string, and then the sequence of characters strings must end with one zero byte (which is included in the length counting). Wichtiginclude the trailing 0 byte in length counting! „hello“ would be marshalled as: | 0x00 0x00 0x00 0x06 0x68 0x65 0x6c 0x6c 0x6f 0x00 |
boolean | is marshalled as a byte, containing 0 if false or 1 if true, so the boolean value true is marshalled as: | 0x01 |
float | is marshalled after the four byte IEEE754 representation - detailed docs how IEEE works are here: http://twister.ou.edu/workshop.docs/common-tools/numerical_comp_guide/ncg_math.doc.html and here: http://java.sun.com/docs/books/vmspec/2nd-edition/html/Overview.doc.html. So, the value 2.15 would be marshalled as: | 0x9a 0x99 0x09 0x40 |
struct | A structure is marshalled by marshalling it's contents. There are no additional prefixes or suffixes required, so the structure struct test { string name; // which is "hello" long value; // which is 10001025 (0x989a81) }; would be marshalled as | 0x00 0x00 0x00 0x06 0x68 0x65 0x6c 0x6c 0x6f 0x00 0x00 0x98 0x9a 0x81 |
sequence | a sequence is marshalled by listing the number of elements that follow, and then marshalling the elements one by one. So a sequence of 3 longs a, with a[0] = 0x12345678, a[1] = 0x01 and a[2] = 0x42 would be marshalled as: | 0x00 0x00 0x00 0x03 0x12 0x34 0x56 0x78 0x00 0x00 0x00 0x01 0x00 0x00 0x00 0x42 |
If you need to refer to a type, all primitive types are referred by the names given above. Structures and enums get own names (like Header). Sequences are referred as *normal type
, so that a sequence of longs is „*long“ and a sequence of Header struct's is „*Header“.
The MCOP message header format is defined as defined by this structure:
struct Header { long magic; // the value 0x4d434f50, which is marshalled as MCOP long messageLength; long messageType; };
The possible messageTypes are currently
mcopServerHello = 1 mcopClientHello = 2 mcopAuthAccept = 3 mcopInvocation = 4 mcopReturn = 5 mcopOnewayInvocation = 6
A few notes about the MCOP messaging:
Every message starts with a Header.
Some messages types should be dropped by the server, as long as the authentication is not complete.
After receiving the header, the protocol (connection) handling can receive the message completely, without looking at the contents.
The messageLength in the header is of course in some cases redundant, which means that this approach is not minimal regarding the number of bytes.
However, it leads to an easy (and fast) implementation of non-blocking messaging processing. With the help of the header, the messages can be received by protocol handling classes in the background (non-blocking), if there are many connections to the server, all of them can be served parallel. You don't need to look at the message content, to receive the message (and to determine when you are done), just at the header, so the code for that is pretty easy.
Once a message is there, it can be demarshalled and processed in one single pass, without caring about cases where not all data may have been received (because the messageLength guarantees that everything is there).
To call a remote method, you need to send the following structure in the body of an MCOP message with the messageType = 1 (mcopInvocation):
struct Invocation { long objectID; long methodID; long requestID; };
after that, you send the parameters as structure, z. B. if you invoke the method string concat(string s1, string s2), you send a structure like
struct InvocationBody { string s1; string s2; };
if the method was declared to be oneway - that means asynchronous without return code - then that was it. Otherwise, you'll receive as answer the message with messageType = 2 (mcopReturn)
struct ReturnCode { long requestID; <resulttype> result; };
where <resulttype> is the type of the result. As void types are omitted in marshalling, you can also only write the requestID if you return from a void method.
So our string concat(string s1, string s2) would lead to a returncode like
struct ReturnCode { long requestID; string result; };
To do invocations, you need to know the methods an object supports. To do so, the methodID 0, 1, 2 and 3 are hardwired to certain functionalities. That is
long _lookupMethod(MethodDef methodDef); // methodID always 0 string _interfaceName(); // methodID always 1 InterfaceDef _queryInterface(string name); // methodID always 2 TypeDef _queryType(string name); // methodID always 3
to read that, you of course need also
struct MethodDef { string methodName; string type; long flags; // set to 0 for now (will be required for streaming) sequence<ParamDef> signature; }; struct ParamDef { string name; long typeCode; };
the parameters field contains type components which specify the types of the parameters. The type of the returncode is specified in the MethodDef's type field.
Strictly speaking, only the methods _lookupMethod()
and _interfaceName()
differ from object to object, while the _queryInterface()
and _queryType()
are always the same.
What are those methodIDs? If you do an MCOP invocation, you are expected to pass a number for the method you are calling. The reason for that is, that numbers can be processed much faster than strings when executing an MCOP request.
So how do you get those numbers? If you know the signature of the method, that is a MethodDef that describes the method, (which contains name, type, parameter names, parameter types and such), you can pass that to _lookupMethod of the object where you wish to call a method. As _lookupMethod is hardwired to methodID 0, you should encounter no problems doing so.
On the other hand, if you don't know the method signature, you can find which methods are supported by using _interfaceName, _queryInterface and _queryType.
Since KDE dropped CORBA completely, and is using DCOP everywhere instead, naturally the question arises why aRts isn't doing so. After all, DCOP support is in KApplication
, is well-maintained, supposed to integrate greatly with libICE, and whatever else.
Since there will be (potentially) a lot of people asking whether having MCOP besides DCOP is really necessary, here is the answer. Please don't get me wrong, I am not trying to say „DCOP is bad“. I am just trying to say „DCOP isn't the right solution for aRts“ (while it is a nice solution for other things).
First, you need to understand what exactly DCOP was written for. Created in two days during the KDE-TWO meeting, it was intended to be as simple as possible, a really „lightweight“ communication protocol. Especially the implementation left away everything that could involve complexity, for instance a full blown concept how data types shall be marshalled.
Even although DCOP doesn't care about certain things (like: how do I send a string in a network-transparent manner?) - this needs to be done. So, everything that DCOP doesn't do, is left to Qt™ in the KDE apps that use DCOP today. This is mostly type management (using the Qt™ serialization operator).
So DCOP is a minimal protocol which perfectly enables KDE applications to send simple messages like „open a window pointing to http://www.kde.org“ or „your configuration data has changed“. However, inside aRts the focus lies on other things.
The idea is, that little plugins in aRts will talk involving such data structures as „midi events“ and „songposition pointers“ and „flow graphs“.
These are complex data types, which must be sent between different objects, and be passed as streams, or parameters. MCOP supplies a type concept, to define complex data types out of simpler ones (similar to structs or arrays in C++). DCOP doesn't care about types at all, so this problem would be left to the programmer - like: writing C++ classes for the types, and make sure they can serialize properly (for instance: support the Qt™ streaming operator).
But that way, they would be inaccessible to everything but direct C++ coding. Specifically, you could not design a scripting language, that would know all types plugins may ever expose, as they are not self describing.
Much the same argument is valid for interfaces as well. DCOP objects don't expose their relationships, inheritance hierarchies, etc. - if you were to write an object browser which shows you „what attributes has this object got“, you'd fail.
While Matthias told me that you have a special function „functions“ on each object that tells you about the methods that an object supports, this leaves out things like attributes (properties), streams and inheritance relations.
This seriously breaks applications like aRts-builder. But remember: DCOP was not so much intended to be an object model (as Qt™ already has one with moc and similar), nor to be something like CORBA, but to supply inter-application communication.
Why MCOP even exists is: it should work fine with streams between objects. aRts makes heavily use of small plugins, which interconnect themselves with streams. The CORBA version of aRts had to introduce a very annoying split between „the SynthModule objects“, which were the internal work modules that did do the streaming, and „the CORBA interface“, which was something external.
Much code cared about making interaction between „the SynthModule objects“ and „the CORBA interface“ look natural, but it didn't, because CORBA knew nothing at all about streams. MCOP does. Look at the code (something like simplesoundserver_impl.cc
). Way better! Streams can be declared in the interface of modules, and implemented in a natural looking way.
One can't deny it. One of the reasons why I wrote MCOP was speed. Here are some arguments why MCOP will definitely be faster than DCOP (even without giving figures).
An invocation in MCOP will have a six-„long“-header. That is:
magic „MCOP“
message type (invocation)
size of the request in bytes
request ID
target object ID
target method ID
After that, the parameters follow. Note that the demarshalling of this is extremely fast. You can use table lookups to find the object and the method demarshalling function, which means that complexity is O(1) [ it will take the same amount of time, no matter how many objects are alive, or how many functions are there ].
Comparing this to DCOP, you'll see, that there are at least
a string for the target object - something like „myCalculator“
a string like „addNumber(int,int)“ to specify the method
several more protocol info added by libICE, and other DCOP specifics I don't know
These are much more painful to demarshall, as you'll need to parse the string, search for the function, usw..
In DCOP, all requests are running through a server (DCOPServer). That means, the process of a synchronous invocation looks like this:
Client process sends invocation.
DCOPserver (man-in-the-middle) receives invocation and looks where it needs to go, and sends it to the „real“ server.
Server process receives invocation, performs request and sends result.
DCOPserver (man-in-the-middle) receives result and ... sends it to the client.
Client decodes reply.
In MCOP, the same invocation looks like this:
Client process sends invocation.
Server process receives invocation, performs request and sends result.
Client decodes reply.
Say both were implemented correctly, MCOPs peer-to-peer strategy should be faster by a factor of two, than DCOPs man-in-the-middle strategy. Note however that there were of course reasons to choose the DCOP strategy, which is namely: if you have 20 applications running, and each app is talking to each app, you need 20 connections in DCOP, and 200 with MCOP. However in the multimedia case, this is not supposed to be the usual setting.
I tried to compare MCOP and DCOP, doing an invocation like adding two numbers. I modified testdcop to achieve this. However, the test may not have been precise on the DCOP side. I invoked the method in the same process that did the call for DCOP, and I didn't know how to get rid of one debugging message, so I used output redirection.
The test only used one object and one function, expect DCOPs results to decrease with more objects and functions, while MCOPs results should stay the same. Also, the dcopserver process wasn't connected to other applications, it might be that if many applications are connected, the routing performance decreases.
The result I got was that while DCOP got slightly more than 2000 invocations per second, MCOP got slightly more than 8000 invocations per second. That makes a factor of 4. I know that MCOP isn't tuned to the maximum possible, yet. (Comparision: CORBA, as implemented with mico, does something between 1000 and 1500 invocations per second).
If you want „harder“ data, consider writing some small benchmark app for DCOP and send it to me.
CORBA had the nice feature that you could use objects you implemented once, as „seperate server process“, or as „library“. You could use the same code to do so, and CORBA would transparently descide what to do. With DCOP, that is not really intended, and as far as I know not really possible.
MCOP on the other hand should support that from the beginning. So you can run an effect inside artsd. But if you are a wave editor, you can choose to run the same effect inside your process space as well.
While DCOP is mostly a way to communicate between apps, MCOP is also a way to communicate inside apps. Especially for multimedia streaming, this is important (as you can run multiple MCOP objects parallely, to solve a multimedia task in your application).
Although MCOP does not currently do so, the possibilities are open to implement quality of service features. Something like „that MIDI event is really really important, compared to this invocation“. Or something like „needs to be there in time“.
On the other hand, stream transfer can be integrated in the MCOP protocol nicely, and combined with QoS stuff. Given that the protocol may be changed, MCOP stream transfer should not really get slower than conventional TCP streaming, but: it will be easier and more consistent to use.
There is no need to base a middleware for multimedia on Qt™. Deciding so, and using all that nice Qt™-streaming and stuff, will easily lead to the middleware becoming a Qt™-only (or rather KDE-only) thing. I mean: as soon as I'll see the GNOMEs using DCOP, too, or something like that, I am certainly proven wrong.
While I do know that DCOP basically doesn't know about the data types it sends, so that you could use DCOP without using Qt™, look at how it is used in daily KDE usage: people send types like QString
, QRect
, QPixmap
, QCString
, ..., around. These use Qt™-serialization. So if somebody choose to support DCOP in a GNOME program, he would either have to claim to use QString
,... types (although he doesn't do so), and emulate the way Qt™ does the streaming, or he would send other string, pixmap and rect types around, and thus not be interoperable.
Well, whatever. aRts was always intended to work with or without KDE, with or without Qt™, with or without X11, and maybe even with or without Linux® (and I have even no problems with people who port it to a popular non-free operating systems).
It is my position that non-GUI-components should be written non-GUI-dependant, to make sharing those among wider amounts of developers (and users) possible.
I see that using two IPC protocols may cause inconveniences. Even more, if they are both non-standard. However, for the reasons given above, switching to DCOP is no option. If there is significant interest to find a way to unite the two, okay, we can try. We could even try to make MCOP speak IIOP, then we'd have a CORBA ORB ;).
I talked with Matthias Ettrich a bit about the future of the two protocols, and we found lots of ways how things could go on. For instance, MCOP could handle the message communication in DCOP, thus bringing the protocols a bit closer together.
So some possible solutions would be:
Write an MCOP - DCOP gateway (which should be possible, and would make interoperation possible) - note: there is an experimental prototype, if you like to work on that.
Integrate everything DCOP users expect into MCOP, and try to only do MCOP - one could add an „man-in-the-middle-option“ to MCOP, too ;)
Base DCOP on MCOP instead of libICE, and slowly start integrating things closer together.
However, it may not be the worst possibility to use each protocol for everything it was intended for (there are some big differences in the design goals), and don't try to merge them into one.
aRts is not only a piece of software, it also provides a variety of APIs for a variety of purposes. In this section, I will try to describe the "big picture", a brief glance what those APIs are supposed to do, and how they interact.
There is one important distinction to make: most of the APIs are language and location independent because they are specified as mcopidl. That is, you can basically use the services they offer from any language, implement them in any language, and you will not have to care whether you are talking to local or remote objects. Here is a list of these first:
Basic definitions that form the core of the MCOP functionality, such as the protocol itself, definitions of the object, the trader, the flow system and so on.
These contain the flow system you will use for connecting audio streams, the definition of Arts::SynthModule which is the base for any interface that has streams, and finally a few useful audio objects
Here, an object that can play a media, Arts::PlayObject gets defined. Media players such as the KDE media player noatun will be able to play any media for which a PlayObject can be found. So it makes sense to implement PlayObjects for various formats (such as mp3, mpg video, midi, wav, ...) on that base, and there are a lot already.
Here, an interface for the system wide sound server artsd is defined. The interface is called Arts::SoundServer, which implements functionality like accepting streams from the network, playing samples, creating custom other aRts objects and so on. Network transparency is implied due to the use of MCOP (as for everything else here).
This module defines basic flow graph functionality, that is, combining simpler objects to more complex ones, by defining a graph of them. It defines the basic interface Arts::StructureDesc, Arts::ModuleDesc and Arts::PortDesc which contain a description of a structure, module, and port. There is also a way to get a "living network of objects" out of these connection and value descriptions, using a factory.
This module defines basic midi functionality, like objects that produce midi events, what is a midi event, an Arts::MidiManager to connect the producers and consumers of midi events, and so on. As always network transparency implied.
Here are various additional filters, oscillators, effects, delays and so on, everything required for real useful signal processing, and to build complex instruments and effects out of these basic building blocks.
This cares about visual objects. It defines the basic type Arts::Widget from which all GUI modules derive. This will produce toolkit independency, and ... visual GUI editing, and serializable GUIs. Also, as the GUI elements have normal attributes, their values can be straight forward connected to some signal processing modules. (I.e. the value of a slider to the cutoff of a filter). As always: network transparent.
Where possible, aRts itself is implemented using IDL. On the other hand, there are some language specific APIs, using either plain C++ or plain C. It is usually wise to use IDL interfaces where possible, and the other APIs where necessary. Here is a list of language specific APIs:
These are convenience KDE APIs for the simple and common common case, where you just want to play a sample. The APIs are plain C++, Qt/KDE optimized, and as easy as it can get.
Plain C interface for the sound server. Very useful for porting legacy applications.
Here all magic for MCOP happens. The library contains the basic things you need to know for writing a simple MCOP application, the dispatcher, timers, iomanagement, but also the internals to make the MCOP protocol itself work.
Besides the implementation of artsflow.idl, some useful utilities like sampling rate conversion.
Integration of MCOP into the Qt event loop, when you write Qt applications using MCOP.
The aRts C API was designed to make it easy to writing and port plain C applications to the aRts sound server. It provides streaming functionality (sending sample streams to artsd), either blocking or non-blocking. For most applications you simply remove the few system calls that deal with your audio device and replace them with the appropriate aRts calls.
I did two ports as a proof of concept: mpg123 and quake. You can get the patches from here. Feel free to submit your own patches to the maintainer of aRts or of multimedia software packages so that they can integrate aRts support into their code.
Sending audio to the sound server with the API is very simple:
include the header file using #include <artsc.h>
initialize the API with arts_init()
create a stream with arts_play_stream()
configure specific parameters with arts_stream_set()
write sampling data to the stream with arts_write()
close the stream with arts_close_stream()
free the API with arts_free()
Here is a small example program that illustrates this:
#include <stdio.h> #include <artsc.h> int main() { arts_stream_t stream; char buffer[8192]; int bytes; int errorcode; errorcode = arts_init(); if (errorcode < 0) { fprintf(stderr, "arts_init error: %s\n", arts_error_text(errorcode)); return 1; } stream = arts_play_stream(44100, 16, 2, "artsctest"); while((bytes = fread(buffer, 1, 8192, stdin)) > 0) { errorcode = arts_write(stream, buffer, bytes); if(errorcode < 0) { fprintf(stderr, "arts_write error: %s\n", arts_error_text(errorcode)); return 1; } } arts_close_stream(stream); arts_free(); return 0; }
To easily compile and link programs using the aRts C API, the artsc-config utility is provided which knows which libraries you need to link and where the includes are. It is called using
artsc-config --libs
to find out the libraries and
artsc-config --cflags
to find out additional C compiler flags. The example above could have been compiled using the command line:
cc
-o artsctest artsctest.c `artsc-config --cflags` `artsc-config --libs`
cc
-o artsctest
artsctest.c
`artsc-config --cflags`
`artsc-config --libs`
Dieses Kapitel beschreibt die Standardmodule von aRts. Eine der mächtigsten Möglichkeiten von aRts ist die Kombinierbarkeit von Modulen zu Strukturen, um neue Funktionen wie Effekte und Instrumente zu implementieren.
Die Module sind in Kategorien eingeteilt. Synthese-Module werden zur Implementation von „Verbindungen“ benötigt, die Multimedia-Datenströme für neue Effekte, Instrumente, Mischer und Anwendungen zusammenfügen. Visuelle Module erlauben die Erzeugung einer graphischen Benutzeroberfläche zur Kontrolle der Klangstrukturen, die mit den Synthese-Modulen aufgebaut werden.
Multiplizert ein Signal mit einem Faktor. Sie können dieses Modul verwenden, um ein Signal zu reduzieren (0 < Faktor < 1) oder zu verstärken (Faktor > 1), oder um ein Signal zu invertieren (Faktor < 0). Der Faktor kann ebenfalls ein Signal sein und muss keine Konstante sein (z.B. eine Hüllkurve oder ein reales Signal).
Dieses Modul teilt ein Signal durch einen Faktor. Es kann verwendet werden, um ein Signal durch ein anderes zu dividieren. Es kann aber auch invalue1 auf 1 gesetzt werden, damit das reziproke von invalue2 als outvalue erscheint. Allerdings darf dann invalue2 nicht 0 werden, da es sonst eine Division durch Null Probleme bereitet.
Addiert eine beliebige Anzahl von Signalen. Wenn Sie die Wellenformen von vier verschiedenen Oszillatoren addieren müssen, können Sie alle Ausgänge mit einem Synth_MULTI_ADD-Modul verbinden. Das ist effektiver als die Verwendung von drei Synth_ADD-Modulen.
Hiermit werden zwei Signale über Kreuz gemischt (crossfading). Wenn der Prozentsatz -1 beträgt, dann ist nur das linke, bei 1 nur das rechte und bei 0 sind beide Signale gleichstark hörbar.
Damit wird es möglich, ein Signal in einem definierten Bereich zu halten. Wenn Sie zwei Signale haben, die beide zwischen -1 und 1 vor dem Mischen waren, befindet sich das gemischte Signal ebenfalls zwischen -1 und 1.
Das Gegenteil eines crossfaders. Hier wird ein Monosignal aufgeteilt in ein Stereosignal: Das Modul kann das Signal automatisch zwischen dem rechten und linken Kanal aufteilen. Das macht lebendigere Mischungen möglich. Eine Standardanwendung wäre ein Gitarren- oder Gesangsklang.
Verbinden Sie einen LFO, eine Sinus- oder Sägezahnschwingung mit inlfo und wählen Sie eine Frequenz zwischen 0.1 und 5Hz für einen traditionellen Effekt oder eine höhere Frequenz für einen Special FX.
Verzögert das Eingangssignal um eine bestimmte Zeit. Die Zeit muss zwischen 0 und maxdelay für eine Verzögerung zwischen 0 und maxdelay Sekunden liegen.
Diese Art von Verzögerungen darf nicht in rückgekoppelten Strukturen verwendet werden, weil es sich um eine variable Verzögerungszeit handelt. Sie können die Zeit ändern, während das Modul läuft und sie auch auf Null setzen. Da aber in einer rückgekoppelten Struktur das eigene Ausgangssignal für die Berechnung des nächsten Eingangssignals notwendig ist, könnte eine Verzögerung, die auf Null abfällt, zu einem Einfrieren führen.
In einem solchen Fall können Sie aber CDELAY verwenden. Nehmen Sie eine geringe konstante Verzögerung (z.B. 0.001 Sekunden) zusammen mit einer einstellbaren Verzögerung.
Außerdem können Sie CDELAY und DELAY kombinieren, um eine variable Verzögerung mit einem positiven Minimalwert in einem rückgekoppelten System zu erhalten. Wichtig ist nur, das ein CDELAY-Modul vorhanden ist.
Verzögert das Eingangssignal um eine bestimmte Zeitspanne. Die Zeit muss größer als 0 für eine Verzögerung von mehr als 0 Sekunden sein. Die Verzögerung ist konstant während der Berechnung, kann also nicht verändert werden.
Das spart Rechenzeit, da keine Interpolation notwendig ist, und ist nützlich für rekursive Strukturen.Siehe weiter oben (Synth_DELAY).
Dies ist eine klassische ADSR-Hüllkurve, das heißt Sie können folgendes festlegen:
Ob die Taste gerade vom Benutzer gedrückt wird.
Das Eingangssignal.
Die Zeit zwischen dem Niederdrücken der Taste und dem Zeitpunkt zu dem das Signal seine maximale Amplitude erreicht (in Sekunden).
Die Zeit, bis das Signal nach dem Maximalwert einen konstanten Dauerwert annimmt (in Sekunden).
Der konstante Dauerwert, bei dem das Signal gehalten wird, nachdem der Benutzer die Taste wieder losgelassen hat.
Die Zeit vom Loslassen der Taste bis das Signal den Wert Null wiedererreicht hat (in Sekunden).
Das skalierte Signal liegt am Ausgang (outvalue) an. Wenn die ADSR-Hüllkurve beendet ist, wird der Ausgang done auf 1 gesetzt. Das können Sie verwenden, um für ein Instrument das Signal „done“ zu generieren (das dazu führt, das die Struktur vom MIDI-Router nach dem Ende der Release-Phase gelöscht wird).
Das Synth_PSCALE-Modul skaliert einen Audiostrom von der Lautstärke 0 (Stille) bis 1 (Maximallautstärke) und zurück zu 0 (Stille). Gesteuert wird das durch den Eingang Position (pos) (dieser Eingang kann mit dem entsprechenden Ausgang von Synth_SEQUENCE belegt werden). Die Stelle, an der der Maximalwert erreicht werden soll, kann als Eingang pos angegeben werden.
Beispiel: Setzen Sie top auf 0.1. Das bedeutet, nach 10% der Note erreicht die Lautstärke ihren Maximalwert und klingt danach aus.
Dies ist ein Nachhall-Effekt. In der augenblicklichen Implementation kann ein Stereosignal durch diesen Effekt geschickt werden. Dabei wird der Nachhall zum ursprünglichen Signal addiert.
Das bedeutet, Sie können das Modul ein innerhalb eines Stereo-Effektstapels verwenden.
Das Eingangssignal wird mit inleft und inright verbunden, das Ausgangssignal liegt bei outleft und outright an.
Sie können folgende Parameter festlegen:
Die Größe des Raumes, für den der Nachhall simuliert wird (Bereich: 0..1, wobei 1 demgrößtmöglichen Raum entspricht).
Dies steuert einen Filter, der die Raumsimulation hohe Frequenzen absorbieren lässt. Der Bereich liegt zwischen 0 (keine Absorption) und 1 (sehr viel Absorption der hohen Frequenzen).
der Anteil des Nachhall-Signals (das ist der Anteil des Signals, das durch die Filter verändert und zu einer „nicht trockenen“ Akustik, also einem „halligen Klang“ führt.
der Anteil des ursprünglichen Signals, der durchgelassen werden soll. Er führt eher zu einem Echo (oder kombinierter Verzögerung) anstatt einem Nachhall-Effekt (Bereich: 0..1)
Der Anteil an Stereo-Magie, den der Nachhall-Effekt hinzufügt und zu einem breiteren Klang im Stereo-Panorama führt (Bereich: 0..1).
[ TODO: Ich glaube, wenn mode 1 ist, wird das ursprüngliche Klangbild beibehalten, während mode 0 der normale Arbeitsmodus ist ]
Das Tremolo-Modul verändert die Amplitude entsprechend einer LFO-Welle. Üblicherweise verwendet man dazu eine Sinusschwingung, aber das ist nicht zwingend notwendig. Man erhält einen intensiven Effekt, der in vielen Arrangements wegen seiner großen Dynamik sehr durchdringend ist. Der Tremolo-Effekt ist einer der liebsten Effekte für Gittaristen, wenn er auch nicht mehr so populär wie in den 1960ern ist.
[TODO: augenblicklich ist dieser Effekt als invalue + abs(inlfo) implementiert - vielleicht wäre es sinnvoller, diesen als invalue * (1+inlfo*depth) zu implementieren, wobei depth den Bereich 0..1 hat - das wird nach KDE2.1 entschieden. Falls Sie Anmerkungen haben, schicken Sie eine Mail an die aRts-Liste ;). ]
Ein Flanger ist ein zeitveränderlicher Verzögerungseffekt. Um die Entwicklung von umfangreichen Flanger-Effekten einfacher zu gestalten, wurde dieses Modul hinzugefügt, das den Kern eines Ein-Kanal-Flangers darstellt.
Folgende Kanäle sind vorhanden:
Das Signal, das verarbeitet werden soll.
Möglichst eine Sinusschwingung, die die Verzögerungszeit (delay) innerhalb des Flangers moduliert (-1 .. 1).
Der minimale Wert für die Verzögerung (delay) innerhalb des Flangers in Millisekunden. Empfohlene Werte: versuchen sie etwa 1 ms. Bitte verwenden Sie Werte < 1000 ms.
Der minimale Wert für die Verzögerung (delay) innerhalb des Flangers in Millisekunden. Empfohlene Werte: versuchen sie etwa 5 ms. Bitte verwenden Sie Werte < 1000 ms.
Das Ausgangssignal. Für den Effekt ist es wichtig, dass dieses Signal mit dem ursprünglichen (nicht veränderten) Signal gemischt wird.
Sie können dieses als die Basis für einen Choreffekt verwenden.
Dieser Höhenverschiebungseffekt verändert die Frequenz des Eingangssignals ohne die Geschwindigkeit des Signals zu verändern. Eine Anwendung für diesen Effekt ist die Veränderung Ihrer Stimme, während Sie sie aufnehmen (und abspielen) in Echtzeit
Der Parameter speed gibt die relative Geschwindigkeit wieder, mit der das Signal wiedergegeben wird. Eine Geschwindigkeit von 2 würde den Klang auf die doppelte Frequenz anheben (z.B. würde eine Eingangsfrequenz von 440Hz zu einer Ausgangsfrequenz von 880Hz führen).
Der Parameter frequency(Frequenz) wird intern verwendet, um zwischen unterschiedlichen Signalgüten umzuschalten. Abhängig von Ihrer Wahl wird der erzeugte Klang mehr oder weniger realistisch sein. Ein guter Startwert liegt zwischen 5 und 10.
Dieses Modul beschneidet ein Signal, um es in den Bereich von [-1;1] einzupassen. Es werden keine Maßnahmen gegen die Störungen getroffen, die beim Abschneiden lauter Signale entstehen. Sie können das als Effekt verwenden (z.B. um eine leicht abgeschnittene Sinusschwingung zu erzeugen). Es ist wahrscheinlich meistens eine gute Idee, das Resultat durch einen Tiefpassfilter zu schicken, damit es nicht so aggressiv klingt.
Ein hübsches Modul eines parametrischen Equalizers. Folgende Parameter sind vorhanden:
Das Signal, das durch den Equalizer gefiltert wird.
Wie tiefe Frequenzen verändert werden sollen. Der Wert ist in dB, wobei 0 keine Änderung der tiefen Frequenzen bedeutet, -6 bedeutet Absenkung um 6dB und +6 bedeutet Anhebung um 6dB.
Wie mittlere Frequenzen durch den Equalizer verändert werden sollen in dB (siehe auch low).
Wie hohe Frequenzen durch den Equalizer verändert werden sollen in dB (siehe auch low).
Dies ist die zentral Frequenz des Equalizers in Hz, die mittleren Frequenzen befinden sich in diesem Bereich, die hohen und tiefen Frequenz oberhalb und unterhalb. Anmerkung: die Frequenz kann nicht höher als die halbe Samplingrate sein, also normalerweise 22050Hz, und nicht tiefer als 1 Hz.
Beeinflusst die Breite des Frequenzspektrums. Es sind nur positive Zahlen > 0 erlaubt. Der Wert Eins ist sinnvoll, höhere Werte von q bedeuten ein schmaleres Frequenzband der mittleren Frequenzen, geringere Werte ein breiteres Band.
Ein gedämpfter Schwingkreis, der alle Frequenzen um seine Resonanzfrequenz filtert. Es gibt keine leichte Möglichkeit, die Resonanzfrequenz festzulegen (die nicht herausgefiltert wird), da es nur zwei ungewöhnliche Konstanten f und b gibt. Der Programmteil stammt noch aus den ersten Tagen von Synthesizern und wird vermutlich durch einen neuen Filter ersetzt werden, bei dem man die Frequenz und den Resonanzwert als Parameter hat.
Versuchen Sie etwa b=5, f=5 oder b=10, f=10 oder b=15, f=15.
Dieses Modul lädt eine Instrumentenstruktur aus einer Datei und registriert sich als MIDI-Ausgangskanal beim aRts-MIDI-Manager. Noten, die an diesen Ausgang gesendet werden, erzeugen Töne dieses Instrumentes.
Sie können so etwas mit artscontrol leichter einrichten als manuell in aRts-builder.
Spielt eine Sequenz von Noten immer wieder. Die Noten werden in Tracker Notation angegeben und durch Semikolons voneinander getrennt. Ein Beispiel ist A-3;C-4;E-4;C-4;
. Die Geschwindigkeit wird in Sekunden pro Note angegeben, wenn Sie also 120 BPM anvisieren, legen Sie die Geschwindigkeit auf 0.5 Sekunden fest, da 60 Sekunden / 0.5 Sekunden pro Note auf 120 BPM führt.
Sie können für jede Note eine individuelle Länge relativ zur Grundlänge festlegen durch einen Doppelpunkt gefolgt von der Länge hinter der Notenbezeichnung. Ein Beispiel dafür ist A-3:2;C-4:0.5;D-4:0.5;E-4;
. Midi-Kompositionsprogramme ist ein wenig komfortabler ;)
Das Synth_SEQUENCE-Modul gibt zusätzliche Informationen über die Position innerhalb der gerade wiedergegebenen Note aus, wobei 0 gerade begonnen und 1 gerade beendet bedeutet. Diese Informationen können im Synth_PSCALE-Modul verwendet werden (siehe unten).
Dieser Menüpunkt spielt eine wav
-Datei ab. Er ist nur dann verfügbar, wenn Sie libaudiofile auf Ihrem Computer installiert haben. Die wave-Datei startet, sobald das Modul erstellt wird.
Sie stoppt, sobald das Ende der wav-Datei erreicht ist. Zusätzlich wird der Parameter finished (beendet) auf 1 gesetzt. Der Geschwindigkeitsparameter (speed) kann verwendet werden, um die Datei schneller oder langsamer wiederzugeben, wobei 1.0 der normalen (aufgenommenen) Geschwindigkeit entspricht.
Sie werden dieses Modul normalerweise nicht benötigen, wenn Sie nicht selbstständige Anwendungen erstellen. Innerhalb von artsd existiert bereits ein Modul Synth_PLAY und ein zweites wird nicht funktionieren.
Das Synth_PLAY-Modul gibt ein Audio-Signal auf die Soundkarte aus. Die linken und rechten Kanäle sollten die normalisierten Eingangssignale für die Kanäle enthalten. Wenn sich das Eingangssignal nicht zwischen -1 und 1 befindet, werden zu hohe Amplituden abgeschnitten (clipping).
Wie bereits gesagt, darf es nur ein Synth_PLAY-Modul geben, da es direkt auf die Soundkarte zugreift. Verwenden Sie Busse, wenn Sie mehr als einen Audiostrom mischen wollen. Verwenden Sie das Modul Synth_AMAN_PLAY, um eine Ausgabe innerhalb von artsd zu erzeugen.
Anmerkung: Das Synth_PLAY-Modul übernimmt das gesamte Timing für die Struktur. Das bedeutet: Wenn Sie kein Synth_PLAY-Modul haben, haben Sie kein Timing und damit auch keine Klangausgabe. Sie benötigen also (genau) ein Synth_PLAY-Objekt.
Sie werden dieses Modul vermutlich niemals benötigen, es sei denn, Sie schreiben selbstständige Anwendungen. Innerhalb von artsd befindet sich bereits ein Synth_RECORD-Modul und ein zweites funktioniert nicht.
Das Synth_RECORD-Modul nimmt ein Signal von Ihrer Soundkarte auf. Die Eingangskanäle left (links) und right (rechts) enthalten die Eingangssignale von der Soundkarte (zwischen -1 und 1).
Wie bereits gesagt kann nur ein Synth_RECORD-Modul verwendet werden, da es direkt auf die Soundkarte zugreift. Verwenden Sie Busse, wenn Sie einen Audiodatenstrom an mehr als einer Stelle verwenden möchten. Verwenden Sie das Modul Synth_AMAN_RECORD, um einen Eingang innerhalb von artsd zu erzeugen. Damit das funktioniert, muss artsd mit Full-Duplex aktiviert gestartet werden.
Das Modul Synth_AMAN_PLAY gibt ein Ausgangssignal aus. Es sollte (nicht notwendigerweise) normalisiert (zwischen -1 und 1) sein.
Dieses Modul verwendet den Audiomanager, um festzulegen, wo das Signal wiedergegeben wird. Der Audiomanager kann mit Hilfe von artscontrol gesteuert werden. Um die Verwendung intuitiver zu gestalten, sollten Sie dem Signal einen Namen geben. Das können Sie, indem Sie den Parameter title (Titel) verwenden. Eine weitere Besonderheit des Audiomanagers ist die Fähigkeit, den letzten Wiedergabekanal eines Signals zu speichern. Dazu muss er die Signale unterscheiden können. Aus diesem Grund sollten Sie autoRestoreID einen eindeutigen Wert geben.
Das Modul Synth_AMAN_RECORD kann Daten einer externen Quelle (z.B. Line In / Mikrofon) innerhalb von artsd aufnehmen. Die Ausgabe ist ein normalisiertes Signal (zwischen -1 und 1).
Über den Audiomanager kann festgelegt werden, von wo das Signal aufgenommen wird. Der Audiomanager kann mit Hilfe von artscontrol gesteuert werden. Um die Verwendung intuitiver zu gestalten, sollten Sie dem Signal einen Namen geben. Das können Sie, indem Sie den Parameter title (Titel) verwenden. Eine weitere Besonderheit des Audiomanagers ist die Fähigkeit, den letzten Wiedergabekanal eines Signals zu speichern. Dazu muss er die Signale unterscheiden können. Aus diesem Grund sollten Sie autoRestoreID einen eindeutigen Wert geben.
Kann zum debuggen verwendet werden. Es gibt den Wert des Signals invalue in gleichbleibenden Abständen (etwa 1 Sekunde) zusammen mit einem von Ihnen festgelegten Kommentar aus. Auf diese Weise können Sie herausfinden, ob gewisse Signale in gewissen Bereichen bleiben oder ob sie überhaupt vorhanden sind.
Hiermit können Sie überprüfen, ob Ihre MIDI-Ereignisse aRts überhaupt erreichen.
Wenn ein MIDI_DEBUG aktiv ist, druckt artsserver etwa die folgenden Angaben:
201 100753.837585 on 0 42 127
202 101323.128355 off 0 42
Die erste Zeile teilt mit, das 100753ms (das sind 100 Sekunden) nach dem Start von MIDI_DEBUG das MIDI-Ereignis "Note an" auf Kanal 0 eingetroffen ist.Dieses Ereignis hatte die Lautstärke (velocity) 127, also den lautest möglichen Wert. Die nächste Zeile zeigt das zugehörige "Note aus"-Ereignis.[ TODO: Das funktioniert momentan noch nicht. Reparieren und durch den MIDI-Manager leiten].
Keiner der Oszillatoren in aRts benötigt eine Frequenz als Eingabe, sondern nur eine Position innerhalb der Welle. Die Position muss zwischen 0 und 1 liegen. Das wird für ein Standard-Synth_WAVE_SIN-Modul auf den Bereich 0 bis 2*Pi umgerechnet. Um eine bestimmte Frequenz zu erzeugen, benötigen Sie ein Synth_FREQUENCY-Modul.
Dieses Modul wird für Frequenzmodulation benötigt. Legen Sie die Grundfrequenz an den Frequenzeingang und ein anderes Signal an den Modulationseingang. Setzen Sie den Modulationswert (modlevel) etwa auf 0.3. Die Frequenz wird mit dem Modulationssignal moduliert. Ein interessantes Signal entsteht, wenn man ein rückgekoppeltes Signal verwendet, d.h. eine Kombination des verzögerten Ausgangssignals von Synth_FM_SOURCE (sie müssen es mit einem Oszillator verbinden, da es nur die Rolle von Synth_FREQUENCY übernimmt), und irgendein anderes Signal.
Arbeitet gut mit Synth_WAVE_SIN-Oszillatoren zusammen.
Sinusgenerator. Legen Sie ein Signal (pos) von Synth_FREQUENCY oder Synth_FM_SOURCE an den Eingang und am Ausgang liegt eine Sinusschwingung an. Das pos-Signal legt die Position in der Schwingung (Phasenverschiebung) im Bereich von 0..1 fest, was intern 0..2*Pi entspricht.
Dreieckgenerator. Legen Sie ein Signal (pos) von Synth_FREQUENCY oder Synth_FM_SOURCE an den Eingang und am Ausgang liegt eine Dreieckschwingung an. Das pos-Signal legt die Position in der Schwingung (Phasenverschiebung) im Bereich von 0..1 fest, was intern 0..2*Pi entspricht. Vorsicht: Das Eingangssignal muss im Bereich von 0..1 sein, damit ein gutes Ausgangssignal entsteht.
Rechteckgenerator. Legen Sie ein Signal (pos) von Synth_FREQUENCY oder Synth_FM_SOURCE an den Eingang und am Ausgang liegt eine Rechteckschwingung an. Das pos-Signal legt die Position in der Schwingung (Phasenverschiebung) im Bereich von 0..1 fest, was intern 0..2*Pi entspricht. Vorsicht: Das Eingangssignal muss im Bereich von 0..1 sein, damit ein gutes Ausgangssignal entsteht.
Abgeschwächte Sägezahnoszillation. Dieses Signal ist ähnlich zum Signal des Dreieckgenerators. Legen Sie ein Signal (pos) von Synth_FREQUENCY oder Synth_FM_SOURCE an den Eingang und am Ausgang liegt eine weiche Sägezahnschwingung an. Das pos-Signal legt die Position in der Schwingung (Phasenverschiebung) im Bereich von 0..1 fest, was intern 0..2*Pi entspricht. Vorsicht: Das Eingangssignal muss im Bereich von 0..1 sein, damit ein gutes Ausgangssignal entsteht.
Impulsgenerator - dieses Modul ist grundsätzlich ähnlich zum Rechteckgenerator (Synth_WAVE_RECT), bietet aber zusätzlich eine Möglichkeit, das Verhältnis von Maximumzeit zu Minimumzeit mit dem Eingang dutycycle einzustellen. Legen Sie ein Signal (pos) von Synth_FREQUENCY oder Synth_FM_SOURCE an den Eingang und am Ausgang liegt eine Impulsschwingung an. Das pos-Signal legt die Position in der Schwingung (Phasenverschiebung) im Bereich von 0..1 fest, was intern 0..2*Pi entspricht. Vorsicht: Das Eingangssignal muss im Bereich von 0..1 sein, damit ein gutes Ausgangssignal entsteht.
Dieses Modul reduziert den Dynamikbereich des Signals. Ein Kompressor ist nützlich, um die großen Lautstärkeschwankungen einer über ein Mikrophon redenden Person zu verringern.
Sobald das Eingangssignal einen bestimmten Pegel (den Grenzpegel) überschreitet, wird der Pegel reduziert. Jeder Pegelwert oberhalb des Grenzpegels wird mit einem Faktor, eine Zahl zwischen 0 und 1, multipliziert. Zum Abschluß wird das gesamte Signal mit dem Ausgangsfaktor multipliziert.
Die Argumente attack und release verzögern den Start und das Ende der Kompression. Das kann verwendet werden, um z.B. den lauten Beginn einer Basedrum zu hören. Das Argument wird in Millisekunden angegeben und ein Wert von 0 ms ist möglich, kann aber zu einem leichten Nebengeräusch führen.
Die Anwendung artsdsp, die weiter oben beschrieben wird, erlaubt den meisten Standardanwendungen, die direkt auf die Audio-Geräte zugreifen, unter aRts problemlos zu funktionieren. Die meisten Anwendungen, die den Enlightenment Sound Daemon (esd) verwenden, funktionieren ebenfalls, indem esd unter artsdsp gestartet wird.
Damit existiert eine gute Übergangslösung, um Anwendungen auf KDE zu portieren. Es erlaubt natürlich keiner Anwendung, direkt von aRts zu profitieren und alle Fähigkeiten von aRts, wie z.B. die Verwendung von Modulen und Multimediaströmen, zu verwenden. Wenn die Anwendung mehr können soll, als nur einfache Audiodateien abzuspielen, sollte man Unterstützung für aRts hinzufügen.
Die Anwendung kann dann viele Aufgaben an aRts übertragen -- sie kann die in aRts enthaltenen Funktionen verwenden, um Dinge wie unterschiedliche Codecs, Medienformate oder die Kontrolle der Audiogeräte.
Wenn Sie aRts verwenden, können Sie zwischen verschiedenen APIs auswählen. Die Entscheidung wird unter anderem davon abhängen, welche Art von Medienstrom (Klang, MIDI, CD-Audio usw.) Sie verwenden wollen, welche API-Fähigkeiten Sie benötigen und ob Sie in C++ programmieren. In den meisten Fällen sollte die Entscheidung abhängig von den benötigten Fähigkeiten klar sein
Anwendungen, die auf anderen Architekturen als KDE funktionieren sollen, können nicht davon ausgehen, das aRts immer vorhanden ist. Durch eine Plugin-Architektur können Sie geschickt verschiedene Multimediaumgebungen unterstützen. Wenn Sie das Plugin-API außerdem veröffentlichen und gut dokumentieren (besonders für nicht als Quelltext verfügbare Anwendungen), ermöglichen Sie auch anderen Entwicklern, ein aRts-Plugin für Ihre Anwendung zu schreiben.
Das aRts-Projekt benötigt einerseite Hilfe von Entwicklern, um existierende Multimedia-Anwendungen für aRts anzupassen, neue Anwendungen zu schreiben und die Möglichkeiten von aRts zu erweitern. Andererseits gibt es auch viele Aufgaben, die von Anderen übernommen werden können. Es werden Tester benötigt, die Fehlermeldungen liefern, Übersetzer, die die Anwendungstexte und die Dokumentation in andere Sprachen übersetzen, Graphiker, die Bitmaps (besonders für die artsbuilder-Module) entwerfen, Musiker, die Beispiele für aRts erzeugen, und Autoren, die die Dokumentation schreiben und korrigieren.
Viele Diskussionen zur Entwicklung von aRts finden in einer von zwei Mailinglisten statt. Hier werden neue Fähigkeiten und Umsetzungsideen diskutiert, hierhin kann man sich bei Problemen wenden.
Die KDE-Multimedia-Mailingliste ist für allgemeine Multimediathemen sowohl aRts als auch andere Multimediaprogramme wie Noatun und aKtion betreffend. Sie können sich auf der Internetseite http://www.kde.org/mailingslists.html oder durch eine E-Mail mit dem Betreff (subject) subscribe
an Ihre-E-Mail-Adresse
(kde-multimedia-request AT kde.org)
anmelden. Die Liste wird unter http://lists.kde.org archiviert.
In der aRts-Mailingliste geht es um aRts-spezifische Themen einschließlich der Nutzung von aRts außerhalb von KDE. Zur Anmeldung senden Sie eine E-Mail mit dem Inhalt subscribe
an Ihre-E-Mail-Adresse
(arts-request AT space.twc.de)
. Die Liste wird unter http://space.twc.de/~stefan/arts-archive archiviert.
For getting a consistent reading through all the sources, it is important to keep the coding style the same, all over the aRts source. Please, even if you just write a module, try to write/format your source accordingly, as it will make it easier for different people to maintain the source tree, and easier to copy pieces of from one source to another.
Qt™/Java™ style, that means capitalization on word breaks, and first letter always without capitalization; no underscores.
This means for instance:
createStructureDesc() updateWidget(); start();
Class members are not capitalized, such as menubar or button.
When there are accessing functions, the standard should be the MCOP way, that is, when having an long member foo
, which shouldn't be visible directly, you create:
foo(long new_value); long foo();
functions to get and set the value. In that case, the real value of foo
should be stored in _foo.
All classes should be wordwise capitalized, that means ModuleView
, SynthModule
. All classes that belong to the libraries should use the aRts namespace, like Arts::Soundserver
.
The implementations of MCOP classes should get called Class_impl
, such as SoundServer_impl
.
Parameters are always uncapitalized.
Local variables are always uncapitalized, and may have names like i
, p
, x
, usw. where appropriate.
One tab is as long as 4 spaces.
Normalerweise müssen in Ausdrücken keine Leerzeichen verwendet werden. Man kann sie allerdings zwischen Operatoren und ihren Operanden setzen. Falls man allerdings ein Leerzeichen direkt vor einen Operator (wie z.B. +) setzt, muss man auch nach dem Operator ein Leerzeichen setzen. Die einzige Ausnahme von dieser Regel bilden listenartige Ausdrücke (mit ,), bei denen man nur hinter dem "," ein Leerzeichen setzen darf. Man kann es aber auch weglassen.
Die folgenden Beispiele demonstrieren die sinnvolle Verwendung von Leerzeichen:
{ int a,b; int c, d, e; int f = 4; a=b=c=d+e+f; a = b = c = d + e + f; if(a == 4) { a = b = c = (d+e)/2; } while(b<3) c--; arts_debug("%d\n", c); }
Die folgenden Beispiele demonstrieren, wie Leerzeichen nicht verwendet werden dürfen. Bei Funktionsaufrufen, nach "if", "for", "switch" und so weiter dürfen keine Leerzeichen stehen.
{ // FALSCH: In einer Liste dürfen Leerzeichen nur nach dem "," stehen int a , b , c , d , e , f; // FALSCH: unsymmetrische Leerzeichen beim =-Operator a= 5; // FALSCH: "if" ist eine Funktion, daher dürfen keine Leerzeichen folgen if (a == 5) { } // FALSCH: nach "while" darf kein Leerzeichen folgen while (a--) b++; // FALSCH: Auf Funktionsnamen dürfen keine Leerzeichen folgen arts_debug ("%d\n", c); // FALSCH: keines von beiden ist ein member-Name Arts::Object o = Arts::Object::null (); }
Source files should have no capitalization in the name. They should have the name of the class when they implement a single class. Their extension is .cc
if they refer to Qt™/GUI independent code, and .cpp
if they refer to Qt™/GUI dependant code. Implementation files for interfaces should be called
, if Foo was the name of the interface. foo
_impl
IDL files should be called in a descriptive way for the collection of interfaces they contain, also all lower case. Especially it is not good to call an IDL file like the class itself, as the .mcopclass trader and type info entries will collide, then.
aRts entwickelt sich zu schnell, als das an dieser Stelle eine aktuelle Liste möglich wäre. Informationen zu Plänen finden Sie in der Datei TODO und in den Mailinglisten. Sie sind eingeladen, sich bei der weiteren Planung und Implementation zu beteiligen.
Dieses in der Entwicklung befindliche Dokument versucht einen Überblick darüber zu geben, wie neue Technologien in aRts integriert werden. Es behandelt folgende Themen:
Wie Schnittstellen funktionieren.
Codecs: Dekodierung von mp3-Strömen in eine Form, so dass sie als Daten verwendet werden können.
Video.
Threading.
Synchronisation.
Dynamische Erweiterung / Maskierung.
Dynamische Komposition.
GUI
MIDI
Dieses befindet sich in Arbeit. Es ist die Grundlage dafür, wie neue Technologien in aRts integriert werden können. Man kann einen ungefähren Eindruck davon bekommen, wie solche Probleme gelöst werden. Sie können alles, was Sie hier sehen, korrigieren.
Programme, die aRts-Technologie verwenden (bitte: koordinieren Sie Ihre Anstrengungen):
KPhone (Voice over IP)
Noatun (Video- / Audiospieler)
artscontrol (Kontrollprogramm des Soundservers)
Brahms (Musiksequencer)
Kaiman (KDE2-Medienspieler - kmedia2-kompatibel)
mpglib/kmpg (mpg Audio- und Videowiedergabetechnologie)
SDL (direkte Medienschicht für Spiele, noch nicht begonnen, aber wohl sinnvoll)
electric ears (Der Autor hat mich kontaktiert - Status unbekannt)
MCOP-Schnittstellen sind die Grundlage des Konzeptes von aRts. Sie sind das netzwerktransparente Äquivalent zu C++-Klassen. Sie sollten Ihre Programmentwicklung mit Schnittstellen durchführen. Die Schnittstellen bestehen aus vier Teilen:
Synchrone Ströme
Asynchrone Ströme
Methods
Attributes
Diese können beliebig kombiniert werden. Neue Technologien sollten als Schnittstellen definiert werden. Lesen Sie die Abschnitte über asynchrone Ströme und synchrone Ströme, sowie den über die KMedia2-Schnittstelle. Es sind gute Beispiele für ihre Funktionsweise
Schnittstellen werden als .idl
-Code spezifiziert und mit dem mcopidl-Compiler übersetzt. Sie leiten die
-Klasse ab, um es zu implementieren und verwenden Interfacename
_implREGISTER_IMPLEMENTATION(Interfacename_impl)
, um Ihre Objekt-Implementation in das MCOP-Objektsystem einzufügen.
Die kmedia2-Schnittstellen erlauben Ihnen zu ignorieren, dass wav-Dateien, mp3-Dateien und viele andere Formate aus Datenströmen bestehen. Stattdessen können Sie Methoden implementieren, um sie abzuspielen.
Sie können eine Routine zum Laden von wav-Dateien schreiben, um wav-Dateien abzuspielen ( als PlayObject), aber niemand sonst kann Ihren Code verwenden.
Asynchrone Ströme sind eine Alternative. Sie definieren eine Schnittstelle, die die Übergabe von Datenblöcken ermöglichen. So sieht das in MCOP aus:
interface Codec { in async byte stream indata; out async byte stream outdata; };
Natürlich können Codecs Attribute verwenden, um zusätzliche Daten, wie Formatinformationen bereitzustellen.
interface ByteAudioCodec { in async byte stream indata; out async byte stream outdata; readonly attribute samplingRate, bits, channels; };
Dieser ByteAudioCodec
kann zum Beispiel mit einem ByteStreamToAudio
-Objekt verbunden werden, um einen Audio-Strom zu formen.
Andere Codec-Typen können stattdessen Video-Daten direkt ausgeben, wie
interface VideoCodec { in async byte stream indata; out video stream outdata; /* note: video streams do not exist yet */ };
Meistens sollte ein Codec-Konzept verwendet werden anstatt einem „Sie wissen, wie man es abspielt und ich nicht“-Konzept, wie zum Beispiel WavPlayObject
es praktiziert. Natürlich muss sich jemand hinsetzen und einige Versuche durchführen, bevor ein API festgeschrieben werden kann.
Mein Vorschlag ist, Video als asynchronen Strom eines MCOP-Datentypen, der Bilder enthält, zu implementieren. Dieser Datentyp muss noch kreiert werden. Auf diese Weise könnten Plugins, die Video-Bilder verarbeiten in der gleichen Art wie Audio-Plugins verbunden werden.
Auf einige Dinge muss dabei geachtet werden:
Es gibt die Farbmodelle RGB und YUV.
Das Format sollte dem Strom aufgeprägt werden.
Synchronisation ist wichtig.
Es sollte möglich sein, die VideoFrame
-Klasse so zu reimplementieren, das Daten im gemeinsam genutzten Speicher gespeichert werden können. Damit würde auch Video-Streaming zwischen verschiedenen Prozessen ohne große Probleme möglich.
Bisher befindet sich für Videodaten von der Dekodierung bis zum Rendern alles im gleichen Prozess.
Ich habe einen Prototyp für ein Videostreaming-Programm implementiert, man kann ihn von hier herunterladen. Er muss nach einigen Experimenten in MCOP integriert werden.
Eine Render-Komponente, die XMITSHM (mit RGB und YUV) unterstützt, sollte programmiert werden. Martin Vogt arbeitet derzeit an so etwas.
Bisher ist MCOP Singel-Threaded. Für Video werden wir allerdings wohl nicht um Threading herum kommen. Dabei ist auf einige Dinge zu achten.
SmartWrappers - sie sind nicht threadsicher wegen nicht abgesicherter Referenzzählung und ähnlichen Dingen.
Dispatcher / I/O - nicht threadsicher.
Ich könnte mir allerdings vorstellen, einzelne Modules sowohl für synchrones als auch asynchrone Streaming threadsicher zu machen. Auf diese Weise - mit einem threadtauglichen Flusssystem - könnte der Signalfluss auf zwei oder mehr Prozessoren verteilt werden. Damit wäre dem Audiosystem in Bezug auf Mehrprozessorsysteme erheblich geholfen.
Wie es funktionieren könnte:
Das Flusssystem entscheidet, welche Module was berechnen sollen - das sind:
Video-Frames (mit der process_indata-Methode)
Synchrone Audio-Ströme (calculateBlock)
oder asynchrone Ströme, hauptsächlich Byte-Ströme
Module können diese Dinge in eigenen Threads berechnen. Für Audio ist es sinnvoll, Threads wiederholt zu benutzen (z.B. rendern in vier Threads auf vier Prozessoren, unabhängig davon, ob 100 Module ausgeführt werden). Für Video und Byte-Dekomprimierung könnte es sinnvoll sein, eine blockierende Implementierung in einem eigenen Thread zu haben, der mit dem Rest das MCOP-Systems durch das Flusssystem synchronisiert wird.
Module dürfen die Funktionen von MCOP (wie entfernte Aufrufe) während Thread-Operationen nicht verwenden
Video und MIDI (und Audio) müssen möglicherweise synchronisiert werden. Das funktioniert über Zeitmarken. Zeitmarken könnten zu asynchronen Strömen hinzugefügt werden, indem jedes Paket mit einer Zeitmarke versehen wird. Zwei Video-Frames müssen also als zwei Pakete gesendet werden (sie sind sowieso recht groß), damit sie unterschiedliche Zeitmarken haben können.
Audio sollte implizit Zeitmarken haben, da Audiodaten synchron wiedergegeben werden.
Es sollte folgendes möglich sein: Ein FX-Effekt ist zusammengesetzt aus mehreren einfachen Modulen. Ein FX-Effekt sollte aussehen wie ein normales MCOP-Modul (siehe auch Maskierung (masquerading)), obwohl er aus anderen Modulen besteht.
Das ist für aRts-builder erforderlich.
Alle GUI-Komponenten werden MCOP-Module sein. Sie sollten Attribute wie Größe (size), Name (label), Farbe (color), ... haben. Ein RAD-Builder (aRts-builder) sollte in der Lage sein, sie visuell zusammenzusetzen.
Das GUI sollte durch Sicherung der Attribute speicherbar sein.
MIDI sollte als asynchroner Strom implementiert werden. Es sollte zwei Möglichkeiten geben, zum Einen die Verwendung von normalen MCOP-Strukturen für die Typen und zum Anderen die Einführung von weiteren angepassten Typen.
Normale Strukturen sollten ausreichen, also etwas wie:
struct MidiEvent { byte b1,b2,b3; sequence<byte> sysex; }
Asynchrone Ströme sollten angepasste Typen unterstützen.
Das ist die Hauptseite für Informationen zum Thema KDE-Multimedia.
Das ist die Internetseite des aRts-Projektes.
Kapitel 14 dieses veröffentlichten Buches beschäftigt sich mit Multimedia allgemein und mit aRts. Es ist gedruckt oder im Internet mit Kommentaren verfügbar unter http://www.andamooka.org.
Diese Internetseite hat eine umfangreiche List zu Klängen und MIDI-Anwendungen unter Linux®.
In diesem Abschnitt finden Sie die Antworten auf einige häufig gestellte Fragen zu aRts.
13.1. | Unterstützt KDE die Ausgabe von Audio über meine Soundkarte? | ||||||||||||||||||||
KDE verwendet aRts für das Abspielen von Klängen und aRts verwendet die Kernel-Treiber von Linux®, entweder OSS oder die OSS-Emulation von ALSA. Wenn Ihre Soundkarte entweder von ALSA oder OSS unterstützt und richtig eingerichtet wurde (d. h. irgendeine andere Linux®-Anwendung kann Klänge ausgeben), funktionieren auch Klänge unter KDE. In speziellen Fällen können allerdings Probleme auftreten. Bei Problemen mit artsd kann man den Abschnitt über Hardware-Probleme lesen. Inzwischen werden verschiedene andere Computersysteme unterstützt. Es folgt eine komplette Liste der Soundausgabe mit der aktuellen Version von aRts. Falls das eigene Computersystem nicht aufgeführt ist, sollte man in Erwägung ziehen, aRts selbst zu portieren.
| |||||||||||||||||||||
13.2. | Ich kann keine | ||||||||||||||||||||
Überprüfen Sie, das artsd mit | |||||||||||||||||||||
13.3. | Ich kann Klänge hören, wenn ich mich als | ||||||||||||||||||||
Die Berechtigungen für die Datei
Sie können das gleiche erreichen, indem Sie in einem Terminal-Fenster Um den Zugriff auf Klänge auf bestimmte Benutzer einzuschränken, können Sie Gruppenrechte verwenden. Bei einige Linux®-Distributionen, z.B. Debian/Potato, gehört | |||||||||||||||||||||
13.4. | Das hilft für artsd, aber wie sieht es mit KMix, KMid, KsCD,usw. aus? | ||||||||||||||||||||
Es gibt verschiedene andere Geräte, auf die Multimedia-Anwendungen zugreifen. Sie können sie alle genau so behanden; Sie können sie entweder für alle Benutzer freigeben oder Gruppenrechte zur Zugriffskontrolle verwenden. Hier ist eine Liste der beteiligten Geräte, die aber unvollständig sein kann (falls es mehrere Geräte einer Art gibt, sind sie in der Form
| |||||||||||||||||||||
13.5. | Was kann ich tun, wenn artsd nicht startet oder während der Ausführung abstürzt? | ||||||||||||||||||||
Versuchen Sie zuerst die Standardeinstellungen in KControl (oder wenn Sie manuell gestartet haben, geben Sie keine zusätzlichen Optionen an, außer vielleicht Eine gute Methode, Problemen mit artsd auf den Grund zu gehen, ist, artsd manuell zu starten. Öffnen Sie dazu ein Konsole-Fenster und geben Sie folgenden Befehl:
Sie können außerdem die Option
Sie erhalten bestimmt einige nützliche Informationen über die Startprobleme. Oder, wenn artsd abstürzt, während etwas Bestimmtes passiert, können Sie auch dieses ausprobieren und Informationen erhalten „wie“ artsd abstürzt. Wenn Sie einen Fehlerbericht einsenden wollen, kann ein Backtrace mit gdb und/oder ein strace hilfreich sein. | |||||||||||||||||||||
13.6. | Kann ich artsd verschieben (die übersetzten Dateien in einen anderen Ordner verschieben)? | ||||||||||||||||||||
Das funktioniert nicht perfekt. Das Problem ist, das artswrapper die Position von artsd aus Sicherheitsgründen fest einkompiliert hat. Sie können allerdings die | |||||||||||||||||||||
13.7. | Kann man aRts mit gcc-3.0 kompilieren? | ||||||||||||||||||||
Kurze Antwort: Nein, aRts funktioniert nicht, wenn er mit GCC 3.0 kompiliert wird. Lange Antwort: In der offiziellen Version von GCC 3.0 gibt es zwei Fehler, die aRts betreffen. Der Erste, GCC-Fehler c++/2733 ist relativ harmlos (und betrifft Probleme mit Assembleranweisungen). Die Kompilation von convert.cc wird dadurch verhindert. Dieser Fehler ist im GCC-3.0-CVS bereits beseitigt und wird in einer Version GCC-3.0.1 oder höher beseitigt sein. Ein Notbehelf wurde zur CVS-Version von KDE/arts hinzugefügt. Der zweite GCC-3.0-Fehler, c++/3145 (Erzeugung falscher Anweisungen in einigen Fällen von mehrfacher virtueller Vererbung) ist kritisch. Anwendng wie artsd stürzen ab beim Start ab, wenn sie mit GCC-3.0 kompiliert werden. Auch wenn bereits kleine Fortschritte zur Beseitigung dieses Problems erzielt wurden, stürzt artsd immer noch oft und unerwartet ab. | |||||||||||||||||||||
13.8. | Welche Anwendungen funktionieren unter aRts? | ||||||||||||||||||||
Offensichtlich sind alle KDE-Anwendungen für aRts vorbereitet. Das schließt Folgende ein:
Einige KDE-Anwendungen, die bisher nicht Teil von KDE-Veröffentlichungen sind (z.B. aus kdenonbeta) unterstützen aRts auch. Das sind unter anderen:
Die folgenden Nicht-KDE-Anwendungen funktionieren ebenfalls mit aRts:
Die folgenden Anwendungen funktionieren nicht mit aRts:
Weitere Informationen finden Sie in den Antworten zu den Fragen im Abschnitt Nicht-aRts-Anwendungen. Dieser Abschnitt ist unvollständig -- wenn Sie Informationen zu weiteren unterstützten und nicht unterstützten Anwendungen haben, senden Sie sie an den Autor, damit sie eingefügt werden können. |
13.1. | Ich kann aRts-builder nicht verwenden. Er stürzt ab, wenn ich eine Struktur ausführen lassen will! |
Der wahrscheinlichste Grund für dieses Problem ist die Verwendung von alten Strukturen oder Modulen, die von KDE2-Version nicht mehr unterstützt werden. Unglücklicherweise bezieht sich die im Internet verfügbare Version auf aRts-0.3.4.1, die lange überholt ist. Der am häufigsten berichtete Fehler ist der folgende: Wenn Struktur ausführen in aRts-builder gestartet wird, erscheint die Fehlermeldung [artsd] Synth_PLAY: Audio-Subsystem wird schon verwendet. Sie sollten Synth_AMAN_PLAY an Stelle von Synth_PLAY verwenden und das Problem verschwindet. Weitere Informationen finden Sie in der Hilfedatei zu aRts-builder (betätigen Sie F1 in aRts-builder). Aktuelle Versionen von aRts-builder (KDE 2.1 Beta 1 und später) haben eine Menge von Beispielen, die Sie verwenden können. |
aRts Programm-Copyright 1998-2001 Stefan Westerfeld (stefan AT space.twc.de)
Dokumentation Copyright 1999-2001 Stefan Westerfeld (stefan AT space.twc.de)
und Jeff Tranter (tranter AT kde.org)
.
Übersetzung: Frank Schütte (F.Schuette AT t-online.de)
Diese Dokumentation ist unter den Bedingungen der GNU Free Documentation License veröffentlicht.
Alle Bibliotheken in aRts stehen unter der GNU Lesser General Public Lizenz. Die überwiegende Menge des aRts-Programms befindet sich in den Bibliotheken, einschließlich des gesamten MCOP und ArtsFlow. Damit können die Bibliotheken auch für nicht-freie/nicht-offene Anwendungen verwendet werden, falls gewünscht.
Es gibt einige Programme (wie artsd), die unter der GNU General Public Lizenz stehen. Da es unterschiedliche Einstellungen dazu gibt, ob es erlaubt ist, GPL-Anwendungen mit Qt™ zu linken, habe ich dieses explizit erlaubt, also zusätzlich zur GPL: Es ist auch erlaubt, dieses Programm mit der Qt™-Bibliothek zu linken; dabei wird Qt™ wie eine Bibliothek benutzt, die normalerweise zu einem Betriebssystem dazugehört, ob das im Einzelfall so ist oder nicht (hier noch der Originaltext: "permission is also granted to link this program with the Qt library, treating Qt like a library that normally accompanies the operating system kernel, whether or not that is in fact the case.")
Inhaltsverzeichnis
Um aRts benutzen zu können, müssen Sie es zuerst installieren und starten. Dazu gibt es zwei Möglichkeiten, die in den nächsten beiden Abschnitten beschrieben werden.
Der schnellste und einfachste Weg, um aRts auf Ihrem System zu installieren ist ein vorkompiliertes Binärpaket. Aktuelle Versionen von Linux®-Distributionen beinhalten KDE, und wenn es sich um KDE 2.0 oder später handelt, enthalten Sie aRts. Wenn KDE nicht in Ihrer Distribution enthalten ist, können Sie sie vielleicht von Ihrem Händler herunterladen. Alternativ sind manche Binärpakete auch von dritter Seite erhältlich. Achten Sie darauf, das die verwendeten Pakete kompatibel mit Ihrer Betriebssystemversion sind.
Eine grundlegende Installation von KDE enthält den Soundserver, damit die meisten Anwendungen Klänge abspielen können. Wenn Sie das gesamte Paket der Multimedia-Werkzeuge und -Anwendungen haben wollen, müssen Sie vermutlich noch einige optionale Pakete installieren.
Der Nachteil an vorkompilierten Binärpaketen ist, das sie vermutlich nicht die aktuellste Version von aRts enthalten. Das ist noch wahrscheinlicher, wenn Sie auf CD-ROM bereitstehen, da die Entwicklungsgeschwindigkeit von aRts und KDE so groß ist, das Medien wie die CD-ROM in der Regel nicht mithalten können. Sie werden auch feststellen, das, wenn Sie eine ungebräuchliche Distribution oder eine ungewöhnliche Konfiguration haben, Binärpakete nicht verfügbar sind. In solchen Fällen sind Sie auf die zweite Methode angewiesen.
Der zeitaufwendigste aber flexibelste Weg ist, aRts aus den Quelltexten selbst zu kompilieren. Das sichert, dass die kompilierte Version optimal für Ihre Systemeinrichtung geeignet ist und erlaubt Ihnen, die jeweils aktuelle Version zu verwenden.
Sie haben zwei Möglichkeiten -- entweder Sie installieren die letzte stabile Version, die zu KDE gehört, oder Sie nehmen die aktuellste (aber vielleicht instabile) Version direkt aus dem CVS-Repository des KDE-Projektes. Die meisten Benutzer, die nicht für aRts entwickeln, sollten die stabile Version verwenden. Sie können sie von ftp://ftp.kde.org oder von einem der vielen Mirrors herunterladen. Wenn Sie für aRts entwickeln, sollten Sie die CVS-Version verwenden. Wenn Sie aRts ohne KDE verwenden wollen, können Sie einen selbstständigen Entwicklugsschnappschuß (Snapshot) von http://space.twc.de/~stefan/kde/arts-snapshot-doc.html herunterladen.
Weiterhin gilt, wenn Sie aus dem CVS kompilieren, es befinden sich einige Komponenten von aRts (d. h. die grundlegenden Komponenten einschließlich des Soundservers) in dem CVS-Modul kdelibs, während weitere Komponenten (z. B. artsbuilder) sich im Modul kdemultimedia befinden. Das kann sich in Zukunft ändern. Im Modul kmusic finden Sie ebenfalls ein Version; es handelt sich dabei um die alte (vor-KDE 2.0) Version, die jetzt überholt ist.
Die Vorbedingungen für die Kompilierung von aRts sind die gleichen wie für den Rest von KDE. Die configure-Skripte sollten Ihr Sytem identifizieren und anzeigen, falls benötigte Komponenten fehlen. Vergewissern Sie sich, das Ihr Sound-Treiber funktioniert (entweder der OSS/Free Kerneltreiber, der OSS-Treiber von 4Front Technologies oder der ALSA-Treiber mit OSS-Emulation).
Weitere Informationen zum Herunterladen und installieren von KDE (einschließlich aRts) finden Sie in der KDE FAQ.
Advanced Linux® Sound Architecture; ein Linux®-Soundkartentreiben, der aktuell nicht Bestandteil des Standard-Kernel-Quelltextes ist.
Analog-Real-Time Synthesizer, der Name der Multimedia-Architektur/der Multimedia-Bibliothek/des Multimedia-Werkzeuges, das vom KDE-Projekt verwendet wird (beachten Sie die Großschreibung)
Berkeley Software Distribution; bezieht sich auf jedes von einigen freienUNIX®-kompatiblen Betriebssystemen, die von BSD UNIX® abstammen.
Common Object Request Broker Architecture;, ein Standard für objektorientierte entfernte Programmausführung.
Concurrent Versions System;, ein Software-Managementsystem, das von vielen Softwareprojekten einschließlich KDE und aRts verwendet wird.
Fast Fourier Transformation, ein Algorithmus, um Daten aus dem Zeitraum in den Frequenzraum zu transformieren; das wird für Signalverarbeitung häufig benötigt.
Die Fähigkeit einer Soundkarte gleichzeitig Daten abzuspielen und aufzunehmen.
GNU General Public Lzenz, die von der Free Software Foundation festgelegt wurde für die Veröffentlichung von freier Software.
Graphische Benutzeroberfläche
Interface Definition Language, ein programmiersprachenunabhängiges Format zur Definition von Schnittstellen (Methoden und Daten).
K Desktop Environment, ein Projekt zur Entwicklung einer freien graphischen Benutzeroberfläche für UNIX®-kompatible Systeme.
GNU Lesser General Public Lizenz, eine Programmlizenz, die von der Free Software Foundation aufgeschrieben wurde, um die Bedingungen für die Veröffentlichung von freier Software festzulegen. Sie ist weniger restriktiv als die GPL und wird häufig für Programmbibliotheken verwendet.
Multimedia COmmunication Protocol, das Protokoll, das für die Kommunikation der Teile von aRts verwendet wird, ähnlich zu CORBA, aber einfacher und für Multimedia optimiert.
Musical Instrument Digital Interface, ein Standardprotokoll zur Kommunikation von elektronischen Musikinstrumenten, häufig auch das Dateifomat zur Speicherung von MIDI-Befehlen.
Open Sound System, die Soundtreiber, die zum Linux®-Kernel gehören (manchmal auch OSS/Free) oder eine kommerzielle Version, die von 4Front Technologies vertrieben werden.
Would you like to make a comment or contribute an update to this page?
Send feedback to the KDE Docs Team