Beim Erzeugen mehrerer KlĂ€nge oder Tonfolgen hintereinander tritt das Problem auf, daĂ die ĂbergĂ€nge zwischen den einzelnen Tönen sehr unsauber klingen, wenn man dabei immer denselben Frame verwendet.
Diese Problematik kann man umgehen, indem man die einzelnen Töne in verschiedene Frames packt und zwischen diesen umschaltet. Atari hat sich dazu ein recht elegantes Verfahren einfallen lassen.
Doch zunÀchst soll ein anderer Bereich kurz beleuchtet werden, nÀmlich die
Stereoklangerzeugung
Viele werden sich sicher schon gefragt haben, wie man es bewerkstelligt, auf jedem Stereokanal einen eigenen Klang oder eine eigene Tonfolge zu generieren. DaĂ man die LautstĂ€rke der beiden KanĂ€le getrennt regeln kann, das kann doch nicht alles sein! Einige erinnern sich vielleicht noch an das âSound Mode Control'-Register, mit dem man in den Stereomodus schalten kann. Also frisch ans Werk und mit gesetzten âSound Mode Control'-Register einen Frame ausgegeben!
Leider ist es nicht ganz so einfach. Der Soundchip holt sich nĂ€mlich im Stereomodus die Daten 16-bitweise aus dem Frame. Das High-Byte des gelesenen Wortes wird dann dem linken Kanal zugeordnet, das Low-Byte dem rechten Kanal. Man muĂ also dafĂŒr Sorge tragen, daĂ die Klangdaten der beiden KanĂ€le richtig in den Frame eingetragen werden.
FĂŒr diejenigen, die sich das zuerst an einem Beispiel ansehen wollen, ist das Listing âSTEREO.Câ gedacht. Dort werden die beiden KanĂ€le mit unterschiedlichen KlĂ€ngen angesteuert.
So, und nun zurĂŒck zum eigentlichen Thema dieser Folge. Nehmen wir einmal folgende Aufgabenstellung als gegeben an: zwei Frames der LĂ€nge 1 und 2 Sekunden sollen nacheinander abgespielt werden. Dies lĂ€Ăt sich noch recht einfach bewerkstelligen.
Bevor der Soundchip mit dem Abarbeiten eines Frames beginnt, speichert er die Werte aus dem âFrame Base Addressâ-und dem âFrame End Addressâ-Register intern zwischen und beginnt dann erst mit der Tonausgabe. Das bedeutet, daĂ man, nachdem der erste Frame aktiviert ist, gleich die Adresse des zweiten Frames eintragen kann. Der Soundchip gibt den ersten Frame aus und registriert an dessen Ende, daĂ bereits ein neuer Frame eingetragen ist und startet diesen.
So weit, so gut - das obige Verfahren geht aber in die Hose, wenn man nach dem zweiten Frame noch einen dritten ausgeben möchte. Dann kann es passieren, daĂ der dritte Frame in die Register eingetragen wird, bevor der erste fertig ausgegeben ist. Damit wĂŒrden jedoch die Daten des zweiten Frames ĂŒberschrieben werden.
Langer Rede, kurzer Sinn - eine Möglichkeit muĂ her, mit der man feststellen kann, wann die Ausgabe eines Frames beendet ist. Dazu existiert am DMA-Soundchip ein Signal namens âDMA Sound Activeâ. Dieses ist 1, wenn ein Frame ausgegeben wird, und 0. wenn gerade keine Ausgabe erfolgt. Das Signal ist mit dem externen Eingang von Timer A verbunden. Setzt man den Timer A in den Event-Count-Mode, und fĂŒllt man den AbwĂ€rtszĂ€hler von Timer A mit dem Wert 1, so passiert folgendes:
WĂ€hrend der Ausgabe eines Frames ist das Signal am Eingang von Timer A 1. Am Ende des Frames geht das Signal von 1 auf 0. Das veranlaĂt den AbwĂ€rtszĂ€hler, seinen Wert um 1 auf 0 zu erniedrigen. Beim Wechsel von 1 auf 0 des AbwĂ€rtszĂ€hlers generiert Timer A einen Interrupt. Genau dieser Interrupt wird dazu benutzt, zwischen den einzelnen Frames umzuschalten.
Um zu verhindern, daĂ durch die Zeit, die fĂŒr das EintrĂ€gen der Frame-Adressen benötigt wird, bei einer Umschaltung zwischen den Frames eine LĂŒcke entsteht, ist man bei Atari noch einen Schritt weiter gegangen. Das âDMA Sound Activeâ-Signal geht genau dann von 1 auf 0, wenn sich der Soundchip das letzte Byte aus dem Frame geholt hat. Da die Bytes aber intern in einem 64-Bit-FIFO-Speicher gepuffert werden, wird der Interrupt schon 8 Bytes vor Ende des Frames generiert. Die Interrupt-Routine hat folglich genauso lange Zeit, die Adressen des neuen Frames einzutragen, wie der Soundchip benötigt, 8 Bytes abzuspielen. Das sind im kritischsten Fall (bei 50066 FIz Sampling-Rate) 160 ”s im Monomodus und 80 ”s im Stereomodus.
Zur Auflockerung des ganzen folgt auch hier wieder ein Beispiel. Die Anwendung besteht dabei aus den Listings âFRM.Sâ, FRM.Hâ und âFRAMES.Câ. âFRM.Sâ beinhaltet folgende von Turbo-C zugĂ€ngliche Funktionen.
frm_stop schaltet die Tonausgabe ab und sperrt den Timer A-Interrupt. frm_start erwartet als Parameter einen Zeiger auf eine FRAME-Struktur. In dieser sind alle Frames zusammengefaĂt, die nacheinander abgespielt werden sollen (siehe âFRM.Hâ). frm_start installiert die Interrupt-Routine f_next, die der Umschaltung zwischen den Frames dient, und setzt den Timer A auf EreigniszĂ€hlung. Danach wird der erste Frame in die Soundchip-Register eingetragen. Der Rest, sprich die Umschaltung, lĂ€uft dann ganz von selbst ab.
In den Routinen ist noch eine kleine Besonderheit enthalten, nĂ€mlich die Möglichkeit, einen Frame nicht nur einma1, sondern mehrmals hintereinander abzuspielen. Dies geschieht, indem man den AbwĂ€rtszĂ€hler von Timer A mit der gewĂŒnschten Zahl an Wiederholungen fĂŒllt und das âSound DMA Controlâ-Register auf Wiederholung setzt. Timer A generiert jetzt den Interrupt erst dann, wenn sein AbwĂ€rtszĂ€hler auf 0 heruntergezĂ€hlt hat.
Noch ein Wort zur FRAME-Struktur. Im Wert anz_frames muĂ die Anzahl der Frames stehen, zwischen denen umgeschaltet werden soll. Dieser Wert wird automatisch von der Funktion frm_alloc gefĂŒllt. Des weiteren legt frm_alloc Speicherplatz fĂŒr die Frames an. Jeder Frame besitzt eine eigene FSOUND-Struktur (siehe âFRM.Hâ). Wie die Frames danach vom Programmierer zu fĂŒllen sind, kann man der Funktion main aus âFRAMES.Câ entnehmen. Auch hier ist bei eigenen Experimenten die Reihenfolge der Eintragungen und Funktionsaufrufe einzuhalten.
Ăbrigens, die Interrupt-Routine f_next, die fĂŒr das Umschalten zustĂ€ndig ist, benötigt fĂŒr diese Aufgabe genau 64.5 ”s, liegt also deutlich unter den fĂŒr den worst case geforderten 80 ”s.
Literatur:
[IJ STE Developer Addendum
[2] Jankowski, Reschke, Rabich: Atari ST Profibuch, Sybex 1989, Seite 699 ff.
/* --------------------------------------- */
/* --- STEREO.C Stereo-Sound-Erzeugung
auf dem STE --- */
/* --- (c) 1991 MAXON Computer --- */
/* --- In Turbo-C 2.0 von Peter Engler --- */
/* --------------------------------------- */
#include <stdio.h>
#include <stdlib.h>
#include <ext.h>
#include <tos.h>
#include <math.h>
#include "sout.h"
/* --- Prototypen der im Modul verwendeten Funktionen --- */
int snd_alloc( SOUND *, unsigned long );
void snd_free( SOUND * );
void stereo( SOUND * );
int main{ void );
/* --- Anlegen des Arrays fĂŒr die zu wandelnden Bytes --- */
int snd_alloc( SOUND *snd, unsigned long anz )
{
/* --- Speicherplatz belegen --- */
snd -> s_ptr = (char *) malloc( anz );
/* --- Fehler aufgetreten --- */
if (! snd -> s_ptr)
{
snd -> anz bytes = 0L;
return( -1 );
}
/* --- Anzahl Bytes des reservierten Bereichs --- */
snd -> anz_bytes = anz;
/* --- Anzahl Bytes, die pro Sekunde gewandelt werden --- */
switch( snd -> mode_reg & 0x000F )
{
case MOD_FR50K : snd -> bytes_pro_sekunde = 50066L;
break;
case MOD_FR25K ; snd -> bytes_pro_sekunde = 25033L;
break;
case MOD_FR12K : snd -> bytes_pro_sekunde = 12517L;
break;
case MOD_FR6K : snd -> bytes_pro_sekunde = 6258L;
break;
default snd -> bytes_pro_sekunde = 0L;
break;
}
return( 0 );
}
/* --- Freigeben des Arrays der SOUND-Struktur --- */
void and_free( SOUND *snd )
{
free ( snd -> s_ptr );
}
/* --- Generieren der Werte (rechts Sinus, links SĂ€gezahn ) --- */
void Stereo( SOUND *snd)
{
unsigned long bytes_pro_periode, index;
char *h_ptr;
h_ptr = snd -> s_ptr;
/* --- Berechnung der Anzahl Bytes pro Periode --- */
bytes_pro_periode = snd -> bytes_pro_sekunde / snd -> frequenz;
/* --- Eintragen in SOUND-Struktur --- */
snd -> anz_bytes = bytes_pro_periode *2;
/* --- Berechnung der Werte fĂŒr den Vierklang --- */
for (index = 0; index < bytes_pro_periode; index++)
{
/* --- Zuerst der linke Kanal --- */
*h_ptr++ = (char) (127 * sin( 2.0 * M_PI * ((double) index) / (double) bytes_pro_periode) - 1);
/* --- Dann der rechte Kanal --- */
*h_ptr++ = (char) (255 * ((double) index) / ((double) bytes_pro_periode ) - 128);
}
}
int main()
{
SOUND snd;
/* --- 'mode_reg' immer vor 1. Aufruf von 'snd_alloc' setzen !! --- */
snd.mode_reg = MOD_FR50K | MOD_STEREO;
snd.control_reg = SND_IMMER;
snd.frequenz = 220; /* --- in Hz ---*/
/* --- Array f. den Frame anlegen --- */
if (snd_alloc( &snd, 65536L)) return(-1);
/* --- Stereoklang berechnen --- */
stereo ( &snd );
/* --- ... und abspielen --- */
snd_play( &snd );
/* --- Auf Tastendruck warten --- */
(void) getch();
/* --- Tonerzeugung ausschalten --- */
snd_stop();
snd_free( &snd );
return( 0 );
}