← ST-Computer 09 / 1991

STE-Soundbox, Teil 1: Struktur der STE-Klangerzeugung

Grundlagen

Bekanntlich sind die Rechner der STE- (und TT-) Reihe mit einer 8 Bit Stereoklangerzeugung ausgestattet. Die vorliegende Artikelserie ‘STE-Soundbox’ soll es dem Interessierten ermöglichen, sich Schritt fĂŒr Schritt mit den Features dieser Klangerzeugung vertraut zu machen. Um die trockene Theorie etwas aufzulockern, endet jede Folge mit einem Listing, in dem das behandelte Thema praktisch aufgearbeitet wird. Die erste Folge beschreibt die grundlegende Arbeitsweise des DMA-Soundchips.

Stereo-DMA-Sound. 8-Bit-D/A-Wandler mit bis zu 50 kHz. So oder Àhnlich lauten die Schlagwörter, mit denen die STE-Klangerzeugung umschrieben wird. Darin steckt schon eine Menge Information - aber alles der Reihe nach.

DMA-Sound

DMA ist wieder solch eine neuhochdeutsche AbkĂŒrzung und bedeutet ‘Direct-Memory-Access’. Dieser Ausdruck steht fĂŒr die FĂ€higkeit des Soundchips, den STE-Speicher direkt zu adressieren. Der Soundchip kann also seine Klangdaten eigenstĂ€ndig aus dem Speicher abrufen, ohne daß die CPU eingreifen muß. Folglich muß sich die CPU nur zu Beginn um die richtige Initialisierung des Soundchips kĂŒmmern und kann sich dann - wĂ€hrend der Soundchip die Klangdaten abarbeitet - getrost anderen Aufgaben zuwenden.

8-Bit-D/A - Wandler

Das ‘D/A’ steht fĂŒr Digital/Analog. Analog bedeutet, daß ein Signal zwischen den vorgegebenen Grenzen jeden beliebigen Wert annehmen kann. Digitale Signale sind jedoch diskrete Signale, d.h. sie können nur eine bestimmte Anzahl von diskreten Werten annehmen. So lassen sich z.B. mit 8 Bit 256 verschiedene Werte darstellen. Ein D/A-Wandler stellt nun das Bindeglied zwischen digitaler Welt (Rechner) und analoger Welt (VerstĂ€rker und Lautsprecher) dar. Abb. 1 zeigt, wie digitale Daten mit einen D/A-Wandler in analoge Signale gewandelt werden.

Sampling-Rate

Nun ist wahrscheinlich jedem bekannt, daß ein Ton in einem VerstĂ€rker nichts anderes ist als eine zeitlich sich verĂ€ndernde Spannung. In Abb. 2 ist dieser Sachverhalt nochmals am Beispiel einer Dreieckspannung aufgezeigt. Zwischen den Grenzen +1V und -IV nimmt die Spannung jeden möglichen Wert an. Nach 0.25 ms ist sie auf 1V angestiegen, nach 0.75 ms hat sie den Wert -1V erreicht, und nach 1 ms schließlich betrĂ€gt die Spannung wieder 0V. Ein solcher Durchlauf des Signalwertes (von 0V ins Positive, danach ins Negative und wieder zurĂŒck nach 0V) nennt man eine Periode. Die Periodendauer betrĂ€gt in unserem Beispiel genau 1 ms. Aus der Dauer einer Periode lĂ€ĂŸt sich die Frequenz wie folgt berechnen: Frequenz = 1 / Periodendauer.

Setzt man die Periodendauer aus unserem Beispiel in die Gleichung ein, erhÀlt man als Frequenz 1000 Hz oder 1 kHz. Wie wird nun eine solche analoge Schwingung im Rechner dargestellt?

Die Digitalisierung des Signals wird in zwei Schritten vollzogen:

  • zunĂ€chst werden auf der Zeitachse diskrete Werte markiert, z.B alle 62.5 Mikrosekunden (”s). (siehe Abb. 2b)
  • danach wird bei jedem markierten Zeitwert die zugehörige Spannung abgelesen. Diese analogen Spannungen werden dann in digitale Werte umgewandelt und im Rechner als Zahlenkolonne hinterlegt.

