Sound in Assembler (ST)

In der Assemblerecke für ST behandeln wir die Programmierung des Soundchip YM-2149 diesmal wollen wir uns mit der Programmierung von Sounds und Geräuschen beschäftigen, nachdem ja in den ersten Folgen mehr die Grafik im Vordergrund stand. Leider kann der ST nicht mit einem Super-Soundchip aufwarten, wie ihn beispielsweise der C 64 oder der Amiga besitzen. Bei effizienter Programmierung kann man aber doch eine ganze Menge aus dem Rechner herausholen. Benutzt man digitalisierte Sounds, so ist er dem Amiga sogar aufgrund des größeren RAM-Speichers klar überlegen (vom C 64 ganz zu schweigen).

Der Soundchip im ST, ein Yamaha YM-2149, der übrigens auch in den CPCs und den MSX-Computern zu finden ist, besitzt insgesamt 16 Register, über die seine drei Stimmen programmiert werden. Die ersten beiden bestimmen die Periodendauer bzw. die Frequenz von Stimme 1. Dazu muß ein 12-Bit-Wert in diese zwei geschrieben werden, wobei dessen obere vier Bit in Register 1 und die unteren acht in Register 0 gehören.
Je höher dieser Wert, desto länger ist die Periodendauer, wodurch die Frequenz sinkt; der Ton wird also tiefer. Um nun zu den Werten für die Notenskala zu kommen, muß man wissen, daß der Periodendauer eine Grundfrequenz von 125 KHz zugrunde liegt; alle erzeugten Frequenzen müssen also Vielfache der Grunddauer von acht Mikrosekunden sein. Die Formel zur Berechnung des 12-Bit-Wertes lautet folglich: Wert = 125 KHz / Frequenz der Note. Für das eingestrichene C (Frequenz 261.6 Hz) ergibt sich daher ein Wert von 125 KHz / 261.6 Hz = 477; es muß also eine 1 in Register 1 (High-Byte) und eine 221 in Register 0 geschrieben werden.
Die Register 2 und 3 sowie 4 und 5 haben genau dieselbe Funktion wie die ersten beiden, sind jedoch für die Stimmen 2 und 3 zuständig. Mit Register 6 läßt sich der im Soundchip enthaltene Rauschgenerator in seiner Frequenz beeinflussen. Hierzu wird in den unteren 5 Bit die Periodendauer festgelegt; ansonsten gilt das gleiche wie für die Register 0 und 1.

Register 7 ist das Kontrollregister, in dem die Stimmen bzw. das Rauschen an- und ausgestellt werden können. Bit 0 bestimmt dabei, ob Stimme 1 an- (0) oder ausgeschaltet (1) ist. Bit 1 und 2 bewirken wiederum dasselbe für Stimme 2 und 3. In Bit 3 läßt sich zum Tonsignal von Stimme 1 ein Rauschsignal zu- (0) bzw. abschalten (1). Bit 6 und 7 dienen der Steuerung der beiden Ein-/ Ausgabe-Ports des Soundchips. Sie sind vorzugsweise auf 1 (also Ausgang) zu stellen, sollen uns aber nicht weiter interessieren.

Register 8 ist für die Lautstärke von Stimme 1 zuständig. In den. Bits 1 bis 3 kann sie in 16 Stufen reguliert werden. Allerdings geschieht dies nach einem logarithmischen Prinzip, d.h., ein Ton der Lautstärke 10 ist nicht doppelt so laut wie einer der Lautstärke 5. Ist Bit 4 in diesem Register gesetzt, finden die unteren vier Bit keine Beachtung. Stattdessen wird die Lautstärke über das Hüllkurvenregister bestimmt (dazu später mehr). Für die Register 9 und 10 gilt wieder das für 8 Gesagte. Die Register 11 und 12 sind für die Dauer der Hüllkurve verantwortlich, wobei alle 16 Bit zur Regulierung dienen. Register 11 stellt das Low-Byte, Register 12 entsprechend das High-Byte dar.

Mit Register 13 kommen wir jetzt endlich zum Hüllkurvenregister, mit dessen Hilfe auch komplexere Geräusche realisiert werden können, wenn das entsprechende Bit in den Lautstärkeregistern gesetzt ist. Ohne die se Hüllkurven lassen sich ja nur gleichmäßig laute Töne erzeugen, deren Lautstärke nur über das Lautstärkeregister beeinflußt werden kann. Sie halten so lange an, bis sie wieder abgestellt werden.

