Coming from the Commodore 64 and being used to coding in assembler, I tried to do the same on my dad’s 386 PC. I decided on a DYCP effect, a popular effect in the Commodore 64 demoscene.

The DYCP itself (the bouncy scroller) wasn’t too hard; most effort and fun went into the music routine for the Soundblaster’s OPL2 (Yamaha YM3812) chip. This is the chip on the Soundblaster that was has a little FM synthesizer in it.

Getting sound out of it involved sending the right number to the various registers of the chip (for pitch, ADSR, operators and modulator and such). This was all in assembler; “composing” the music involved creating different table of numbers to be sent in sequence to the chip, using the music routines I knew from my Commodore 64 heroes as inspiration.

Main code for the DYCP Link to heading

;DEBUG equ

.MODEL TINY
.286
LOCALS

INCLUDE HLib.INC ; HeatWave Adlib Player header

IFDEF DEBUG
    Mark MACRO color
             mov  ah,color
             call Border
         ENDM
ELSE
    Mark MACRO
         ENDM
ENDIF

.CODE
ORG 100h

Start: ;call Check286 ; check for 286 or higher and VGA present
       ;jne  @@a286
       ;mov  ah,9
       ;mov  dx,OFFSET no286
       ;int  21h
       ;int  20h
@@a286: call CheckVideo
       jne  @@aVGA
       mov  ah,9
       mov  dx,OFFSET noVideo
       int  21h
       int  20h
                 
@@aVGA: mov  ax,11h ; Set 640x480x2 mode
       int  10h
       mov  ax,1130h ; Get 8x8 font address
       mov  bh,6
       int  10h
       mov  word ptr [Font],bp
       mov  word ptr [Font+2],es
       mov  ax,word ptr [Font] ; Clear Text-buffer
       push ds                  
       pop  es  
       mov  cx,80
       mov  di,OFFSET Text
       rep  stosw  
       mov  ax,OFFSET dycpmus
       push ax   
       call HAP_Init ; Init HeatWave Adlib Player
       pop  ax 
       
       mov  dx,03d4h ; write-unprotect some CRT-regs
       mov  al,11h
       out  dx,al
       inc  dx
       in   al,dx
       and  al,01111111b
       out  dx,al
       
       dec  dx
       mov  ax,2003h ; End Horizontal Blanking/Skew=001b
       out  dx,ax
       mov  ax,4d01h ; Horizontal Displayed=77 characters
       out  dx,ax
       
IFDEF DEBUG
         mov  dx,03c0h ; Set border color to 1
         mov  al,11h
         out  dx,al
         mov  al,1
         out  dx,al
ENDIF
        
DemoLoop: cli
          call WaitSync  ; wait for vertical sync-pulse
          Mark 20
          call dycp      ; draw the dycp
          Mark 40
          call Scroll
          call HAP_Play ; update music
          Mark 0 
          call Key       ; check for keypress 
          jmp  DemoLoop     

IFDEF DEBUG

Border : mov  dx,03c8h
         mov  al,1
         out  dx,al
         mov  al,ah
         inc  dx
         out  dx,al
         shl  al,1
         out  dx,al
         shr  al,2
         out  dx,al
         ret
ENDIF
          
dycp:     mov  ax,0a000h ; es -> screen segment
          mov  es,ax 
          xor  bx,bx
@@b: 
          mov  di,[dycpsin][bx]    ; get screen loc for char
          mov  si,[Text][bx]       ; get char offset
          push ds
          mov  ax,word ptr [Font+2] 
          mov  ds,ax
          mov  cx,15
@@a:      movsb
          add  di,79
          loop @@a
          pop  ds
          add  bx,2
          cmp  bx,160
          jne  @@b
          ret

WaitSync: mov dx,03dah
@@a:      in  al,dx
          test al,8
          jne   @@a
@@b:      in   al,dx
          test al,8
          je  @@b 
          ret