Die Anzahl der Werte, die dabei pro Sekunde ermittelt werden, heißt Sampling-Rate oder Sampling-Frequenz. Wird beispielsweise alle 62.5 ”s ein Wert ermittelt, ergibt dies eine Sampling-Rate von 1/62.5 ”s = 16 kHz. Töne werden auf dem STE dadurch erzeugt, daß man entsprechend der gewĂŒnschten Schwingung eine Zahlenkolonne erzeugt und an den Soundchip weiterleitet (siehe Abb. 3).

Der DMA-Soundchip bietet hierzu folgende Möglichkeiten:

  • Zahlen werden als 8-Bit-Werte verarbeitet. Es können also 256 verschiedene Ausgangsspannungen erzeugt werden.
  • 4 Samplingraten: 6258 Hz, 12517 Hz, 25033 Hz, 50066 Hz.
  • Mono- und Stereoklangerzeugung.

Nun fragt man sich natĂŒrlich, ob denn 256 verschiedene Ausgangsspannungen reichen, um die theoretisch unendlich vielen Spannungswerte einer analogen Schwingung nachzubilden. Die Dreieckspannung aus Abb. 3, die aus einem 3-Bit-D/A-Wandler stammt, sieht ja doch recht eckig aus und erinnert nur noch sehr entfernt an ihr Pendant aus der analogen Welt. Bei 8 Bit sieht das ganze natĂŒrlich deutlich besser aus, aber Ecken gibt es trotzdem, und die bedeuten nun mal Verzerrungen und Rauschen.

Soundchip-Register

Leider werden von Atari keine Betriebssystemroutinen fĂŒr die Programmierung des DMA-Soundchips zur VerfĂŒgung gestellt. Deshalb habe ich zu jedem Feature kleine Assembler-Routinen entwickelt, die man in Turbo-C einbinden kann. Diese werden bei der Behandlung des je welligen Themas vorgestellt.

Die Werte fĂŒr den D/A-Wandler werden im STE-Speicher als vorzeichenbehaftete 8-Bit-Zahl (entspricht ‘char’ in Turbo C) abgelegt. Eine Gruppierung solcher 8-Bit-Zahlen, welche spĂ€ter den Ton ergeben sollen, nennt Atari einen ‘Frame’. Man kann einen Frame einmal abspielen oder ihn theoretisch unendlich oft wiederholen. In Abb. 4 sind die Soundchip-Register aufgefĂŒhrt. Die ‘Frame Base Address’ ist dabei die Adresse des ersten Bytes des Frames, die ‘Frame End Address’ die Adresse des ersten Bytes nach dem Frame. Ein Frame muß immer eine gerade Anzahl an Bytes beinhalten.

So, jetzt das versprochene Listing: In ‘SOUT.S’ sind die Assembler-Funktionen ‘sndjplay’ und ‘snd_stop’ implementiert. ‘snd_stop’ dient dazu, die Tonausgabe an einer beliebigen Stelle anzuhalten. Dies wird erreicht, indem das ‘Sound DMA Control’-Register mit 0 gefĂŒllt wird. Die Funktion ‘snd_play’, welche fĂŒr das Abspielen eines Frames zustĂ€ndig ist, erwartet als Parameter einen Zeiger auf die ‘SOUND’-Struktur. Diese Struktur ist in ‘SOUT.H’ definiert und beinhaltet unter anderem die fĂŒr die Tonausgabe relevanten Daten. Siehe hierzu auch die Kommentare im Listing.

Bei der Programmierung des Soundchips sollte man folgende Schritte berĂŒcksichtigen:

  1. ‘Frame Base Address’ eintragen
  2. ‘Frame End Address’ eintragen
  3. ‘Sound Mode Control’-Register setzen
  4. Durch Setzen des ‘Sound DMA Control’-Registers die Tonausgabe starten
Abb. 1: Der D/A-Wandler stellt das Bindeglied zwischen digitaler und analoger Welt dar.
Abb. 2a: Die analoge Dreieckspannung nimmt zwischen den Grenzen +1V und -1V koninuierlich jeden möglichen Wert an.
Abb. 2b: Rasterung der Zeitachse. Durch die Spannungswerte an den Rasterpunkten wird die Dreieckspannung im Rechner reprÀsentiert.
Abb. 3: Die mit 3 Bit digitalisierte Dreieckspannung

Zu beachten ist noch, daß alle Zugriffe auf die Soundchip-Register im Supervisormodus der CPU erfolgen mĂŒssen.