In Register 13 lassen sich nun aber acht verschiedene Hüllkurven einstellen, die dann den Lautstärkeverlauf eines Klangs festlegen. Dabei gibt es sogenannte Continous-Kurven, die sich immer wiederholen, und Kurven, die den Ton nur einmal erklingen lassen. Je nach Periodendauer der Hüllkurve können so die verschiedensten Effekte von der einfachen Dreieckschwingung bis zum Schlagzeug simuliert werden. Das es schlecht möglich ist, diese Hüllkurven zu beschreiben, werden sie im Bild nebst dem Wert für das Register dargestellt. Die letzten beiden Register (14 und 15) des Chips beziehen sich wieder auf die beiden Ports.

Nachdem nun die Funktionen der Register erläutert wurden, wollen wir dieses Wissen auch anwenden. Grundsätzlich gibt es auf der Assembler-Ebene ja, nur zwei Möglichkeiten, den Soundchip anzusprechen; entweder man verwendet die Betriebssystemroutine oder adressiert ihn direkt. Zum ersten Vorgang ist die XBIOS-Routine Nr. 32 mit dem Namen DOSOUND vorhanden, der vor dem Aufruf nur ein Zeiger auf die Sound-Tabelle zu übergeben ist (s. Beispiel). Diese Sound-Tabelle hat nun folgenden Aufbau: Zuerst kommt ein Befehls-Byte, dem je nach Kommando zwischen einem und drei Parameter folgen. Die Befehls-Bytes 0 bis 15 werden als Registernummer interpretiert. Der nachstehende Wert wird in das durch das Befehls-Byte beschriebene Register geladen.

Das Kommando 128 (80) bewirkt, daß der folgende Wert in ein von der Routine verwaltetes Hilfsregister kommt. Nähere Bedeutung erhält dieser Vorgang durch die Anweisung 129 (81), der insgesamt 3 Werte folgen. Der erste enthält das Register, in das der Inhalt des Hilfsregisters kopiert werden soll. Beim nächsten handelt es sich um einen Zweierkomplementwert, der danach zum Hilfsregister addiert wird. Der dritte ist die Zielvorgabe, d.h., bei Erreichen dieses Wertes wird der Vorgang beendet. Die Befehle 130 bis 255 (82 FF) erwarten nur ein Argument, das die Dauer bis zum Abarbeiten des nächsten Kommandos der Tabelle in 20-ms-Schritten festlegt. Ist dieser Wert gleich Null, wird die Sound-Verarbeitung ganz beendet.

Der Vorteil dieser Betriebssystemroutine besteht darin, daß der Sound unabhängig vom eigenen Programm im Hintergrund interruptgesteuert abgearbeitet wird, so daß man sich nach dem Start nicht mehr darum kümmern muß. Allen, die sowieso eigene IRQ-Routinen schreiben oder aber komplexere Geräusche, z.B. für ein Musikprogramm oder eine digitalisierte Melodie, programmieren wollen, ist diese Routine natürlich viel zu langsam. Ihnen bleibt nur das direkte Schreiben in den Soundchip übrig.

Dieser hat zwar nur zwei Register, die vom Prozessor wie jeder andere RAM-Bereich adressiert werden können, doch erweist sich die Programmierung als sehr einfach. Zuerst muß der Prozessor in den Supervisor-Modus gebracht werden (siehe Listing), da der Soundchip sich außerhalb des normalen Adreßbereichs befindet. Das erste Register liegt auf Adresse FF8800 (Register-Select). Hier muß jeweils die Nummer des Registers hineingeschrieben werden, dessen Wert man verändern will. Der eigentliche Wert kommt dann in das Write-DataRegister an der Adresse FF8802.
Zum Schluß seien noch einige Beispiel-Sounds mit ihren Parametern aufgeführt. So hat z. B. der normale Tastaturklick in Register 0 eine 59, in Register 7 eine 254, im Lautstärkeregister 8 eine 16, also eine angeschaltete Hüllkurve. Als Hüllkurve kommt eine 3 (9) in Register 13. Die Länge der Kurve wird durch eine 1 in Register 12 und eine 127 in Register 11 festgelegt. Weitere Möglichkeiten zeigt das Beispiel-Listing, in dem auch beide Arten der Sound-Programmierung genutzt werden.

