;This program demonstrates how to use an AdLib-compatible sound card's FM
;chip to play MIDI-style music. It plays a regular scale from C to C. It
;should work with any Sound Blaster or AdLib clone, as the ports it uses are
;quite standard. No IRQs or DMAs are used, just plain hardware I/O ports.
;Before we begin, we need to prepare the channels we will use by setting
;the 4 register types for both the carrier and the modulator, meaning each
;channel has a total of 8 registers we must set. The 4 register types are:
;AM/vibrato/octave shift, key scaling level/output level, attack rate/decay
;rate, and sustain level/release time.
;All registers here are for channel 1, as it's the only channel used by this
;program.
MOV AL,20h ;Set AM/vibrato/octave register for carrier.
MOV AH,1 ;Play note at specified octave, no AM or vibrato.
CALL SetReg
MOV AL,23h ;Set AM/vibrato/octave register for modulator.
MOV AH,1 ;Play note at specified octave, no AM or vibrato.
CALL SetReg

MOV AL,40h ;Set key scaling level/output level register for carrier.
MOV AH,00011111xB ;Scaling level of 0, fairly loud output level.
CALL SetReg
MOV AL,43h ;Set key scaling level/output level register for modulator.
MOV AH,0
CALL SetReg

MOV AL,60h ;Set attack rate/decay rate register for carrier.
MOV AH,11100100xB ;High attack rate, low decay rate.
CALL SetReg
MOV AL,63h ;Set attack rate/decay rate register for modulator.
MOV AH,11100100xB ;High attack rate, low decay rate.
CALL SetReg

MOV AL,80h ;Set sustain level/release time register for carrier.
MOV AH,10011101xB ;Medium sustain level, short release time.
CALL SetReg
MOV AL,83h ;Set sustain level/release time register for modulator.
MOV AH,10011101xB ;Medium sustain level, short release time.
CALL SetReg

;INITIAL CHANNEL PREPARATION IS COMPLETE, AND WE ARE NOW READY TO BEGIN
;PLAYING NOTES!!!

;Begin C
MOV AL,0A0h
MOV AH,10101110xB
CALL SetReg
MOV AL,0B0h
MOV AH,00101010xB
CALL SetReg
CALL WaitNote
;End C

;Begin D
MOV AL,0A0h
MOV AH,10000001xB
CALL SetReg
MOV AL,0B0h
MOV AH,00101101xB
CALL SetReg
CALL WaitNote
;End D

;Begin E
MOV AL,0A0h
MOV AH,10110000xB
CALL SetReg
MOV AL,0B0h
MOV AH,00101101xB
CALL SetReg
CALL WaitNote
;End E

;Begin F
MOV AL,0A0h
MOV AH,11001010xB
CALL SetReg
MOV AL,0B0h
MOV AH,00101101xB
CALL SetReg
CALL WaitNote
;End F

;Begin G
MOV AL,0A0h
MOV AH,00000010xB
CALL SetReg
MOV AL,0B0h
MOV AH,00101110xB
CALL SetReg
CALL WaitNote
;End G

;Begin A
MOV AL,0A0h
MOV AH,01000001xB
CALL SetReg
MOV AL,0B0h
MOV AH,00101110xB
CALL SetReg
CALL WaitNote
;End A

;Begin B
MOV AL,0A0h
MOV AH,10000111xB
CALL SetReg
MOV AL,0B0h
MOV AH,00101110xB
CALL SetReg
CALL WaitNote
;End B

;Begin C
MOV AL,0A0h
MOV AH,10101110xB
CALL SetReg
MOV AL,0B0h
MOV AH,00101110xB
CALL SetReg
CALL WaitNote
;End C

MOV AX,4C00h ;terminate program
INT 21h

SetReg:
;This routine sets a register on the sound card. The procedure to do this is
;fairly simple: First, output the value of the register you want to write to
;onto I/O port 388h. Then, output the value to write to that register onto
;port 389h.
;Inputs: AL is the register to be written to, AH is the value to write to it.
MOV DX,388h
OUT DX, AL
MOV AL,AH
MOV DX,389h
OUT DX, AL
RET

WaitNote:
;This routine waits a moment after the note begins, then shuts the note off
;and waits a little more to give the sound card time to recover.
;Here begins the first time-wait routine, used when the note begins.
MOV AH,0
INT 1Ah
ADD DX,10
MOV BX,DX
looper:
INT 1Ah
CMP DX,BX
JNE looper
;Here ends the first time-wait routine.

MOV AL,0B0h ;Set register B0...
MOV AH,0    ;...to 0, thus turning the note off.
CALL SetReg

;Here begins the second time-wait routine, used after the note ends.
MOV AH,0
INT 1Ah
ADD DX,10
MOV BX,DX
looper2:
INT 1Ah
CMP DX,BX
JNE looper2
RET

    Source: geocities.com/siliconvalley/2072

               ( geocities.com/siliconvalley)