Die Assembler-Funktionen sind jeweils so konzipiert, daß sie von Turbo-C aus aufgerufen werden können. Ein kleines Beispiel fĂŒr die Ausgabe eines Sinustones ist in ‘KLANG.C’ enthalten. Der Ton wird in Mono mit einer Sampling-Rate von 50 kHz wiedergegeben. Seine Frequenz betrĂ€gt 440 Hz. Wer sich die Funktion ‘sinus’ etwas genauer ansieht, wird feststellen, daß in einem Frame genau eine Periode des Sinustones enthalten ist. Es macht natĂŒrlich keinen Sinn, solch einen Frame nur einmal abzuspielen, deshalb wird das ‘Sound DMA Control’-Register auf Wiederholung gesetzt.

So, das war’s fĂŒrs erste. Viel Spaß beim Experimentieren mit eigenen Sounds.

Literatur:

STE Developer Addendum

Abb. 4: Die Register des DMA-Soundchips
/* SOUT.H Headerdatei fĂŒr das Assemblermodul SOUT.S */ /* Konstanten f. das Sound-DMA-Control Register */ #define SND_STOP 0 #define SND_EINMAL 1 #define SND_IMMER 3 /* Konstanten fĂŒr das Sound-Mode Register */ #define MOB_FR6K 0x0000 /* Samplingfrequenz: 6258 Hz */ #define MOD_FR12K 0x0001 /* 12517 Hz */ #define MOD_FR25K 0x0002 /* 25033 Hz */ #define MOD_FR50K 0x0003 /* 50066 Hz */ #define MOD_STEREO 0x0000 /* Stereo-Wiedergabe */ #define MOD_MONO 0x0080 /* Mono-Wiedergabe */ /* Typdeklaration */ typedef struct { unsigned long anz_bytes; /* LĂ€nge des Frames */ unsigned long bytes_pro_sekunde; /* Samplingfrequenz */ int control_reg; /* Wert fĂŒr Sound-DMA-Control-Register */ int mode_reg; /* Wert fĂŒr Sound-Mode-Control Register */ int frequenz; /* Was wohl? */ char *s_ptr; /* Zeiger auf den Frame */ } SOUND; /* Prototypen der Assemblerfunktionen aus sout.s */ void snd_stop( void ); void snd_play( SOUND * ); * ------------------------------------------------------- * * SOUT.S : Routinen zur Programmierung der DMA-Soundchips * * * * Zum Einbinden in Turbo-C 2.0, Peter Engler * * (c) MAXON Computer 1991 * * ------------------------------------------------------- * * Deklaration der Routinen GLOBL snd_stop GLOBL snd_play * Adressen der DMA-Soundchipregister S_CNTRL EQU $FF8900 F_BASE EQU $FF8903 F_COUNT EQU $FF8908 F_END EQU $FF890F S_MODE EQU $FF8920 * Daten DATA EVEN * Heap BSS EVEN * Speicher fĂŒr Parameter reservieren SND_ADR: ds.l 1 * Code TEXT EVEN * Aufruf von s_stop im Supervisormodus snd_stop: pea s_stop * Adresse von s_stop auf Stack move.w #$26,-(sp) * Xbios Nr. $26 (->SUPEXEC) trap #14 * Xbios Aufruf addq.l #6,sp * Stackpointer korrigieren rts * Aufruf von s_play im Supervisormodus snd_play: move.l a0,SND_ADR * Adresse der Struktur retten pea s_play * Adresse von s_stop auf Stack move.w #$26,-(sp) * Xbios Nr. $26 (->SUPEXEC) trap #14 * Xbios Aufruf addq.l #6,sp * Stackpointer korrigieren rts * Stoppen der Tonausgabe s_stop: move.w #0,S_CNTRL * Sound-Control-Register löschen rts * Ton ausgeben: Der Ton wird durch die SOUND- * Struktur beschrieben s_play: movem.l d3/d4,-(sp) * d3 und d4 retten movea.l SND_ADR,a0 * Adresse der Sound-Struktur in a0 move.l (a0)+,d0 * Anzahl Bytes in d0 lea.l 4(a0),a0 * Auf 'control_reg' positionieren move.w (a0)+,d3 * 'control_reg' in d3 move.w (a0),d4 * 'mode_reg' in d4 lea.l 4(a0),a0 * Auf 's_ptr' positionieren move.l (a0),d1 * Adresse der Bytes in d1 move.l d1,d2 * Adresse merken move.b d1,F_BASE+4 * Low-Byte eintragen asr.l #8,d1 * Mid-Byte holen move.b d1,F_BASE+2 * Mid-Byte eintragen asr.l #8,d1 * High-Byte holen move.b d1,F_BASE * High-Byte eintragen add.l d0,d2 * Frame-End berechnen move.b d2,F_END+4 * Low-Byte eintragen asr.l #8,d2 * Mid-Byte holen move.b d2,F_END+2 * Mid-Byte eintragen asr.l #8,d2 * High-Byte holen move.b d2,F_END * High-Byte eintragen move.w d4,S_M0DE * Mode-Register setzen move.w d3,S_CNTRL * Sound ausgeben (Control-Register) movem.l (sp)+,d3/d4 * d3 und d4 restaurieren rts END snd_play: move.l a0,SND_ADR * Adresse der Struktur retten pea s_play * Adresse von s_stop auf Stack move.w #$26,-(sp) * Xbios Nr. $26 (->SUPEXEC) trap #14 * Xbios Aufruf addq.l #6,sp * Stackpointer korrigieren rts * Stoppen der Tonausgabe s_stop: move.w #0,S_CNTRL * Sound-Control-Register löschen rts * Ton ausgeben Der Ton wird durch die SOUND- * Struktur beschrieben s_play: movem.l d3/d4,-(sp) * d3 und d4 retten movea.l SND_ADR,a0 * Adresse der Sound-Struktur in a0 move.l (a0)+,d0 * Anzahl Bytes in d0 lea.l 4(a0),a0 * Auf 'control_reg' positionieren move.w (a0)+,d3 * 'control_reg' in d3 move.w (a0),d4 * 'mode_reg' in d4 lea.l 4(a0),a0 * Auf 's_ptr' positionieren move.l (a0),d1 * Adresse der Bytes in d1 move.l d1,d2 * Adresse merken move.b d1,F_BASE+4 * Low-Byte eintragen asr.l #8,d1 * Mid-Byte holen move.b d1,F_BASE+2 * Mid-Byte eintragen asr.l #8,d1 * High-Byte holen move.b d1,F_BASE * High-Byte eintragen add.l d0,d2 * Frame-End berechnen move.b d2,F_END+4 * Low-Byte eintragen asr.l #8,d2 * Mid-Byte holen move.b d2,F_END+2 * Mid-Byte eintragen asr.l #8,d2 * High-Byte holen move.b d2,F_END * High-Byte eintragen move.w d4,S_MODE * Mode-Register setzen move.w d3,S_CNTRL * Sound ausgeben (Control-Register) movem.l (sp)+,d3/d4 * d3 und d4 restaurieren rts END /* -------------------------------------------- */ /* KLANG1.C: */ /* Erzeugung eines Sinustones auf dem STE */ /* */ /* In Turbo-C 2.0 implementiert v. Peter Engler */ /* (c) MAXON Computer 1991 */ /* -------------------------------------------- */ #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 sinus( SOUND * ); int main( void ) ; /* Anlegen des Arrays f. 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 snd_free( SOUND *snd ) { free( snd -> s_ptr ); } /* Generieren der Werte fur den Sinuston */ void sinus( 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; /* Wert muß gerade sein */ if ( (bytes_pro_periode % 2) == 1) bytes_pro_periode++; /* Eintragen in SOUND-Struktur */ snd -> anz_bytes = bytes_pro_periode; /* Berechnung der Werte fur einen Sinuston */ for (index = 0; index < bytes_pro_periode, index++) { *h_ptr++ = (char) (127 * sin( 2.0 * M_PI * ((double) index) / (double) bytes_pro_periode) - 1); } } int main( ) { SOUND snd; /* 'mode_reg' immer vor 1. Aufruf von 'snd_alloc' setzen!! */ snd.mode_reg = MOD_FR50K | MOD_MONO; snd.control_reg = SND_IMMER; snd.frequenz = 440; /* in Hz */ /* Array fĂŒr den Frame anlegen */ if (snd_alloc( &snd, 65536L)) return(-1); /* Sinus in SOUND-Struktur eintragen */ sinus( &snd ); /* ... und abspielen */ snd_play ( &snd ); /* Auf Tastendruck warten */ (void) getch( ); /* Tonerzeugung ausschalten */ snd_stop( ); snd_free( &snd ); return( 0 ); }
Peter Engler