Skip to content

Instantly share code, notes, and snippets.

@dhy2000
Last active April 5, 2023 14:38
An 8086 assembly program that plays a happy birthday song with MASM under dosbox.
; Happy Birthday in MASM
STACK SEGMENT PARA STACK
STACK_AREA DW 100h DUP(?)
STACK_TOP EQU $-STACK_AREA
STACK ENDS
DATA SEGMENT PARA
; message is a string
MESSAGE DB 'Happy birthday, '
DB 'chang' ; change it to your name
DB '$' ; '$' is the terminator for dos print
; frequency of music notes
G1 EQU 3043
A1 EQU 2711
B1 EQU 2415
C2 EQU 2280
D2 EQU 2031
E2 EQU 1809
F2 EQU 1715
G2 EQU 1521
; para 1: sol sol la sol DO si
PARA1 DW G1, G1, A1, G1, C2, B1
; para 2: sol sol la sol RE DO
PARA2 DW G1, G1, A1, G1, D2, C2
; para 3: sol sol SOL ME DO si la
PARA3 DW G1, G1, G2, E2, C2, B1, A1
; para 4: FA FA MI DO RE DO
PARA4 DW F2, F2, E2, C2, D2, C2
; total song with 4 paragraphs
NUM_PARA DW 4
PARA_HEAD DW PARA1, PARA2, PARA3, PARA4
PARA_LEN DW 6, 6, 7, 6
DURATION DW 10
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA, SS:STACK
; TICK: wait until the timer ticks
TICK PROC
PUSH AX
PUSH ES
MOV AX, 40h
MOV ES, AX
MOV AX, ES:[6Ch] ; read hardware clock ticks from 0040h:6Ch
TICK_WAIT:
CMP AX, ES:[6Ch] ; until clock ticks increase
JE TICK_WAIT
POP ES
POP AX
RET
TICK ENDP
; DELAY AX: delay for at least AX ticks
DELAY PROC
PUSH AX
PUSH CX
MOV CX, AX
DELAY_COUNT:
CALL TICK
LOOP DELAY_COUNT
POP CX
POP AX
RET
DELAY ENDP
; PLAY(freq, dur): play note of a frequency and lasts a duration
PLAY PROC
PUSH BP
MOV BP, SP ; use BP to address argument stack
ADD BP, 4 ; skip BP and ra
PUSH AX
; setup speaker: send 0B6h to port 43h
MOV AL, 0B6h
OUT 43h, AL
; setup frequency: send to port 42h
MOV AX, [BP+02h] ; load frequency
OUT 42h, AL ; send low byte
MOV AL, AH
OUT 42h, AL ; send high byte
; start beep: set bit 1 and 0 to high of port 61h
IN AL, 61h
OR AL, 03h
OUT 61h, AL
; delay for duration
MOV AX, [BP+00h] ; load duration
CALL DELAY
; stop beep: set bit 1 and 0 to low of port 61h
IN AL, 61h
AND AL, 0FCh
OUT 61h, AL
POP AX
POP BP
RET 4
PLAY ENDP
MAIN PROC
; setup stack and data segment register
MOV AX, STACK
MOV SS, AX
MOV SP, STACK_TOP
MOV AX, DATA
MOV DS, AX
; outer loop: para
XOR CX, CX ; set CX=0
LP_PARA:
MOV BX, CX
SHL BX, 1 ; offset for para head
; inner loop: note
PUSH CX
XOR CX, CX
LP_NOTE:
MOV SI, CX
SHL SI, 1 ; offset inside para
; load freq and dur of the note
PUSH BX
MOV BX, PARA_HEAD[BX]
MOV AX, [BX+SI] ; AX=para_i[j]
POP BX
PUSH AX ; freq
MOV AX, WORD PTR DURATION
PUSH AX ; dur
CALL PLAY
MOV AX, PARA_LEN[BX]
INC CX
CMP CX, AX
JB LP_NOTE
POP CX
; break between paras
MOV AX, WORD PTR DURATION
CALL DELAY
; next para
INC CX
CMP CX, WORD PTR NUM_PARA
JB LP_PARA
; print message
MOV AH, 9
LEA DX, MESSAGE
INT 21h
; return to dos
MOV AX, 4C00h
INT 21h
MAIN ENDP
CODE ENDS
END MAIN
@dhy2000
Copy link
Author

dhy2000 commented Mar 22, 2023

A first-meeting gift for the X86 Assembly course~
Happy birthday to @lightweigh on 2023-03-24 and wish you a great semester.

@dhy2000
Copy link
Author

dhy2000 commented Mar 22, 2023

运行环境

DOSBox + MASM

MASM 可从 这里 下载,并参考 说明 搭建环境。

播放声音

这部分课程没有讲过,仅作补充。

8086 汇编播放音乐需操作 Speaker 硬件,使用 INOUT 指令来交互。操作步骤详见 PLAY 子程序,解读如下:

  1. 43h 端口传送 0B6h 选择 Speaker ;
  2. 42h 端口按先低后高的顺序分两个字节传送音符频率;
  3. 61h 端口的值的低两位置高,开启 Speaker 开始播放;
  4. Sleep 一段时间,当前音符保持播放;
  5. 61h 端口的值的低两位置低,停止 Speaker 播放。

其中 Sleep 借助了硬件时钟,这里采用直接读取 0040h:6Ch 地址获取时钟的 tick 次数,循环等待指定的 tick 数。

参考资料:

  1. MASM 手册
  2. 8086 播放音符及音符频率表
  3. 用汇编程序播放音乐
  4. 硬件时钟

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment