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:
- âFrame Base Addressâ eintragen
- âFrame End Addressâ eintragen
- âSound Mode Controlâ-Register setzen
- 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 );
}