*************************************************************************** * Wie schreibt man externe Player? * *************************************************************************** © 1992 by Delirium Datum 12.09.1992 Das externe Player Konzept DeliTracker unterstützt sog. externe Player. Das sind Player, die in einem speziellen Format vorliegen und von DeliTracker nachgeladen werden können. Ist ein solcher Player geladen worden, erkennt und spielt DeliTracker die Module, die von diesem Player unterstützt werden. Derzeit kann DeliTracker maximal 64 externe Player nachladen. Dies sollte jedoch erst mal für eine Weile reichen. Beim Start von DeliTracker werden alle Player, die sich im Verzeichnis 'DeliPlayers' (b.z.w im Configfile gespeicherten Playerpfad) befinden, geladen. Zusätzlich haben Sie jederzeit die Möglichkeit einen weiteren bzw. neueren Player nachzuladen. Diese Player verleihen DeliTracker seine ungeahnte Flexibilität. Externe Player sind Executables, d.h. sie werden reloziert. Am Anfang besitzen sie die charakteristische Playerstruktur. Sie wird mit dem Macro aus dem Includefile 'misc/deliplayer.i' erzeugt. Es ist relativ einfach, eine Replayroutine an DeliTracker anzupassen. Das 'einzige' was Sie tun müssen ist ein wenig Interfacecode zu schreiben. Dies ist aber halb so wild, denn DeliTracker stellt ihnen viele hilfreiche Routinen zur Verfügung. 1. Grundvoraussetzungen Um ein neues Soundsystem anzupassen, benötigen Sie zum einen den Sourcecode oder ein linkbares Objektfile der Replayroutine, zum anderen mindestens ca. 5 Module zum testen der Funktionstüchtigkeit des Players. 2. Schematischer Aufbau eines externen Players { Playerheader TagArray Interfacecode Replaycode+Daten } 3. Funktionen, die der externe Player zur Verfügung stellt Das PLAYERHEADER Macro generiert den Header, der das File als externen Player identifiziert. Dieses Macro muß angegeben werden und ganz am Anfang des Players stehen. Als einziger Parameter ist ein Pointer auf ein Tag Array einzutragen, in dem alle Funktionen stehen, die der Player DeliTracker zur Verfügung stellt. Bei allen Funktionsaufrufen außer der Interruptroutine (DTP_Interrupt) enthält a5 die Base. Globale Variablen können dadurch adressiert werden. (Siehe auch 'misc/deliplayer.i'). Da DeliTracker vor Aufruf einer Playerroutine (außer bei DTP_Interrupt) alle Register sichert, dürfen diese verändert werden. PLAYERHEADER tagarray Pointer auf ein Tag Array, das mit TAG_DONE abgeschlossen sein muß. Tags, bei denen ti_Data NULL ist, werden ignoriert. Außerdem können folgende SystemTags verwendet werden: TAG_DONE TAG_IGNORE TAG_MORE TAG_SKIP Derzeit existieren folgende Tags: DTP_CustomPlayer dieser Tag deklariert einen Player als Customplayer ti_Data darf nicht NULL sein ! DTP_RequestDTVersion damit kann man sicherstellen, daß mindestens eine bestimmte Version von DeliTracker vorhanden ist. Dieser Tag muß angegeben werden, wenn bei den DeliTrackerGlobals neue Funktionspointer hinzugekommen sind und diese vom Player benutzt werden. ti_Data ist dabei die Playerrevision. DTP_RequestV37 wenn dieser Tag vorhanden ist, wird der Player nur noch von der Kick 2.0 Version von DeliTracker geladen. (d.h. dtg_GadToolsBase ist gültig) DTP_PlayerVersion Tag, der die Revisionsnummer des Players enthält. Bei zwei Playern mit dem gleichen Playernamen wird derjenige mit der größeren Revisionsnummer geladen. DTP_PlayerName ti_Data enthält den Pointer auf den Namen des Players. Dieser String kann zwar beliebig lang sein aber zur Zeit werden nur die ersten 24 Zeichen angezeigt. Dieser Tag muß existieren ! DTP_Creator Pointer auf den Namen des Autors. Dieser wird im Setup- fenster im Playerinfofeld angezeigt. Dieser String kann $A als Zeilenumbruch enthalten. DTP_Check1 Pointer auf eine Modulerkennungsroutine, die aufgerufen wird, wenn 1024 Bytes des Moduls geladen sind. Wird das Modul erkannt, liefert sie d0=0, ansonsten d0<>0. DTP_Check2 Pointer auf eine Modulerkennungsroutine, die aufgerufen wird, wenn das komplette Modul geladen und evtl. entpackt ist. Wird das Modul erkannt, liefert sie d0=0, ansonsten d0<>0. DTP_ExtLoad Pointer auf eine optionale Laderoutine für Module. Ist kein Fehler aufgetreten, wird d0=0 zurückgegeben, sonst d0<>0. Hinweis: Achten Sie darauf, daß im Fehlerfall alle allocierten Ressourcen (Memory, Locks, ...) wieder frei- gegeben werden, da dann keine weiteren Playerfunktionen mehr angesprungen werden. DTP_Interrupt Pointer auf eine Interruptroutine, die mittels eines Timerinterrupts standardmäßig alle 1/50 sec aufgerufen wird. Dies ist die Standardmethode, um mit der richtigen Abspielgeschwindigkeit auf im PAL/NTSC/Productivity Videomodus zu spielen. Wenn keine DTP_faster/DTP_slower Tags angegeben sind, übernimmt DeliTracker dies durch Verändern der Interruptfrequenz. Dieser Tag kann auch nicht existieren, dann müssen aber DTP_StartInt/DTP_StopInt vorhanden sein ! DTP_Stop Pointer auf eine optionale Stoproutine. Wenn dieser Tag nicht vorhanden ist, verfährt DeliTracker folgendermaßen: Interrupt stoppen (DTP_StopInt) Sound Cleanup (DTP_EndSnd) Song initialisieren (DTP_InitSnd) Ansonsten hat diese Routine die Aufgabe, einen evtl. spielenden Song anzuhalten und so zu initialisieren, daß dieser beim nächsten Starten des Interrupts von Anfang an zu spielen beginnt. DTP_Config Pointer auf eine optionale Initialisierungsroutine. Diese wird nur einmal unmittelbar nach dem Laden des Players aufgerufen. Mögliche Anwendungen: Laden einer player- spezifischen Konfigurationsdatei. DTP_UserConfig Pointer auf eine optionale Initialisierungsroutine. Diese wird nur dann aufgerufen, wenn der User den Player im Setupfenster anwählt und das 'Config' Gadget drückt. Mögliche Anwendungen: Öffnen eines Fensters zum Setzen weiterer Optionen (Instrumentenpfad, ...) und abspeichern einer playerspezifischen Konfigurationsdatei. DTP_SubSongRange Dieser Tag sollte angegeben werden, wenn der Player Multi- Module unterstützt. ti_Data zeigt dabei auf eine Routine, die als Returnwert in d0 die minimale und in d1 die maximale Subsongnummer zurückgeben muß. DTP_InitPlayer Pointer auf eine optionale Initialsierungsroutine, die aufgerufen wird, wenn ein Modul erfolgreich geladen wurde. Tritt kein Fehler auf, liefert sie d0=0, ansonsten d0<>0. Hier muß die Allocation der Audiokanäle stattfinden ! (DeliTracker stellt dafür eine eigene Routine zur Verfügung) Falls der Player Multi-Module unterstützt, muß hier dtg_SndNum(a5) auf die erste Subsongnummer gesetzt werden. Falls eine Routine für DTP_SubSongRange existiert, macht DeliTracker das automatisch (d.h. die Initialisierung von dtg_SndNum(a5) kann weggelassen werden). DTP_EndPlayer Pointer auf eine optionale Cleanuproutine, die aufgerufen wird, wenn das Modul aus dem Speicher entfernt wird. Hier muß die Freigabe der Audiokanäle stattfinden ! (DeliTracker stellt dafür eine eigene Routine zur Verfügung) DTP_InitSound Pointer auf eine optionale Initialsierungsroutine. Diese muß das Modul initialisieren, so daß beim Starten des Interrupts der Song von Anfang an zu spielen beginnt. DTP_EndSound Pointer auf eine optionale Cleanuproutine. Diese kann z.B. die Lautstärkeregister und die Audio-DMA rücksetzen. DTP_StartInt Pointer auf eine Initialsierungsroutine, die existieren muß, wenn DTP_Interrupt nicht vorhanden ist. In diesem Fall muß hier der Sound gestartet werden. DTP_StopInt Pointer auf eine Cleanuproutine, die existieren muß, wenn DTP_Interrupt nicht vorhanden ist. In diesem Fall muß hier der Sound gestoppt werden. DTP_Volume Pointer auf eine Funktion, die die Lautstärke neu setzt. Die Funktion wird aufgerufen, wenn die Volume neu gesetzt wird (Slider, ARexx-Kommando) und beim Initialisieren des Moduls vor DTP_InitSnd. Die Mastervolume steht in dtg_SndVol(a5). Die Mastervolume ist dabei der maximale Volumewert. Die effektive Volume errechnet sich also durch: VOL_eff = ( ( dtg_Volume(a5) * modulevolume ) >> 6 ) Näheres siehe Beispielsourcen. DTP_Balance Pointer auf eine Funktion, die die Balance neu setzt. Die Funktion wird aufgerufen, wenn die Balance neu gesetzt wird (Slider, ARexx-Kommando) und beim Initialisieren des Moduls vor DTP_InitSnd. Die Balance für die linken Kanäle steht in dtg_SndLBal(a5), für die rechten Kanäle in dtg_SndRBal(a5). Hinweis: Alle Player die Balance unterstützen können auch Volume ! Man verwendet dann die gleiche Routine zum setzen der Volume&Balance. Die linke Volume errechnet sich wie folgt: ( ( dtg_Volume(a5) * dtg_SndLBal(a5) ) >> 6 ) Entsprechendes gilt für rechts. DTP_Faster Pointer auf eine Funktion, die den Abspielvorgang beschleunigt. DTP_Slower Pointer auf eine Funktion, die den Abspielvorgang verlangsamt. DTP_NextPatt Pointer auf eine Funktion, die den Patternzeiger um eins erhöht. DTP_PrevPatt Pointer auf eine Funktion, die den Patternzeiger um eins erniedrigt. DTP_NextSong Pointer auf eine Funktion, die Subsongnummer auf den nächsten Subsong setzt und diesen spielt. (Falls vorhanden) DTP_PrevSong Pointer auf eine Funktion, die Subsongnummer auf den vorhergehenden Subsong setzt und diesen spielt. (Falls vorhanden) 4. Funktionen, die DeliTracker dem externen Player zur Verfügung stellt Eine Funktion wird wie folgt aufgerufen: move.l dtg_XXX(a5),a0 jsr (a0) Alle folgenden Funktionen außer dtg_SongEnd verwenden d0/d1/a0/a1 als Scratchregister. In a5 muß bei allen Aufrufen (außer dtg_SongEnd) die Base stehen. Derzeit existieren folgende Funktionen: dtg_GetListData SYNOPSIS memory size = dtg_GetListData(number) a0 d0 d0.l FUNCTION Liefert Adresse und Länge eines mit LoadFile() geladenen Files. INPUTS number - Nummer eines Files beginnend mit 0 für das vom User selektierte File. RESULT memory - ein Pointer auf die Startadresse des Files im Speicher oder NULL im Fehlerfall. size - Länge des Files in Bytes bzw. 0 im Fehlerfall. dtg_LoadFile SYNOPSIS success = dtg_LoadFile(name) FUNCTION Lädt und entpackt ggf. das angegebene File ins Chip-Memory. (Hinweis: diese Funktion ergänzt automatisch, falls das File mit dem angegebenen Namen nicht geöffnet werden konnte '.pp') INPUTS name - der Filename steht in einem internen Buffer (seine Adresse steht in dtg_PathArray) RESULT success - alles ok d0.l=0, sonst d0.l<>0. dtg_CopyDir SYNOPSIS dtg_CopyDir() FUNCTION Kopiert das Directory des von User angewählten Files an das Ende des Strings, auf den dtg_PathArray(a5) zeigt. dtg_CopyFile SYNOPSIS dtg_CopyFile() FUNCTION Kopiert den Filenamen des von User angewählten Files an das Ende des Strings, auf den dtg_PathArray(a5) zeigt. dtg_CopyString SYNOPSIS dtg_CopyString(string) a0 FUNCTION Kopiert den String, auf den das Register a0 zeigt, an das Ende des Strings, auf den dtg_PathArray(a5) zeigt. INPUTS string - der Pointer auf den anzuhängenden String steht in a0 dtg_AudioAlloc SYNOPSIS success = dtg_AudioAlloc() FUNCTION Belegt alle Audiokanäle. RESULT success - alles ok d0.l=0, sonst d0.l<>0. dtg_AudioFree SYNOPSIS dtg_AudioFree() FUNCTION Gibt die mit dtg_AudioAlloc belegten Audiokanäle wieder frei. dtg_StartInt SYNOPSIS dtg_StartInt() FUNCTION Startet den Soundinterrupt. (Falls er nicht schon läuft.) Falls DTP_Interrupt existiert, startet DeliTracker einen Timerinterrupt, ansonsten wird DTP_StartInt aufgerufen. dtg_StopInt SYNOPSIS dtg_StopInt() FUNCTION Stoppt den Soundinterrupt. (Falls er nicht schon angehalten ist.) Falls DTP_Interrupt existiert, stoppt DeliTracker seinen Timerinterrupt, ansonsten wird DTP_StopInt aufgerufen. dtg_SongEnd SYNOPSIS dtg_SongEnd() FUNCTION Signalisiert DeliTracker, daß das Modul einmal komplett gespielt wurde. Diese Funktion verändert keine Register und darf auch von Interrupts aufgerufen werden. dtg_CutSuffix SYNOPSIS dtg_CutSuffix() FUNCTION Entfernt am Ende des Strings, auf den dtg_PathArray(a5) zeigt ggf. die Endung '.pp' 5. Die Modul-Erkennung Damit DeliTracker die einzelnen Module unterscheiden kann, befindet sich in jedem Player eine Erkennungsroutine, die auf ein zugehöriges Modul testet. Diese Routine prüft auf Stellen im Modul, die bei allen Modulen eines Players gleich sind. So z.B. auf 'M.K.' bei Offset $438 im NoiseTracker-Player. Bei Sound- und NoiseTracker ist nur das Modul abgespeichert, z.B. bei MarkII hängt vor jedem Modul noch die Replayroutine. Bei so einem Modul müssen Sie auf signifikante Assemblerbefehle testen. Ein Vergleich auf ein oder zwei Sprungbefehle, egal ob bra oder jmp, ist hier nicht ausreichend, da bei vielen Modulen dieses Typs Sprungtabellen am Anfang stehen und der Player möglicherweise das falsche Modul erkennen könnte, was in den meisten Fällen zum Absturz führt. Der Player MUß GENAU EINE Checkroutine verwenden. Daraus ergeben sich zwei Grundtypen von Playern. 1. Typ eins Player - in der Regel etwas komplexer Bei ihnen ist die Check1 Funktion implementiert. Vorteil: Es lassen sich auch Player realisieren, die das Modul selbst nachladen. Nachteil: Gepackte Files werden nicht unterstützt. Dieser Player sollte nur für Härtefälle verwendet werden. (z.B: FTM, IFF-8SVX Player, der das Sample während dem Spielen nachlädt, ...) 2. Typ zwei Player - die einfachere Variante Bei ihnen ist die Check2 Funktion implementiert Vorteil: Das Modul kann gepackt sein, der Player bemerkt davon nichts. Nachteil: Das Modul ist in jedem Fall im Chip-Memory. Es sollte im Normalfall dieser Playertyp verwendet werden. Egal ob Typ eins oder zwei, wenn der Player das Modul erkannt hat, muß er in d0.l=0 zurückliefern, wenn nicht d0.l<>0. 6. Player-Interrupt Hier gibt es auch grob eine Einteilung in zwei verschiedene Typen. 1. Player die den DeliTracker Interrupt verwenden Vorteil: Der Player ist vom Videomodus unabhängig. Besitzt automatisch die faster/slower Funktion. Kein Aufwand für den Interrupt (Code + Interruptstruktur). Ist kompatibel zum serial.device Nachteil: Der Interrupt kommt nicht synchron zum VBlank. (Dies führt nur in den seltensten Fällen zu Problemen.) 2. Player die ihren eigenen Interrupt erzeugen. Vorteil: Es können andere Interruptquellen benutzt werden (z.B. AudioIRQ). Nachteil: Erhöhter Aufwand, bei VBlank nicht mehr unabhängig vom Videomodus. Wenn ein eigener Timerinterrupt verwendet wird, sollte die CIAB (wg. OS2.0) und die entsprechenden Resourcefunktionen verwendet werden. Außerdem ist es sehr sinnvoll die Replayroutine nicht direkt im CIA-B Timer- Interrupt abspielen zu lassen, sondern im Timerinterrupt einen Soft Interrupt mittels Cause() zu generieren. In diesem SoftInt kann man dann die eigentliche Replayroutine ablaufen lassen. So hat man den Vorteil, daß man wesentlich kompatibler zum serial.device ist. Dies liegt daran, daß SoftInt eine niedrigere Priorität als der der RBF (Read Buffer Full) Interrupt hat d.h. erst wird der serielle Port bedient, dann erst die Replayroutine. Es wird davor gewarnt, direkt in die Interruptvektoren zu schreiben! Zur Erinnerung: vom Betriebssystem werden die Funktionen AddIntServer() und SetIntVector() zur Verfügung gestellt ! 7. Custom Module Dies sind keine Module im herkömmlichen Sinn, sondern im wesentlichen Player, die ein Spezialmodul beinhalten. Mit diesem Format können Sie auch die ausgefallensten Module mit DeliTracker abspielen. Sollten für den Player jedoch mehrere Module existieren, ist es in dem Fall ratsam, einen richtigen Player zu schreiben. Schematischer Aufbau eines Custom Moduls { Playerheader TagArray Interfacecode Replaycode+Daten MODUL } Mit dem Tag DTP_CustomPlayer wird ein Player als Customplayer deklariert, folgende Tags sind dann bedeutungslos: - DTP_PlayerVersion - DTP_PlayerName - DTP_Creator - DTP_Check1 - DTP_Check2 - DTP_ExtLoad - DTP_Config - DTP_UserConfig 8. Checkliste Dies ist eine kleine Checkliste für Player/Custommodul Anpassung, die erfüllt werden muß, damit der Player systemkonform ist und einwandfrei funktioniert. [ ] ist die Checkroutine präzise genug ? [ ] werden die Audiokanäle richtig belegt und freigegeben ? [ ] wird aller allocierter Speicher freigegeben ? [ ] werden alle Locks wieder freigegeben ? [ ] enforcer und mungwall - Test bestanden ? [ ] werden alle möglichen Fehler korrekt abgefangen ? [ ] wurde der Player mit 1.2, 1.3 und 2.0 getestet ? [ ] spielt der Player in allen Videomodi korrekt ? 9. Diverses Der Player sollte den Zustand der LED unbeeinflußt lassen, da im Setupwindow ein entsprechende Funktion existiert. Problemecke Symptom mögliche Ursache Beseitigung Absturz a5 überschrieben Register nicht gesichert falsche oder zu späte Initialisierung Modul zu neu für den Player präziserer Check klingt schräg Audiodaten nicht im Chipmem Player ins Chipmem falsche Initialisierung Modul zu neu für den Player präziserer Check Player benötigt bestimmte Werte in zusätzliche Initialisierung einigen Registern unverträglicher Videomodus kein Ton Audio-DMA abgeschaltet bei >68000 Player schreibt direkt auf Betriebssystemfuntionen Interruptvektoren benutzen fast keine fehlerhaftes Interrupt-Handling bei VBlank-IRQ: freie CPU-Zeit Z-Flag muß am Ende gesetzt sein! lahmes System 68000/020/030 zu langsam 68040 kaufen ;-)