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

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:

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:

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
Links

Copyright-Bestimmungen: siehe Über diese Seite
Classic Computer Magazines
[ Join Now | Ring Hub | Random | << Prev | Next >> ]