STE-Soundbox Teil 4: Abspielen verschiedener Frames mittels Interrupt

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.

Rahmenumschaltung

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 );
}

Peter Engler
Aus: ST-Computer 12 / 1991, Seite 138

Links

Copyright-Bestimmungen: siehe Über diese Seite