Scroll: 
        push ds   ; Shift Text buffer
        pop  es
        mov  di,OFFSET Text
        mov  bx,[di]
        mov  si,di
        add  si,2
        mov  cx,79
        rep  movsw
        mov  [di],bx

        mov  dx,03c0h     ; Update 'D016'
        mov  al,13h OR 32
        out  dx,al
        mov  al,[D016]
        out  dx,al
        or   al,al        ; D016=0 -> do not roughscroll
        jne  @@RoughScroll
        
@@get_char:         
            mov  bx,[textpoint] ; Get a new character
            inc  [textpoint]
            mov  al,byte ptr [dycptxt][bx]
            cmp  al,0dh
            je   @@get_char
            cmp  al,0ah
            je   @@get_char    
            cmp  al,'@'
            jne  @@no_end
            mov  [textpoint],0
            jmp  @@get_char

@@no_end: mov  bl,[Rough]
          xor  bh,bh
          shl  bl,1
          xor  ah,ah
          shl  ax,4
          add  ax,word ptr [Font]
          mov  [Text][bx],ax
          jmp  @@go_on   

@@RoughScroll:      
        dec  [Rough]
        jge  @@sip
        mov  [Rough],79 
@@sip:  mov  dx,03d4h
        mov  al,13
        mov  ah,[Rough]
        out  dx,ax
@@go_on: 
        inc [D016]
        inc [D016]
        and [D016],7
        ret

Key:   mov  ah,1
       int  16h
       je   @@nokey
       xor  ah,ah
       int  16h
       mov  ax,3
       int  10h
       mov  ah,9
       mov  dx,OFFSET credits
       int  21h
       int  20h
@@nokey: ret                 

.DATA
no286 db 0ah,0dh,'You need a 286 or higher to run this...',0ah,0dh,'$'
noVideo db 0ah,0dh,'You need VGA or MCGA to see this...',0ah,0dh,'$'

Font dd ?

EXTRN dycpsin, dycptxt, dycpmus

color db 0
textpoint dw 0
D016 db 0
Rough db 79

credits db 'When  : ',??DATE,', ',??TIME,0ah,0dh
        db 'Code  : Mad B/HTW',0ah,0dh
        db 'Grafix: IBM',0ah,0dh
        db 'Muzak : Rhyme Design/HTW',0ah,0dh,'$'

Text dw 80 DUP(' '*16)

END Start

Adlib music player Link to heading

