   ; Programm : Sound Effect Machine
   ; Modul    : Play Sampled Sound
   ; by       : Dan Dark Software / Jörg Schließer
   ; Version  : V1.0 / November 1989
   ; Assembler: Kick-Ass V1.20

   ; Modul zum Abspiel eines Sound-Effekts im IFF-Format 8SVX


   ; Übergabeparameter beim Aufruf:

   ; a0 = Adresse der IFF-Daten im RAM ( Chip-Ram ! )
   ; d0 = Mode:
   ;      d0 = 0,1,2,3 => Angabe des zu bespielenden Audiokanals
   ;      d0 = 4       => nur Audiointerrupts initialisieren
   ; d1 = Zahl der zu spielenden Wiederholungen

   ; Rückgabeparameter:

   ; d0 = 0 => Abspiel korrekt gestartet
   ; d0 = 1 => Error ... geforderter Audiokanal besetzt
   ; d0 = 2 => Error ... VHDR- oder BODY-Chunk nicht gefunden

   ; Betriebsystem-Routinen

SetIntVector   =   -162         ; Exec-Library

   ; Chip-Register:

intreq    =   $DFF09C      ; Interrupt Request (write)
intreqr   =   $DFF01E      ; Interrupt Request (read)
intena    =   $DFF09A      ; Interrupt Enable (write)
dmacon    =   $DFF096      ; DMA Kontrollregister (write)
audxadr   =   $DFF0A0      ; Basisadresse des 1. Kanals
audxlch   =   $0           ; Zeiger auf Adresse der Sampledaten
audxlcl   =   $2           ; ( Loword zu audxlch )
audxlen   =   $4           ; Länge der Sampl edaten in Wörtern
audxper   =   $6           ; Sampleperiod zum Abspiel der Samples
audxvol   =   $8           ; Lautstärke zum Abspiel der Samples

   ; Konstanten:

execbase  =   $4           ; Exec Basisadresse


   ; Playsound - Modul zum Abspielen eines Sample-Sounds im
   ; IFF-Format 8SVX, mit Angabe der Wiederholungen und Steuerung
   ; über Audio-Interrupts

   even
playsound:
   movem.l    d2-d7/a1-a6,-(sp) ; Registerinhalte retten
   btst       #2,d0             ; Prüfen auf Mode: Init Interrupt
   beq        noinitaudioi      ; nein ... weiter unten
   tst.b      audiflag          ; Prüfen: Interrupt schon init.
   bne        exitplaysound     ; ja ... OK und fertig
   bsr        initaudioi        ; Interrupt initialisieren
   bra        exitplaysound     ; ... OK und fertig
noinitaudioi:
   tst.b      audiflag          ; Prüfen, ob Interrupt initialisiert
   bne        irqisset          ; ja ... weiter unten
   bsr        initaudioi        ; Interrupt initialisieren
irqisset:
   lea        audicount,a1      ; Zeiger auf Abspielzähler nach a1
   tst.b      0(a1,d0)          ; Prüfen, ob letztes Abspiel zu ende
   bne        playerror1        ; nein ... Request zurückweisen
   addq.b     #1,d1             ; Zählwert um 1 erhöhen
   move.b     d1,0(a1,d0)       ; und in Abspielzähler schreiben
   move.l     #$DFF0A0,a5       ; Basisadresse des 1. Kanals in a5
   moveq      #0,d5
   bset       d0,d5             ; Bit mit Nr des Kanals in d5 setzen
   mulu       #16,d0            ; Kanalnr. * 16 zum Zeiger addieren
   add.l      d0,a5             ; ergibt Zeiger auf Chip-Register

   ; Adresse von BODY und VHDR-Chunk des IFF-Files ermitteln

   moveq      #0,d0
   move.l     #0,a3
   move.l     #0,a4
seekchunks:
   cmp.l      #132000,d0        ; nach Xtausend Byte noch kein Chunk ?
   bge        playerror2        ; ja ... War wohl kein IFF-File
   addq.l     #2,a0             ; Adresszeiger um 1 Word erhöhen
   addq.l     #2,d0             ; Zähler um 1 Word erhöhen
   cmp.l      #'VHDR',(a0)      ; 'VHDR'-Kennung gefunden ?
   bne        novhdrfound       ; nein ... weiter unten
   move.l     a0,a4             ; Zeiger auf VHDR-Daten-Anfang
   addq.l     #8,a4             ; nach a4
   cmp.l      #0,a3             ; Prüfen, ob BODY-Chunk auch schon
   bne        foundchunks       ; gefunden ... ja ... weiter unten
novhdrfound:
   cmp.l      #'BODY',(a0)      ; 'BODY'-Kennung gefunden ?
   bne        seekchunks        ; nein ... weiter suchen
   move.l     a0,a3             ; Zeiger auf BODY-DAten-Anfang
   addq.l     #8,a3             ; nach a3
   cmp.l      #0,a4             ; Prüfen, ob VHDR-Chunk auch schon
   beq        seekchunks        ; gefunden ... nein ... weiter suchen

   ; Werte zum Abspiel des Sounds ermitteln, in Chip-Register
   ; schreiben und Abspiel starten

foundchunks:
   move.l     a3,audxlch(a5)    ; Adresse der Sounddaten in Chipreg.
   move.l     (a4),d0           ; Länge des OneShotParts nach d0
   add.l      4(a4),d0          ; + Länge des RepeatParts ergibt Länge
   lsr.l      #1,d0             ; geteilt durch 2 gibt Länge in Words
   move.w     d0,audxlen(a5)    ; Länge des Sounds in Chipregister
   move.l     #3579546,d0       ; Konstante für Sample-Period in d0
   divu       12(a4),d0         ; und durch Sample-Rate teilen
   move.w     d0,audxper(a5)    ; ergibt Sample-Period ... in Chip
   move.w     #64,audxvol(a5)   ; maximale Lautstärke ... in Chip
   or.w       #$8200,d5         ; Bits zum Schreiben in dmacon setzen
   move.w     d5,dmacon         ; und Audio-DMA des Kanals starten
   move.w     d5,d4             ; Wert nach d4 und Bits für dmacon
   and.w      #15,d4            ; wieder löschen dann
   asl.w      #7,d4             ; 7mal links schieben ... und Bit
   or.w       #$8000,d4         ; für Schreiben in intena setzen
   move.w     d4,intena         ; und Interrupt für den Kanal starten
   bra        exitplaysound     ; Fertig !

playerror1:
   moveq      #1,d0             ; Fehler 1: geforderter Audiokanal
   bra        exitplaymodul     ; besetzt
playerror2:
   moveq      #2,d0             ; Fehler 2: VHDR- oder BODY-Chunk
   bra        exitplaymodul     ; nicht gefunden
exitplaysound:
   moveq      #0,d0             ; Alles OK ! Kein Fehler !
exitplaymodul:
   movem.l    (sp)+,d2-d7/a1-a6 ; Register zurückschreiben
   rts                          ; und Fertig !


   ; 4 Interrupt-Strukturen und Vektoren initialisieren

initaudioi:
   movem.l    d0-d1/a0-a1,-(sp) ; Register d0,d1,a0 und a1 retten
   move.w     #$780,intena      ; Interrupt sicherheitshalber ver-
   move.w     #$780,intreq      ; bieten und alten Request löschen
   move.l     execbase,a6       ; Exec-Basisadresse in a6
   lea        structs,a2        ; Zeiger auf Speicher für Strukturen
   moveq      #3,d2             ; Schleifenzähler d2
   moveq      #7,d3             ; Interrupt-Nummer in d3
istructsloop:
   move.b     #2,8(a2)          ; ln_Type: Interrupt
   move.b     #0,9(a2)          ; ln_Pri : 0
   clr.l      10(a2)            ; Zeiger auf ln_Name
   clr.l      14(a2)            ; Zeiger auf ln_Data
   move.l     #countprog,18(a2) ; Zeiger auf ln_Code
   move.l     d3,d0             ; Interruptnummer nach d0
   move.l     a2,a1             ; Zeiger auf Interrupt-Struktur
   jsr        SetIntVector(a6)  ; und Interrupt-Vektoren setzen
   add.l      #32,a2            ; Zeiger auf nächste Struktur
   addq.w     #1,d3             ; Interruptnummer plus 1
   dbra       d2,istructsloop   ; und weiter geht's
   move.b     #1,audiflag       ; Flag für initiierte Interrupts
   clr.l      audicount         ; Wiederholungszähler löschen
   movem.l    (sp)+,d0-d1/a0-a1 ; Register zurückschreiben
   rts                          ; und Fertig !


   ; Interrupt-Routine zum Zählen der Abspielungen

countprog:
   movem.l    d0-d7/a0-a6,-(sp) ; Registerinhalte sichern
   moveq      #0,d0             ; Interrupt-Request-Register (read)
   move.w     intreqr,d0        ; lesen und Interrupt-Request
   and.w      #$780,d0          ; zurückschreiben, sonst kommt
   move.w     d0,intreq         ; gleich der nächste IRQ
   lsr.w      #7,d0             ; 7mal rechts schieben => Kanalnummer
   lea        audicount,a1      ; Zeiger auf Zählerbytes nach a1
   moveq      #3,d1             ; Schleifen-/Kanalzähler
countloop:
   btst       d1,d0             ; Prüfen, ob IRQ für Kanalnummer d1 ?
   bne        countdown         ; ja ... also Runterzählen
countgoon:
   dbra       d1,countloop      ; nein ... nächsten Kanal prüfen
   movem.l    (sp)+,d0-d7/a0-a6 ; Register zurückschreiben
   rts                          ; und Fertig !
countdown:
   subq.b     #1,0(a1,d1)       ; Zähler um 1 runterzählen
   bne        countgoon         ; Zähler noch nicht 0, also weiter ...
   move.w     d0,d2             ; Kanalnummer nach d2
   and.w      #15,d2            ; Bit für DMA-löschen löschen
   move.w     d2,d3             ; Kanalnummer nach d3
   asl.w      #7,d3             ; 7mal links schieben für
   move.w     d3,intena         ; Abschalten des Interrupts
   move.w     d2,dmacon         ; und DMA für Audiokanal abschalten
   bra        countgoon         ; und weiter geht's


   ; Speicher für Strukturen, Variablen, Zähler und Flags

structs:     blk.b   128,0   ; 4 Interruptstrukturen
audicount:   dc.l    0       ; 4 Zählerbytes für Abspielungen
audiflag:    dc.b    0       ; Flag für Interrupts initiiert
   even