Assemblerlisting

; Assembler-Demo zum Teil 3 der ST-Assembler Ecke
;
; geschrieben im August '87
;
; von C. Rduch
;
start:
move.w #32,-(sp) ;Supervisor
trap #1 ;Modus
addq.l #2,sp ;einschalten
bsr pause
move.l #sound1,a0 ;Anfang des
loop1: ;Bell-Sounds
move.b (a0)+,$ff8800 ;Werte in die
move.b (a0)+,$ff8802 ;Register
cmp.l #sound2,a0 ;schreiben
bne loop1 ;
bsr pause
move.l #sound2,a0 ;
loop2: ;Tastatur
move.b (a0)+,$ff8800 ;Klick
move.b (a0)+,$ff8802 ;
cmp.l #sound3,a0 ;
bne loop2 ;
bsr pause
move.l #sound3,a0 ;
loop3: ;Schlagzeug
move.b (a0)+,$ff8800 ; move.b (a0)+,$ff8802 ;
cmp.l #sound4,a0 ;
bne loop3 ;
bsr pause
move.l #sound4,a0 ;
loop4: ; Takt-machine
move.b (a0)+,$ff8800 ; move.b (a0)+,$ff8802 ;
cmp.l #sound5,a0 ;
bne loop4 ;
bsr pause
move.l #sound5,a0 ;
loop5: ; Alarm-Sirene
move.b (a0)+,$ff8800 ; move.b (a0)+,$ff8802 ;
cmp.l #sound6,a0 ;
bne loop5 ;
bsr pause
move.l #sound6,a0 ;
loop6: ; Flugzeug-Motor
move.b (a0)+,$ff8800 ; move.b (a0)+.$ff8802 ; cmp.l #sound7,a0 ; bne loop6
bsr pause
move.l #sound7,-(sp) ;Nun noch die
move.w #32,-(sp) ;Xbios-Routine
trap #14 ;fuer Sound?
addq.l #6,sp
bsr pause
ende:bra ende
pause:
move.w #50,d0
pause1: ;Verzoegerungs
move.w #65000,d1 ;schleife, die
pause2: ;ca. 5 sec
dbra d1,pause2 ;dauert !
dbra d0,pause1
rts
sound1:
dc.b0,52,7,254,8,16,12,16,13,9
dc.b1,0,2,0,3,0,4,0,5,0,6,0,9,0,10,0
dc.b11,0
sound2:
dc.b0,59,7,254,8,16,13,3,11,128,12,1
dc.b1,0,2,0,3,0,4,0,5,0,8,0,9,0,10,0
sound3:
dc.b1,3,6,13,7,246,8,16,12,4,13,9
dc.b0,0,2,0,3,0,4,0,5,0,9,0,10,0,11,0
sound4:
dc.b1,10,3,9,7,252,8,16,9,16,12,4,13,8
dc.b0,0,2,0,4,0,5,0,6,0,10,0,11,0
sound5:
dc.b1,3,2,30,3,3,4,50,5,3,6,5,7,216
dc.b8,16,9,16,10,16,11,1,12,0,13,10,0,0
sound6:
dc.b1,10,3,10,6,20,7,236,8,16,9,16
dc.b11,30,13,14,0,0,2,0,4,0,5,0,10,0
sound7:
dc.b7,255,1,0,6,0,8,0,12,0,13,0
dc.b0,0,2,0,3,0,4,0,5,0,9,0,10,0,11,0
dc.b255,150
dc.b1,3,6,13,7,246,8,16,12,4,13,9
dc.b255,60
dc.b1,3,6,13,7,246,8,16,12,4,13,9
dc.b255,40
dc.b1,3,6,13,7,246,8,16,12,4,13,9
dc.b255,15
dc.b1,3,6,13,7,246,8,16,12,4,13,9
dc.b255,6
dc.b1,3,6,13,7,246,8,16,12,4,13,9
dc.b255,15
dc.b1,3,6,13,7,246,8,16,12,4,13,9
dc.b255,12
dc.b1,3,6,13,7,246,8,16,12,4,13,9
dc.b255,0

Christian Rduch
Aus: Atari-Magazin 06 / 1987, Seite

Links

Copyright-Bestimmungen: siehe Über diese Seite