; ADLIB music-player v0.01 (c) 1994 by Mad B/Rhyme Design/HeatWave
;------------------------------------------------------------------------
; Plays HAM (HeatWave Adlib Module) files.
; Call HAP_Init to initialize player.
; Call HAP_Play to update music.
; Background playing: call PlayHAM to start background playing
;                     call StopHAM to stop  "                "
;
; THIS VERSION USES INT08 FOR BACKGROUND PLAYING (Very unwise!!!)
; Still to implement:
; [-Transpose doesn't work correctly due to 12 notes/16 bits] FIXED 8/3/94
; -Repeat block function in track ($c0+x)
; -Drums are shitty
; [-Separate drumtrack (6) where notes are interpreted as drums] DONE 8/3/94
; -Interrupt play using ADLIB timer 
; -Multiple songs in one, caller provides pointer to InitPlayer 
; -Sound-setting for Drums

.MODEL TINY,C

PUBLIC HAP_Init,HAP_Play,PlayHAM,StopHAM,HAPvers

ADLIB_ADDR equ 0388h

.DATA
EXTRN Track0,Track1,Track2,Track3,Track4,Track5,DrumTrack
EXTRN BlockAdr,Sound0,SongName,tempo:BYTE

HAPvers db 'HeatWave Adlib Player version ',??DATE,'$'

operator db 0,1,2,8,9,0ah,10h,11h,12h ; base address for operator 1
                                      ; operator 2 = 3 + operator 1

notehi db 1,1,1,1,1,1,2,2,2,2,2,2
notelo db 6bh,81h,98h,0b0h,0cah,0e5h,02h,20h,41h,63h,87h,0aeh

trackp db ?,?,?,?,?,?,?  ; where are we in track
blockp db ?,?,?,?,?,?,?  ; where are we in block
songdelay  db ?              ; delay counter for whole song
counter db ?,?,?,?,?,?,?

curblock db ?,?,?,?,?,?,? ; which block currently playing
transpose db ?,?,?,?,?,?,? ; current transpose for each track

delay db 4,4,4,4,4,4,4

trackadr dw OFFSET Track0,OFFSET Track1,OFFSET Track2
         dw OFFSET Track3,OFFSET Track4,OFFSET Track5
         dw OFFSET DrumTrack

.CODE
LOCALS

HAP_Init PROC

           mov  ax,0100h   ; just poke zero to all (1..f5) registers...
           mov  cx,0f5h
@@a:       call poke
           inc  ah
           loop  @@a
           mov  ax,0120h   ; enable waveform-modulation
           call poke
           xor  si,si
@@next:    mov  [trackp][si],0  ; reset some variables
           mov  [counter][si],1
           mov  [transpose][si],0
           call GetBlock
           inc  si
           cmp  si,7
           jb   @@next
           mov  si,6       ; Set the sound for voice 6,7 & 8 (drums)
           mov  al,5
           call SetSound
           inc  si
           mov  al,5
           call SetSound
           inc  si
           mov  al,5
           call SetSound
           mov  al,[tempo]
           mov  [songdelay],al
           ret

HAP_Init ENDP

PlayHAM    PROC

           call HAP_Init           
           mov  ax,3508h   ; Hook up Play-routine to timer interrupt
           int  21h
           mov  word ptr cs:[OldInt8],bx
           mov  word ptr cs:[OldInt8+2],es
           mov  ax,2508h
           mov  dx,OFFSET NewInt8
           int  21h
           ret

PlayHAM ENDP

StopHAM PROC
        push ds
        mov  dx,word ptr [OldInt8]    ; Disconnect our routine from timer
        mov  ax,word ptr [OldInt8+2]
        mov  ds,ax
        mov  ax,2508h
        int  21h
        pop  ds
        ret

StopHAM ENDP

NewINT8: pushf
         call dword ptr cs:[OldINT8]
         push ax bx cx dx si di ds es
         pushf
         push cs
         push cs
         pop  es
         pop  ds
         call HAP_Play
         popf
         pop  es ds di si dx cx bx ax
         iret
         
OldINT8: dd ?        


poke: push ax cx
      mov  dx,ADLIB_ADDR  ;*** Writes AL to Adlib-register AH
      xchg ah,al      ;al and dx are destroyed
      out  dx,al
      in   al,dx        ; Wait
      in   al,dx
      in   al,dx  
      in   al,dx
      in   al,dx
      in   al,dx
      inc  dx
      mov  al,ah
      out  dx,al       ; Wait
      dec  dx
      mov  cx,10 ;35
@@b:  in   al,dx
      loop @@b
      pop  cx ax
      ret

HAP_Play PROC
     
     dec [songdelay]
     je  @@go_on   
     jmp @@exit
     
@@go_on: mov al,[tempo]
     mov [songdelay],al

     xor  si,si           ; si=voice
@@a: dec  [counter][si]
     jne  @@next

     call GetNote

     cmp  si,6         ; For track 6, play the drums
     jne  @@b
     or   al,11100000b
     mov  bl,al
     mov  ah,0bdh
     mov  al,11100000b
     call poke
     mov  al,bl
     call poke
     jmp  @@c

@@b:                   ; Else play a note
     mov  ah,al                ; Transpose note
     and  ah,0fh
     mov  bh,[transpose][si]
     and  bh,0fh
     add  ah,bh
     cmp  ah,0bh
     jbe  @@d  
     add  al,4
@@d: add  al,[transpose][si]
     mov  bx,si                ; Play note
     call StartNote
@@c:
     mov  al,[delay][si]
     mov  [counter][si],al

@@next: inc  si
        cmp  si,7
        jb   @@a

@@exit: ret

HAP_Play ENDP

GetBlock: mov  bx,si
          shl  bx,1
          mov  bx,[trackadr][bx]

          mov  al,[trackp][si]
          inc  [trackp][si]
          xor  ah,ah
          add  bx,ax
          mov  al,[bx]            ; get block-index
          cmp  al,0ffh
          jne  @@a
          mov  [trackp][si],0
          jmp  GetBlock

@@a:      cmp  al,80h
          jb   @@b
          sub  al,80h
          mov  [transpose][si],al
          jmp  GetBlock    
@@b:      mov  [curblock][si],al
          mov  [blockp][si],0
          ret

GetNote:  xor  bh,bh
          mov  bl,[curblock][si]
          shl  bx,1
          mov  bx,[blockadr][bx]
          mov  al,[blockp][si]
          xor  ah,ah
          add  bx,ax
          mov  al,[bx]
          inc  [blockp][si]
          cmp  al,0ffh
          jne  @@a
          call GetBlock
          jmp  GetNote

@@a:      cmp  al,0f0h
          ja   @@c

          mov  ah,al
          and  ah,11000000b
          cmp  ah,080h
          jne  @@b


          sub  al,80h
          mov  [delay][si],al
          jmp  GetNote

@@b:      cmp  ah,0c0h
          jne  @@c
          sub  al,0c0h
          call SetSound
          jmp  GetNote

@@c:      ret

StartNote:
                         ; al=oct:note, bl=voice
          mov  bh,al
          shr  bh,1
          shr  bh,1
          mov  di,ax
          and  di,0fh
          and  bh,11100b
          or   bh,[notehi][di]
          or   bh,100000b      ; bh=key on:octave:hifreq

          mov  al,[notelo][di]
          mov  ah,0a0h
          add  ah,bl           ; bl=voice
          call poke
          add  ah,10h
          mov  al,0
          call poke
          mov  al,bh
          call poke
          xor bh,bh
          ret

PokeWord:  lodsb         
           call poke
           add  ah,3
           lodsb
           call poke
           add  ah,20h-3
           ret

SetSound: ; si=voice al=sound num
          ; kills ax, bx, cx, dx, di

              xor  ah,ah  ; sound=sound*16
              shl  ax,1
              shl  ax,1
              shl  ax,1
              shl  ax,1
              add  ax,OFFSET Sound0
              mov  di,ax

              mov  ax,si
              mov  ah,20h ; set multiple (base-reg 20h and 23h)

              mov bx,OFFSET operator ; 'Adds' voice AL to base-address AH
              xlat
              add ah,al

              xchg si,di 
              call PokeWord
              call PokeWord ; set level (base-reg 40h and 43h)
              call PokeWord ; set attack/decay (base-reg 60h and 63h)
              call PokeWord ; set sustain/release (base-reg 80h and 83h)
              add  ah,40h   ; set waveform (base-reg e0h and e3h)
              call PokeWord
              mov  ax,di
              mov  ah,al
              add  ah,0c0h ; set feedback/algorithm (base-reg c0h)
              lodsb
              call poke

              mov  si,di
              ret
END

Data making up the song Link to heading

.MODEL TINY

PUBLIC dycpmus

.DATA

dycpmus=$

songname db '1234567890123456'
         db 0
vers     dw 0100
tempo    db 3

soundadr dw OFFSET Sound0

trackadr dw OFFSET Track0,OFFSET Track1,OFFSET Track2
         dw OFFSET Track3,OFFSET Track4,OFFSET Track5
         dw OFFSET DrumTrack

blockadr dw OFFSET Block0,OFFSET Block1,OFFSET Block2
         dw OFFSET Block3,OFFSET Block4,OFFSET Block5
         dw OFFSET Block6,OFFSET Block7,OFFSET Block8
         dw OFFSET Block9,OFFSET Block0a,OFFSET Block0b

Sound0 dw 0000h,1f1fh,0000h,0000h,0000h
       db 0h,0,0,0,0,0
Sound1 dw 4141h,000fh,0e6d5h,08888h,0101h ; Bass
       db 0010b,0,0,0,0,0
Sound2 dw 04101h,040eh,0e464h,06666h,0202h ; Solo 
       db 1011b,0,0,0,0,0
Sound3 dw 04101h,0914h,0e462h,06666h,0202h ; Solo echo
       db 1011b,0,0,0,0,0
Sound4 dw 0101h,0b14h,0e6f5h,04f4fh,0203h ; Chords
       db 1100b,0,0,0,0,0
Sound5 dw 0909h,0400h,0faf9h,0ffffh,0203h ; DrumSound
       db 0100b,0,0,0,0,0
Sound6 dw 0209h,0810h,0f676h,07777h,0201h ; Metal 
       db 0110b,0,0,0,0,0
Sound7 dw 0105h,0404h,0f7f7h,07878h,0201h ; Metal 
       db 1000b,0,0,0,0,0

;*** Tracks: 80h+x=transpose x notes 0FFh=End ***

Track0 db 90h,3,3,3,3,1,1,0ffh
Track1 db 90h,4,4,4,4,7,7,0ffh
Track2 db 90h,5,5,5,5,8,8,0ffh
Track3 db 90h,6,6,6,6,9,9,0ffh
Track4 db 90h,0,0,0ah,0ffh
Track5 db 90h,0,0ffh
DrumTrack db 80h,2,0ffh

;*** Blocks: 80h+x=duration x (x>0) 0C0h+x=sound x 0FFh=End ***

Block0 db 0a0h,0c0h,00h,00h,00h,00h,0ffh ; 'Empty' block

Block1 db 0c1h
       db 86h,0ah,82h,1ah,0c7h,84h,50h,0c1h,0ah
       db 86h,15h,82h,25h,0c7h,84h,50h,0c1h,15h
       db 86h,10h,82h,20h,0c7h,84h,50h,0c1h,10h
       db 86h,12h,82h,22h,0c7h,84h,50h,50h,0c1h        
       db 86h,0ah,82h,1ah,0c7h,84h,50h,0c1h,0ah
       db 86h,10h,82h,20h,0c7h,84h,50h,0c1h,10h
       db 86h,15h,82h,25h,0c7h,84h,50h,0c1h,15h
       db 86h,15h,82h,25h,0c7h,84h,50h,82h,50h,50h,0ffh        

Block2 db 84h,2,16,1,16,0ffh

Block3 db 0c1h
       db 86h,12h,82h,22h,0c7h,84h,50h,0c1h,15h
       db 86h,09h,82h,19h,0c7h,84h,50h,0c1h,15h
       db 86h,14h,82h,24h,0c7h,84h,50h,0c1h,17h
       db 86h,05h,82h,15h,0c7h,84h,50h,50h,0ffh 


Block4 db 0c0h,84h,0,0c4h,88h,29h,29h,29h,29h,27h,27h,25h,84h,25h,0ffh
Block5 db 0c0h,84h,0,0c4h,88h,32h,32h,30h,30h,2bh,2bh,29h,84h,29h,0ffh
Block6 db 0c0h,84h,0,0c4h,88h,35h,35h,34h,34h,34h,34h,30h,84h,30h,0ffh

Block7 db 0c0h,84h,0,0c4h,88h,2ah,2ah,29h,29h,27h,27h,29h,29h
       db 2ah,2ah,27h,27h,29h,29h,29h,84h,29h,0ffh 

Block8 db 0c0h,84h,0,0c4h,88h,32h,32h,30h,30h,30h,30h,32h,32h
       db 32h,32h,30h,30h,30h,30h,30h,84h,30h,0ffh        
Block9 db 0c0h,84h,0,0c4h,88h,35h,35h,35h,35h,34h,34h,35h,35h
       db 35h,35h,34h,34h,35h,35h,35h,84h,35h,0ffh

Block0a db 0c0h,88h,0,0c2h
        db 81h,38h,87h,39h,88h,37h,35h,81h,3bh,83h,40h,84h,42h,40h,94h,39h
        db 81h,44h,87h,45h,84h,44h,42h,88h,40h,84h,42h,81h,38h,0abh,39h
        db 81h,38h,87h,39h,88h,37h,35h,81h,3bh,83h,40h,84h,42h,40h,90h,39h
        db 81h,38h,83h,39h,84h,45h,44h,88h,42h,81h,43h,83h,44h,84h,45h,47h
        db 81h,44h,0a3h,45h,0ffh
Block0b db 0ffh

END