汇编课设 备份

5.4

;==========================================================================
; 
; 功能:
; 
; ① 选择清屏
; ② 弹奏音乐(部分音阶1~高音1,Ctrl+Z返回主菜单)
; ③ 选择播放音乐(可选三首,可增加,Q结束播放,ESC返回主菜单)
; ④ 显示时间(W结束显示并返回主菜单)
; ⑤ F1变换界面颜色(中断方法)
; 
;===========================================================================

title main(exe)
include macro.mac
extrn piano:far
.8086
.model small
.stack 1000H
.data
      zd dw ?, ?     ;存原中断入口地址
      menu db 22 dup(0), 0c9h, 34 dup(0cdh), 0bbh, 10, 13
           db 22 dup(0), 0bah, '            Menu                  ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   1.Clear the screen.            ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   2.Play like a piano.           ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   3.Select a song to play.       ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   4.Displays the current time.   ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   5.Press F1 to change the color.', 0bah, 10, 13
           db 22 dup(0), 0bah, '   0.Exit the program.            ', 0bah, 10, 13
           db 22 dup(0), 0c8h, 34 dup(0cdh), 0bch, 10, 13
      tips db 10, 13, 22 dup(0), 'Please enter a number from 0 to 5.              ', 14, 10, 13, '$'

      time_tip db ' Press the W key to exit the time display.        ', '$'

      tip5 db ' !!!Press F1 to change the color!!!               ', '$'

      nextline db 10, 13, 22 dup(0), '$'

      error db '  Error! Please re-enter.                         ', '$'

      songs_name db 22 dup(0), '         Song track              ', 10, 13
                 db 22 dup(0), '1.Two tigers.(liang zhi lao hu)  ', 10, 13
                 db 22 dup(0), '2.Whitewasher.(fen shua jiang)   ', 10, 13
                 db 22 dup(0), '3.A Good New Year.(xin nian hao) ', 10, 13
                 db 10, 13
                 db 22 dup(0), '    Press Q to quit playing.     ', 10, 13
                 db 22 dup(0), 'Press ESC to return to the menu. ', 10, 13
                 db 10, 13, '$'

      play_finished db '  Play finished.        ', '$'
      play_exited   db '  Playback exited.      ', '$'


      ; 两只老虎
      mus_freq1 dw 2 dup(262,294,330,262)
                dw 2 dup(330,349,392)
                dw 2 dup(392,440,392,349,330,262)
                dw 2 dup(294,196,262)
                dw -1 ;结束
      mus_time1 dw 2 dup(20,20,20,20)
                dw 2 dup(20,20,40)
                dw 2 dup(10,10,10,10,20,20)
                dw 2 dup(20,20,40)

      ; 粉刷匠
      mus_freq2 dw 392,330,392,330,392,330,262
                dw 294,349,330,294,392
                dw 392,330,392,330,392,330,262
                dw 294,349,330,294,262
                dw 294,294,349,349,330,262,392
                dw 294,349,330,294,392
                dw 392,330,392,330,392,330,262
                dw 294,349,330,294,262
                dw -1 ;结束
      mus_time2 dw 3 dup(10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,40h)
                dw 10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,20h

      ; 新年好
      mus_freq3 dw 262,262,262,196
                dw 330,330,330,262
                dw 262,330,392,392
                dw 349,330,294
                dw 294,330,349,349
                dw 330,294,330,262
                dw 262,330,294,196
                dw 247,294,262
                dw -1 ;结束
      mus_time3 dw 3 dup(12,12,25,25),12,12,50
                dw 3 dup(12,12,25,25),12,12,50

      ; 直接定址表
      table dw m1, m2, m3

.code


;============================================================
; 
;             主程序: main
;             功能: 程序入口
;             入口参数:无
;             出口参数:无
; 
;============================================================

main proc

start:
      mov ax, @data
      mov ds, ax

; 改中断例程入口地址
      mov  ax, 0
      mov  es, ax
      push es:[9*4]
      pop  ds:[0]
      push es:[9*4+2]
      pop  ds:[2]
      mov  word ptr es:[9*4], offset int9
      mov  es:[9*4+2], cs

begin:
      call cls
      point 6, 0
      print menu

  lop:
      enter_cmd
      input_21h
      sub al, 48
      cmp al, 0
      je exit
      call call_fun
      jmp lop

 exit:
      ; 恢复原来中断的地址
      mov  ax, 0
      mov  es, ax
      push ds:[0]
      pop  es:[9*4]
      push ds:[2]
      pop  es:[9*4+2]

      call cls

      mov ax, 4c00h
      int 21h
main endp


;============================================================
; 
;             子程序: int9
;             功能: 定义中断例程
;             入口参数:无
;             出口参数:无
; 
;============================================================

int9:
      pushall
      push es

      in al, 60h
      pushf
      call dword ptr ds:[0]

      cmp al, 03bh  ;F1扫描码
      jne int9ret
      ;改变颜色
      mov ax, 0b800h
      mov es, ax
      mov bx, 1     ;指向显存属性单元
      mov cx, 80*25 ;改变整个页面的颜色
    s1:
      inc byte ptr es:[bx]    ;属性值+1
      add bx, 2
      loop s1

int9ret:
      pop es
      popall
      iret


;============================================================
; 
;             子程序: call_fun
;             功能: 选择菜单功能
;             入口参数:al - 功能序号
;             出口参数:无
; 
;============================================================

call_fun proc
    f1:
      cmp al, 1
      jne f2
      jmp retu
    f2:
      cmp al, 2
      jne f3
      push8
      call far ptr piano
      pop8
      jmp retu
    f3:
      cmp al, 3
      jne f4
      call choose_song
      jmp retu
    f4:
      cmp al, 4
      jne f5
      call display_time
      jmp retu
    f5:
      cmp al, 5
      jne f_
      print tip5
      jmp return
    f_:
      in al, 60h
      cmp al, 3bh  ;F1扫描码
      je isf1

      print error
      jmp return

  isf1:
      point 17, 0
      jmp return

  retu:
      call cls
      point 6, 0
      print menu
return:
      ret
call_fun endp


;============================================================
; 
;             子程序: choose_song
;             功能: 输入选择播放的歌曲序号
;             入口参数:无
;             出口参数:无
; 
;============================================================

choose_song proc
      call cls
      point 7, 0
      print songs_name

    cc:
      enter_cmd
    ff:
      input_16h   ;输入选择哪首歌

      cmp ah, 3bh ;F1扫描码
      je ff
      cmp ah, 01h ;ESC扫描码
      je over

      cmp ah, 02h
      je con
      cmp ah, 03h
      je con
      cmp ah, 04h
      je con

      ; print error
      jmp ff

   con:
      sub al, 48
      mov bl, al
      mov bh, 0
      call play_music
      jmp cc

  over:
      ret
choose_song endp


;============================================================
; 
;             子程序: play_music
;             功能: 确定歌曲的数据地址并播放
;             入口参数:无
;             出口参数:无
; 
;============================================================

play_music proc
      add bx, bx
      jmp word ptr table[bx-2]

  m1: setsidi mus_freq1, mus_time1
      jmp play
  m2: setsidi mus_freq2, mus_time2
      jmp play
  m3: setsidi mus_freq3, mus_time3

  play:
      mov dx, [si]
      cmp dx, -1
      je end1play

      in  al, 60h
      cmp al, 10h ;Q扫描码
      je end2play

      call sound
      add si, 2
      add di, 2
      jmp play

end1play:
      print play_finished
      jmp endplay
end2play:
      print play_exited
endplay:
      ret
play_music endp


;============================================================
; 
;             子程序: sound
;             功能: 播放一个音符
;             入口参数:si - 频率
;                      di - 音长
;             出口参数:无
; 
;============================================================

; @演奏一个音符  
sound proc
      pushall

      ; 8253 芯片(定时/计数器)的设置
      mov al, 0b6h  ;8253初始化
      out 43h, al   ;43H是8253芯片控制口的端口地址
      mov dx, 12h
      mov ax, 34dch
      div word ptr [si] ;计算分频值,赋给ax
      out 42h, al   ;先送低8位到计数器,42h是8253芯片通道2的端口地址
      mov al, ah
      out 42h, al   ;后送高8位计数器

      ; 设置8255芯片 控制扬声器的开/关
      in al, 61h    ;读取8255 B端口原值
      mov ah, al    ;保存原值
      or al, 3      ;使低两位置1,以便打开开关
      out 61h, al   ;开扬声器, 发声

      mov dx, [di]  ;保持[di]时长
delay:
      mov cx, 7000h
wait_:
      nop
      loop wait_
      dec dx
      jnz delay

  off:
      mov al, ah    ;恢复扬声器端口原值
      out 61h, al

      popall
      ret
sound endp


;============================================================
; 
;             子程序: cls
;             功能: 清屏
;             入口参数:无
;             出口参数:无
; 
;============================================================

cls proc
      push ax
      mov ah, 0fh ;BIOS-读取显示器模式
      int 10h     ;出口参数之一AL为显示模式
      mov ah, 0h  ;BIOS-设置显示器模式
      int 10h     ;入口参数AL为显示模式(已保存)
      pop ax
      ret
cls endp


;============================================================
; 
;             子程序: display_time
;             功能: 显示系统日期和时间
;             入口参数:无
;             出口参数:无
; 
;============================================================

display_time proc
      pushall
      push es
      push si

 show:
      mov ax, 0b800h
      mov es, ax

      mov si, 0
      mov al, 8   ;月
      call disp_num

      add si, 6
      mov al, 7   ;日
      call disp_num

      add si, 6
      mov al, 4   ;时
      call disp_num

      add si, 6
      mov al, 2   ;分
      call disp_num

      add si, 6
      mov al, 0   ;秒
      call disp_num

      in  al, 60h
      cmp al, 11h ;W扫描码
      je r

      jmp short show

    r:
      pop si
      pop es
      popall
      ret
display_time endp


;============================================================
; 
;             子程序: disp_num
;             功能: 把时间写入内存
;             入口参数:al - 时间对象
;                      si - 要写入内存的地址
;             出口参数:无
; 
;============================================================

disp_num proc
      out 70h, al
      in  al, 71h
      mov ah, al
      mov cl, 4
      shr ah, cl
      and al, 00001111b
      add ah, 30h
      add al, 30h
      mov bx, 0b800h
      mov es, bx
      mov byte ptr es:[160*6+32*2+si], ah
      mov byte ptr es:[160*6+32*2+si+1], 01001111b
      mov byte ptr es:[160*6+32*2+si+2], al
      mov byte ptr es:[160*6+32*2+si+2+1], 01001111b
      mov byte ptr es:[160*6+32*2+si+4], ' '
      mov byte ptr es:[160*6+32*2+si+4+1], 01001111b
      ret
disp_num endp


end start
title piano(exe)
public piano
include macro.mac
.8086
.model small
.stack 10H
.data
      scale dw 659, 262, 294, 330, 349, 392, 440, 494, 524, 587
      ;高3 1 2 3 4 5 6 7 高1 高2  对应 “0123456789”
      piano_exit db ' You can press 0-9. CTRL+Z exit.', '$'

.code

;============================================================
; 
;             子程序: piano
;             功能: 弹奏
;             入口参数:无
;             出口参数:无
; 
;============================================================

piano proc
      pushall

      mov ax, @data
      mov ds, ax

      print piano_exit

   co:
      mov ah, 0
      int 16h

      cmp ah, 2ch ;Ctrl+Z扫描码
      je r

      cmp al, '0'
      jb co
      cmp al, '9'
      ja co

      sub al, 30H
      mov bh, 0
      mov bl, al
      add bx, bx
      mov si, word ptr ds:[scale+bx]

      call sound1

      jmp co

   r:
      popall
      retf
piano endp


;============================================================
; 
;             子程序: sound1
;             功能: 播放一个音符
;             入口参数:无
;             出口参数:无
; 
;============================================================

sound1 proc
      pushall

      mov al, 0b6h      ;8253初始化
      out 43h, al       ;43H是8253芯片控制口的端口地址
      mov dx, 12h
      mov ax, 34dch
      div si            ;计算分频值,赋给ax
      out 42h, al       ;先送低8位到计数器,42h是8253芯片通道2的端口地址
      mov al, ah
      out 42h, al       ;后送高8位计数器

      ; 扬声器的开关
      in  al, 61h ;读取8255 B端口原值
      mov ah, al  ;保存原值
      or  al, 3   ;使低两位置1,以便打开开关
      out 61h, al ;开扬声器, 发声

      mov dx, 10  ;保持
delay:
      mov cx, 7000H
   w:
      nop
      loop w
      dec dx
      cmp dx, 0
      je  off
      jmp delay

 off: mov al, ah  ;恢复扬声器端口原值
      out 61h, al

      popall
      ret
sound1 endp  

end 
; macro.mac

;===========================宏定义===========================

;**************************
; 
; # PUSH ax bx cx dx
; 
;************************** 
pushall macro
      push ax
      push bx
      push cx
      push dx
endm

;**************************
; 
; # POP dx cx bx ax
; 
;************************** 
popall macro
      pop  dx
      pop  cx
      pop  bx
      pop  ax
endm

;**************************
; 
; # PUSH ax bx cx dx es ds si di
; 
;************************** 
push8 macro
      push ax
      push bx
      push cx
      push dx
      push es
      push ds
      push si
      push di
endm

;**************************
; 
; # POP di si ds es dx cx bx ax
; 
;************************** 
pop8 macro
      pop di
      pop si
      pop ds
      pop es
      pop dx
      pop cx
      pop bx
      pop ax
endm

;**************************
; 
; # 设置光标位置 (r,c)
; 
;************************** 
point macro row, column
      pushall
      mov  ah, 02h      ;INT10h的2号中断-设置光标位置
      mov  bh, 0        ;页码-第0页
      mov  dh, row      ;行Y -第row行
      mov  dl, column   ;列X -第column列
      int  10h
      popall
endm

;**************************
; 
; # 显示字符串  以'$'结尾
; 
;************************** 
print macro label
      lea dx, label     ;取偏移地址
      mov ah, 09h       ;21h的9号中断-显示字符串
      int 21h
endm


;**************************
; 
; # 输出一个字符
; 
;************************** 
putchar macro char
      mov ah, 02h
      mov dl, char
      int 21h
endm


;**************************
; 
; # 输入一个字符
; 
;************************** 
input_21h macro
      mov ah, 0ch       ;21h中断的c号功能 AL保存读入字符的ASCII码
      mov al, 01h
      int 21h
endm


;**************************
; 
; # 从键盘读入字符
; # AH保存扫描码
; # AL保存ASCII码
; 
;************************** 
input_16h macro
      mov ah, 0h
      int 16h
endm


;**************************
; 
; # 进入下一行
; 
;************************** 
newline macro
      print nextline
endm


;**************************
; 
; # 下一行等待输入命令
; 
;************************** 
enter_cmd macro
      newline
      putchar '$'
      putchar ' '
endm


;**************************
; 
; # 设置si di偏移地址
; 
;************************** 
setsidi macro S, D
      lea si, S
      lea di, D
endm
;==========================宏定义完==========================

5.3
①选择清屏
②弹奏音乐(部分音阶,Ctrl+Z返回主菜单)
③选择播放音乐(可选三首,可增加,Q结束播放,ESC返回主菜单)
④显示时间(显示的位置算是摆正了,W结束显示并返回主菜单,无法加提示语)
⑤变换界面颜色(中断方法)

title main(exe)
include macro.mac
extrn piano:far
.8086
.model small
.stack 1000H
.data
      zd dw ?, ?     ;存原中断入口地址
      menu db 22 dup(0), 0c9h, 34 dup(0cdh), 0bbh, 10, 13
           db 22 dup(0), 0bah, '            Menu                  ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   1.Clear the screen.            ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   2.Play like a piano.           ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   3.Select a song to play.       ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   4.Displays the current time.   ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   5.Press F1 to change the color.', 0bah, 10, 13
           db 22 dup(0), 0bah, '   0.Exit the program.            ', 0bah, 10, 13
           db 22 dup(0), 0c8h, 34 dup(0cdh), 0bch, 10, 13
      tips db 10, 13, 22 dup(0), 'Please enter a number from 0 to 4.              ', 14, 10, 13, '$'

      time_tip db ' Press the W key to exit the time display.        ', '$'

      tip5 db ' !!!Press F1 to change the color!!!               ', '$'

      nextline db 10, 13, 22 dup(0), '$'

      error db '  Error! Please re-enter.                         ', '$'

      songs_name db 22 dup(0), '         Song track              ', 10, 13
                 db 22 dup(0), '1.Two tigers.(liang zhi lao hu)  ', 10, 13
                 db 22 dup(0), '2.Whitewasher.(fen shua jiang)   ', 10, 13
                 db 22 dup(0), '3.A Good New Year.(xin nian hao) ', 10, 13
                 db 10, 13
                 db 22 dup(0), '    Press Q to quit playing.     ', 10, 13
                 db 22 dup(0), 'Press ESC to return to the menu. ', 10, 13
                 db 10, 13, '$'

      play_finished db '  Play finished.        ', '$'
      play_exited   db '  Playback exited.      ', '$'


      ; 两只老虎
      mus_freq1 dw 2 dup(262,294,330,262)
                dw 2 dup(330,349,392)
                dw 2 dup(392,440,392,349,330,262)
                dw 2 dup(294,196,262)
                dw -1 ;结束
      mus_time1 dw 2 dup(20,20,20,20)
                dw 2 dup(20,20,40)
                dw 2 dup(10,10,10,10,20,20)
                dw 2 dup(20,20,40)

      ; 粉刷匠
      mus_freq2 dw 392,330,392,330,392,330,262
                dw 294,349,330,294,392
                dw 392,330,392,330,392,330,262
                dw 294,349,330,294,262
                dw 294,294,349,349,330,262,392
                dw 294,349,330,294,392
                dw 392,330,392,330,392,330,262
                dw 294,349,330,294,262
                dw -1 ;结束
      mus_time2 dw 3 dup(10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,40h)
                dw 10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,20h

      ; 新年好
      mus_freq3 dw 262,262,262,196
                dw 330,330,330,262
                dw 262,330,392,392
                dw 349,330,294
                dw 294,330,349,349
                dw 330,294,330,262
                dw 262,330,294,196
                dw 247,294,262
                dw -1 ;结束
      mus_time3 dw 3 dup(12,12,25,25),12,12,50
                dw 3 dup(12,12,25,25),12,12,50

      ; 直接定址表
      table dw m1, m2, m3

.code

main proc

start:
      mov ax, @data
      mov ds, ax

; 改中断例程入口地址
      mov  ax, 0
      mov  es, ax
      push es:[9*4]
      pop  ds:[0]
      push es:[9*4+2]
      pop  ds:[2]
      mov  word ptr es:[9*4], offset int9
      mov  es:[9*4+2], cs

begin:
      call cls
      point 6, 0
      print menu

  lop:
      enter_cmd
      input_21h
      sub al, 48
      cmp al, 0
      je exit
      call call_fun
      jmp lop

 exit:
      ; 恢复原来的地址
      mov  ax, 0
      mov  es, ax
      push ds:[0]
      pop  es:[9*4]
      push ds:[2]
      pop  es:[9*4+2]

      call cls

      mov ax, 4c00h
      int 21h
main endp


; 定义中断例程
int9:
      pushall
      push es

      in al, 60h
      pushf
      call dword ptr ds:[0]

      cmp al, 03bh  ;F1扫描码
      jne int9ret
      ;改变颜色
      mov ax, 0b800h
      mov es, ax
      mov bx, 1     ;指向显存属性单元
      mov cx, 80*25 ;改变整个页面的颜色
    s1:
      inc byte ptr es:[bx]    ;属性值+1
      add bx, 2
      loop s1

int9ret:
      pop es
      popall
      iret



; @功能选择 al:选择的功能号
call_fun proc
    f1:
      cmp al, 1
      jne f2
      jmp retu
    f2:
      cmp al, 2
      jne f3
      push8
      call far ptr piano
      pop8
      jmp retu
    f3:
      cmp al, 3
      jne f4
      call choose_song
      jmp retu
    f4:
      cmp al, 4
      jne f5
      call display_time
      jmp retu
    f5:
      cmp al, 5
      jne f_
      print tip5
      jmp return
    f_:
      in al, 60h
      cmp al, 3bh  ;F1扫描码
      je isf1

      print error
      jmp return

  isf1:
      point 17, 0
      jmp return

  retu:
      call cls
      point 6, 0
      print menu
return:
      ret
call_fun endp



; @选择播放的歌曲
choose_song proc
      call cls
      point 7, 0
      print songs_name

    cc:
      enter_cmd
    ff:
      input_16h

      cmp ah, 3bh ;F1扫描码
      je ff
      cmp ah, 01h ;ESC扫描码
      je over

      cmp ah, 02h
      je con
      cmp ah, 03h
      je con
      cmp ah, 04h
      je con

      ; print error
      jmp ff

   con:
      sub al, 48
      mov bl, al
      mov bh, 0
      call play_music
      jmp cc

  over:
      ret
choose_song endp



play_music proc
      add bx, bx
      jmp word ptr table[bx-2]

  m1: setsidi mus_freq1, mus_time1
      jmp play
  m2: setsidi mus_freq2, mus_time2
      jmp play
  m3: setsidi mus_freq3, mus_time3

  play:
      mov dx, [si]
      cmp dx, -1
      je end1play

      in  al, 60h
      cmp al, 10h ;Q扫描码
      je end2play

      call sound
      add si, 2
      add di, 2
      jmp play

end1play:
      print play_finished
      jmp endplay
end2play:
      print play_exited
endplay:
      ret
play_music endp



; @演奏一个音符  si频率 di音长
sound proc
      pushall

      ; 8253 芯片(定时/计数器)的设置
      mov al, 0b6h  ;8253初始化
      out 43h, al   ;43H是8253芯片控制口的端口地址
      mov dx, 12h
      mov ax, 34dch
      div word ptr [si] ;计算分频值,赋给ax
      out 42h, al   ;先送低8位到计数器,42h是8253芯片通道2的端口地址
      mov al, ah
      out 42h, al   ;后送高8位计数器

      ; 设置8255芯片 控制扬声器的开/关
      in al, 61h    ;读取8255 B端口原值
      mov ah, al    ;保存原值
      or al, 3      ;使低两位置1,以便打开开关
      out 61h, al   ;开扬声器, 发声

      mov dx, [di]  ;保持[di]时长
delay:
      mov cx, 7000h
wait_:
      nop
      loop wait_
      dec dx
      jnz delay

  off:
      mov al, ah    ;恢复扬声器端口原值
      out 61h, al

      popall
      ret
sound endp



; @清屏
cls proc
      push ax
      mov ah, 0fh ;BIOS-读取显示器模式
      int 10h     ;出口参数之一AL为显示模式
      mov ah, 0h  ;BIOS-设置显示器模式
      int 10h     ;入口参数AL为显示模式(已保存)
      pop ax
      ret
cls endp



; 显示系统日期和时间
display_time proc
      pushall
      push es
      push si

 show:
      mov ax, 0b800h
      mov es, ax

      mov si, 0
      mov al, 8   ;月
      call disp_num

      add si, 6
      mov al, 7   ;日
      call disp_num

      add si, 6
      mov al, 4   ;时
      call disp_num

      add si, 6
      mov al, 2   ;分
      call disp_num

      add si, 6
      mov al, 0   ;秒
      call disp_num

      in  al, 60h
      cmp al, 11h ;W扫描码
      je r

      jmp short show

    r:
      pop si
      pop es
      popall
      ret
display_time endp



disp_num proc
      out 70h, al
      in  al, 71h
      mov ah, al
      mov cl, 4
      shr ah, cl
      and al, 00001111b
      add ah, 30h
      add al, 30h
      mov bx, 0b800h
      mov es, bx
      mov byte ptr es:[160*6+32*2+si], ah
      mov byte ptr es:[160*6+32*2+si+1], 01001111b
      mov byte ptr es:[160*6+32*2+si+2], al
      mov byte ptr es:[160*6+32*2+si+2+1], 01001111b
      mov byte ptr es:[160*6+32*2+si+4], ' '
      mov byte ptr es:[160*6+32*2+si+4+1], 01001111b
      ret
disp_num endp


end start
title piano(exe)
public piano
include macro.mac
.8086
.model small
.stack 10H
.data
      scale dw 659, 262, 294, 330, 349, 392, 440, 494, 524, 587
      ;高3 1 2 3 4 5 6 7 高1 高2  对应 “0123456789”
      piano_exit db ' You can press 0-9. CTRL+Z exit.', '$'
.code
piano proc
      pushall

      mov ax, @data
      mov ds, ax

      print piano_exit

   co:
      mov ah, 0
      int 16h

      cmp ah, 2ch ;Ctrl+Z扫描码
      je r

      cmp al, '0'
      jb co
      cmp al, '9'
      ja co

      sub al, 30H
      mov bh, 0
      mov bl, al
      add bx, bx
      mov si, word ptr ds:[scale+bx]

      call sound1

      jmp co

   r:
      popall
      retf
piano endp


sound1 proc
      pushall

      mov al, 0b6h      ;8253初始化
      out 43h, al       ;43H是8253芯片控制口的端口地址
      mov dx, 12h
      mov ax, 34dch
      div si            ;计算分频值,赋给ax
      out 42h, al       ;先送低8位到计数器,42h是8253芯片通道2的端口地址
      mov al, ah
      out 42h, al       ;后送高8位计数器

      ; 扬声器的开关
      in  al, 61h ;读取8255 B端口原值
      mov ah, al  ;保存原值
      or  al, 3   ;使低两位置1,以便打开开关
      out 61h, al ;开扬声器, 发声

      mov dx, 10  ;保持
delay:
      mov cx, 7000H
   w:
      nop
      loop w
      dec dx
      cmp dx, 0
      je  off
      jmp delay

 off: mov al, ah  ;恢复扬声器端口原值
      out 61h, al

      popall
      ret
sound1 endp  
end 

;===========================宏定义===========================
; # PUSH ax bx cx dx
pushall macro
      push ax
      push bx
      push cx
      push dx
endm


; # POP dx cx bx ax
popall macro
      pop  dx
      pop  cx
      pop  bx
      pop  ax
endm


push8 macro
      push ax
      push bx
      push cx
      push dx
      push es
      push ds
      push si
      push di
endm


pop8 macro
      pop di
      pop si
      pop ds
      pop es
      pop dx
      pop cx
      pop bx
      pop ax
endm


; # 设置光标位置
point macro row, column
      pushall
      mov  ah, 02h      ;INT10h的2号中断-设置光标位置
      mov  bh, 0        ;页码-第0页
      mov  dh, row      ;行Y -第row行
      mov  dl, column   ;列X -第column列
      int  10h
      popall
endm


; # 显示到屏幕  以'$'结尾
print macro label
      lea dx, label     ;取偏移地址
      mov ah, 09h       ;21h的9号中断-显示字符串
      int 21h
endm


; # 输出一个字符
putchar macro char
      mov ah, 02h
      mov dl, char
      int 21h
endm


; # 输入一个字符
input_21h macro
      mov ah, 0ch       ;21h中断的c号功能 AL保存读入字符的ASCII码
      mov al, 01h
      int 21h
endm


; # 从键盘读入字符,AH保存扫描码 AL保存ASCII码
input_16h macro
      mov ah, 0h
      int 16h
endm


; # 进入下一行
newline macro
      print nextline
endm


; # 选择功能显示
enter_cmd macro
      newline
      putchar '$'
      putchar ' '
endm


setsidi macro S, D
      lea si, S
      lea di, D
endm
;==========================宏定义完==========================

5.2

①选择清屏
②弹奏音乐(部分音阶)
③选择播放音乐(可选三首,可增加)
④显示时间(显示位置没解决)
⑤变换界面颜色(中断方法)

title main(exe)
include macro.mac
extrn piano:far
.8086
.model small
.stack 1000H
.data
      zd dw ?, ?     ;存原中断入口地址
      menu db 22 dup(0), 0c9h, 34 dup(0cdh), 0bbh, 10, 13
           db 22 dup(0), 0bah, '            Menu                  ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   1.Clear the screen.            ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   2.Play like a piano.           ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   3.Select a song to play.       ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   4.Displays the current time.   ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   5.Press F1 to change the color.', 0bah, 10, 13
           db 22 dup(0), 0bah, '   0.Exit the program.            ', 0bah, 10, 13
           db 22 dup(0), 0c8h, 34 dup(0cdh), 0bch, 10, 13
      tips db 10, 13, 22 dup(0), 'Please enter a number from 0 to 4.              ', 14, 10, 13, '$'

      time_tip db ' Press the W key to exit the time display.        ', '$'

      tip5 db ' !!!Press F1 to change the color!!!               ', '$'

      nextline db 10, 13, 22 dup(0), '$'

      error db '  Error! Please re-enter.                         ', '$'

      songs_name db 22 dup(0), '         Song track              ', 10, 13
                 db 22 dup(0), '1.Two tigers.(liang zhi lao hu)  ', 10, 13
                 db 22 dup(0), '2.Whitewasher.(fen shua jiang)   ', 10, 13
                 db 22 dup(0), '3.A Good New Year.(xin nian hao) ', 10, 13
                 db 10, 13
                 db 22 dup(0), '    Press Q to quit playing.     ', 10, 13
                 db 22 dup(0), 'Press ESC to return to the menu. ', 10, 13
                 db 10, 13, '$'

      play_finished db '  Play finished.        ', '$'
      play_exited   db '  Playback exited.      ', '$'


      ; 两只老虎
      mus_freq1 dw 2 dup(262,294,330,262)
                dw 2 dup(330,349,392)
                dw 2 dup(392,440,392,349,330,262)
                dw 2 dup(294,196,262)
                dw -1 ;结束
      mus_time1 dw 2 dup(20,20,20,20)
                dw 2 dup(20,20,40)
                dw 2 dup(10,10,10,10,20,20)
                dw 2 dup(20,20,40)

      ; 粉刷匠
      mus_freq2 dw 392,330,392,330,392,330,262
                dw 294,349,330,294,392
                dw 392,330,392,330,392,330,262
                dw 294,349,330,294,262
                dw 294,294,349,349,330,262,392
                dw 294,349,330,294,392
                dw 392,330,392,330,392,330,262
                dw 294,349,330,294,262
                dw -1 ;结束
      mus_time2 dw 3 dup(10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,40h)
                dw 10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,20h

      ; 新年好
      mus_freq3 dw 262,262,262,196
                dw 330,330,330,262
                dw 262,330,392,392
                dw 349,330,294
                dw 294,330,349,349
                dw 330,294,330,262
                dw 262,330,294,196
                dw 247,294,262
                dw -1 ;结束
      mus_time3 dw 3 dup(12,12,25,25),12,12,50
                dw 3 dup(12,12,25,25),12,12,50

      ; 直接定址表
      table dw m1, m2, m3

.code

main proc

start:
      mov ax, @data
      mov ds, ax

; 改中断例程入口地址
      mov  ax, 0
      mov  es, ax
      push es:[9*4]
      pop  ds:[0]
      push es:[9*4+2]
      pop  ds:[2]
      mov  word ptr es:[9*4], offset int9
      mov  es:[9*4+2], cs

begin:
      call cls
      point 6, 0
      print menu

  lop:
      enter_cmd
      input_21h
      sub al, 48
      cmp al, 0
      je exit
      call call_fun
      jmp lop

 exit:
      ; 恢复原来的地址
      mov  ax, 0
      mov  es, ax
      push ds:[0]
      pop  es:[9*4]
      push ds:[2]
      pop  es:[9*4+2]

      call cls

      mov ax, 4c00h
      int 21h
main endp


; 定义中断例程
int9:
      pushall
      push es

      in al, 60h
      pushf
      call dword ptr ds:[0]

      cmp al, 03bh  ;F1扫描码
      jne int9ret
      ;改变颜色
      mov ax, 0b800h
      mov es, ax
      mov bx, 1     ;指向显存属性单元
      mov cx, 80*25 ;改变整个页面的颜色
    s1:
      inc byte ptr es:[bx]    ;属性值+1
      add bx, 2
      loop s1

int9ret:
      pop es
      popall
      iret



; @功能选择 al:选择的功能号
call_fun proc
    f1:
      cmp al, 1
      jne f2
      jmp retu
    f2:
      cmp al, 2
      jne f3
      push8
      call far ptr piano
      pop8
      jmp retu
    f3:
      cmp al, 3
      jne f4
      call choose_song
      jmp retu
    f4:
      cmp al, 4
      jne f5
      call display_time
      jmp retu
    f5:
      cmp al, 5
      jne f_
      print tip5
      jmp return
    f_:
      in al, 60h
      cmp al, 3bh  ;F1扫描码
      je isf1

      print error
      jmp return

  isf1:
      point 17, 0
      jmp return

  retu:
      call cls
      point 6, 0
      print menu
return:
      ret
call_fun endp



; @选择播放的歌曲
choose_song proc
      call cls
      point 7, 0
      print songs_name

    cc:
      enter_cmd
    ff:
      input_16h

      cmp ah, 3bh ;F1扫描码
      je ff
      cmp ah, 01h ;ESC扫描码
      je over

      cmp ah, 02h
      je con
      cmp ah, 03h
      je con
      cmp ah, 04h
      je con

      ; print error
      jmp ff

   con:
      sub al, 48
      mov bl, al
      mov bh, 0
      call play_music
      jmp cc

  over:
      ret
choose_song endp



play_music proc
      add bx, bx
      jmp word ptr table[bx-2]

  m1: setsidi mus_freq1, mus_time1
      jmp play
  m2: setsidi mus_freq2, mus_time2
      jmp play
  m3: setsidi mus_freq3, mus_time3

  play:
      mov dx, [si]
      cmp dx, -1
      je end1play

      in  al, 60h
      cmp al, 10h ;Q扫描码
      je end2play

      call sound
      add si, 2
      add di, 2
      jmp play

end1play:
      print play_finished
      jmp endplay
end2play:
      print play_exited
endplay:
      ret
play_music endp



; @演奏一个音符  si频率 di音长
sound proc
      pushall

      ; 8253 芯片(定时/计数器)的设置
      mov al, 0b6h  ;8253初始化
      out 43h, al   ;43H是8253芯片控制口的端口地址
      mov dx, 12h
      mov ax, 34dch
      div word ptr [si] ;计算分频值,赋给ax
      out 42h, al   ;先送低8位到计数器,42h是8253芯片通道2的端口地址
      mov al, ah
      out 42h, al   ;后送高8位计数器

      ; 设置8255芯片 控制扬声器的开/关
      in al, 61h    ;读取8255 B端口原值
      mov ah, al    ;保存原值
      or al, 3      ;使低两位置1,以便打开开关
      out 61h, al   ;开扬声器, 发声

      mov dx, [di]  ;保持[di]时长
delay:
      mov cx, 7000h
wait_:
      nop
      loop wait_
      dec dx
      jnz delay

  off:
      mov al, ah    ;恢复扬声器端口原值
      out 61h, al

      popall
      ret
sound endp



; @清屏
cls proc
      push ax
      mov ah, 0fh ;BIOS-读取显示器模式
      int 10h     ;出口参数之一AL为显示模式
      mov ah, 0h  ;BIOS-设置显示器模式
      int 10h     ;入口参数AL为显示模式(已保存)
      pop ax
      ret
cls endp



; 显示系统日期和时间
display_time proc
      pushall
      push es
      push si

 show:
      mov ax, 0b800h
      mov es, ax

      mov si, 0
      mov al, 8   ;月
      call disp_num

      add si, 6
      mov al, 7   ;日
      call disp_num

      add si, 6
      mov al, 4   ;时
      call disp_num

      add si, 6
      mov al, 2   ;分
      call disp_num

      add si, 6
      mov al, 0   ;秒
      call disp_num

      in  al, 60h
      cmp al, 11h ;W扫描码
      je r

      jmp short show

    r:
      pop si
      pop es
      popall
      ret
display_time endp



disp_num proc
      out 70h, al
      in  al, 71h
      mov ah, al
      mov cl, 4
      shr ah, cl
      and al, 00001111b
      add ah, 30h
      add al, 30h
      mov bx, 0b800h
      mov es, bx
      mov byte ptr es:[160*12+40*2+si], ah
      mov byte ptr es:[160*12+40*2+si+1], 01001111b
      mov byte ptr es:[160*12+40*2+si+2], al
      mov byte ptr es:[160*12+40*2+si+2+1], 01001111b
      mov byte ptr es:[160*12+40*2+si+4], '-'
      mov byte ptr es:[160*12+40*2+si+4+1], 01001111b
      ret
disp_num endp


end start
title piano(exe)
public piano
include macro.mac
.8086
.model small
.stack 10H
.data
      scale dw 659, 262, 294, 330, 349, 392, 440, 494, 524, 587
      ;高3 1 2 3 4 5 6 7 高1 高2  对应 “0123456789”
      piano_exit db ' You can press 0-9. CTRL+Z exit.', '$'
.code
piano proc
      pushall

      mov ax, @data
      mov ds, ax

      print piano_exit

   co:
      mov ah, 0
      int 16h

      cmp ah, 2ch ;Ctrl+Z扫描码
      je r

      cmp al, '0'
      jb co
      cmp al, '9'
      ja co

      sub al, 30H
      mov bh, 0
      mov bl, al
      add bx, bx
      mov si, word ptr ds:[scale+bx]

      call sound1

      jmp co

   r:
      popall
      retf
piano endp


sound1 proc
      pushall

      mov al, 0b6h      ;8253初始化
      out 43h, al       ;43H是8253芯片控制口的端口地址
      mov dx, 12h
      mov ax, 34dch
      div si            ;计算分频值,赋给ax
      out 42h, al       ;先送低8位到计数器,42h是8253芯片通道2的端口地址
      mov al, ah
      out 42h, al       ;后送高8位计数器

      ; 扬声器的开关
      in  al, 61h ;读取8255 B端口原值
      mov ah, al  ;保存原值
      or  al, 3   ;使低两位置1,以便打开开关
      out 61h, al ;开扬声器, 发声

      mov dx, 10  ;保持
delay:
      mov cx, 7000H
   w:
      nop
      loop w
      dec dx
      cmp dx, 0
      je  off
      jmp delay

 off: mov al, ah  ;恢复扬声器端口原值
      out 61h, al

      popall
      ret
sound1 endp  
end 

;===========================宏定义===========================
; # PUSH ax bx cx dx
pushall macro
      push ax
      push bx
      push cx
      push dx
endm


; # POP dx cx bx ax
popall macro
      pop  dx
      pop  cx
      pop  bx
      pop  ax
endm


push8 macro
      push ax
      push bx
      push cx
      push dx
      push es
      push ds
      push si
      push di
endm


pop8 macro
      pop di
      pop si
      pop ds
      pop es
      pop dx
      pop cx
      pop bx
      pop ax
endm


; # 设置光标位置
point macro row, column
      pushall
      mov  ah, 02h      ;INT10h的2号中断-设置光标位置
      mov  bh, 0        ;页码-第0页
      mov  dh, row      ;行Y -第row行
      mov  dl, column   ;列X -第column列
      int  10h
      popall
endm


; # 显示到屏幕  以'$'结尾
print macro label
      lea dx, label     ;取偏移地址
      mov ah, 09h       ;21h的9号中断-显示字符串
      int 21h
endm


; # 输出一个字符
putchar macro char
      mov ah, 02h
      mov dl, char
      int 21h
endm


; # 输入一个字符
input_21h macro
      mov ah, 0ch       ;21h中断的c号功能 AL保存读入字符的ASCII码
      mov al, 01h
      int 21h
endm


; # 从键盘读入字符,AH保存扫描码 AL保存ASCII码
input_16h macro
      mov ah, 0h
      int 16h
endm


; # 进入下一行
newline macro
      print nextline
endm


; # 选择功能显示
enter_cmd macro
      newline
      putchar '$'
      putchar ' '
endm


setsidi macro S, D
      lea si, S
      lea di, D
endm
;==========================宏定义完==========================

5.1

title main(exe)
include macro.mac
extrn piano:far
.8086
.model small
.stack 1000H
.data
      zd dw ?, ?     ;存原中断入口地址
      menu db 22 dup(0), 0c9h, 34 dup(0cdh), 0bbh, 10, 13
           db 22 dup(0), 0bah, '            Menu                  ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   1.Clear the screen.            ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   2.Play like a piano.           ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   3.Select a song to play.       ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   4.Displays the current time.   ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   5.Press F1 to change the color.', 0bah, 10, 13
           db 22 dup(0), 0bah, '   0.Exit the program.            ', 0bah, 10, 13
           db 22 dup(0), 0c8h, 34 dup(0cdh), 0bch, 10, 13
      tips db 10, 13, 22 dup(0), 'Please enter a number from 0 to 4. ', 14, 10, 13, '$'

      tip4 db ' !!!Press W to return to the menu!!!', '$'

      tip5 db ' !!!Press F1 to change the color!!!', '$'

      nextline db 10, 13, 22 dup(0), '$'

      error db '  Error! Please re-enter.', '$'

      songs_name db 22 dup(0), '         Song track              ', 10, 13
                 db 22 dup(0), '1.Two tigers.(liang zhi lao hu)  ', 10, 13
                 db 22 dup(0), '2.Whitewasher.(fen shua jiang)   ', 10, 13
                 db 22 dup(0), '3.A Good New Year.(xin nian hao) ', 10, 13
                 db 10, 13
                 db 22 dup(0), '    Press Q to quit playing.     ', 10, 13
                 db 22 dup(0), 'Press ESC to return to the menu. ', 10, 13
                 db 10, 13, '$'

      play_finished db '  Play finished.', '$'
      play_exited db '  Playback exited.', '$'

      place db 7*80 dup(0), '$'

      ; 两只老虎
      mus_freq1 dw 2 dup(262,294,330,262)
                dw 2 dup(330,349,392)
                dw 2 dup(392,440,392,349,330,262)
                dw 2 dup(294,196,262)
                dw -1 ;结束
      mus_time1 dw 2 dup(20,20,20,20)
                dw 2 dup(20,20,40)
                dw 2 dup(10,10,10,10,20,20)
                dw 2 dup(20,20,40)

      ; 粉刷匠
      mus_freq2 dw 392,330,392,330,392,330,262
                dw 294,349,330,294,392
                dw 392,330,392,330,392,330,262
                dw 294,349,330,294,262
                dw 294,294,349,349,330,262,392
                dw 294,349,330,294,392
                dw 392,330,392,330,392,330,262
                dw 294,349,330,294,262
                dw -1 ;结束
      mus_time2 dw 3 dup(10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,40h)
                dw 10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,20h

      ; 新年好
      mus_freq3 dw 262,262,262,196
                dw 330,330,330,262
                dw 262,330,392,392
                dw 349,330,294
                dw 294,330,349,349
                dw 330,294,330,262
                dw 262,330,294,196
                dw 247,294,262
                dw -1 ;结束
      mus_time3 dw 3 dup(12,12,25,25),12,12,50
                dw 3 dup(12,12,25,25),12,12,50

      ; 直接定址表
      table dw m1, m2, m3

.code

main proc

start:
      mov ax, @data
      mov ds, ax

; 改中断例程入口地址
      mov  ax, 0
      mov  es, ax
      push es:[9*4]
      pop  ds:[0]
      push es:[9*4+2]
      pop  ds:[2]
      mov  word ptr es:[9*4], offset int9
      mov  es:[9*4+2], cs

begin:
      call cls
      point 6, 0
      print menu

  lop:
      enter_cmd
      input_21h
      sub al, 48
      cmp al, 0
      je exit
      call call_fun
      jmp lop

 exit:
      ; 恢复原来的地址
      mov  ax, 0
      mov  es, ax
      push ds:[0]
      pop  es:[9*4]
      push ds:[2]
      pop  es:[9*4+2]

      call cls

      mov ax, 4c00h
      int 21h
main endp


; 定义中断例程
int9:
      pushall
      push es

      in al, 60h
      pushf
      call dword ptr ds:[0]

      cmp al, 03bh  ;F1扫描码
      jne int9ret
      ;改变颜色
      mov ax, 0b800h
      mov es, ax
      mov bx, 1     ;指向显存属性单元
      mov cx, 80*25 ;改变整个页面的颜色
    s1:
      inc byte ptr es:[bx]    ;属性值+1
      add bx, 2
      loop s1

int9ret:
      pop es
      popall
      iret



; @功能选择 al:选择的功能号
call_fun proc
    f1:
      cmp al, 1
      jne f2
      jmp retu
    f2:
      cmp al, 2
      jne f3
      call far ptr piano
      jmp retu
    f3:
      cmp al, 3
      jne f4
      call choose_song
      jmp retu
    f4:
      cmp al, 4
      jne f5
      call display_time
      jmp retu
    f5:
      cmp al, 5
      jne f_
      print tip5
      jmp return
    f_:
      in al, 60h
      cmp al, 3bh  ;F1扫描码
      je isf1

      print error
      jmp return

  isf1:
      point 17, 0
      jmp return

  retu:
      call cls
      point 6, 0
      print menu
return:
      ret
call_fun endp



; @选择播放的歌曲
choose_song proc
      call cls
      point 7, 0
      print songs_name

    cc:
      enter_cmd
    ff:
      input_16h

      cmp ah, 3bh ;F1扫描码
      je ff
      cmp ah, 01h ;ESC扫描码
      je over

      cmp ah, 02h
      je con
      cmp ah, 03h
      je con
      cmp ah, 04h
      je con

      ; print error
      jmp ff

   con:
      sub al, 48
      mov bl, al
      mov bh, 0
      call play_music
      jmp cc

  over:
      ret
choose_song endp



play_music proc
      add bx, bx
      jmp word ptr table[bx-2]

  m1: setsidi mus_freq1, mus_time1
      jmp play
  m2: setsidi mus_freq2, mus_time2
      jmp play
  m3: setsidi mus_freq3, mus_time3

  play:
      mov dx, [si]
      cmp dx, -1
      je end1play

      in  al, 60h
      cmp al, 10h ;Q扫描码
      je end2play

      call sound
      add si, 2
      add di, 2
      jmp play

end1play:
      print play_finished
      jmp endplay
end2play:
      print play_exited
endplay:
      ret
play_music endp



; @演奏一个音符  si频率 di音长
sound proc
      pushall

      ; 8253 芯片(定时/计数器)的设置
      mov al, 0b6h  ;8253初始化
      out 43h, al   ;43H是8253芯片控制口的端口地址
      mov dx, 12h
      mov ax, 34dch
      div word ptr [si] ;计算分频值,赋给ax
      out 42h, al   ;先送低8位到计数器,42h是8253芯片通道2的端口地址
      mov al, ah
      out 42h, al   ;后送高8位计数器

      ; 设置8255芯片 控制扬声器的开/关
      in al, 61h    ;读取8255 B端口原值
      mov ah, al    ;保存原值
      or al, 3      ;使低两位置1,以便打开开关
      out 61h, al   ;开扬声器, 发声

      mov dx, [di]  ;保持[di]时长
delay:
      mov cx, 7000h
wait_:
      nop
      loop wait_
      dec dx
      jnz delay

  off:
      mov al, ah    ;恢复扬声器端口原值
      out 61h, al

      popall
      ret
sound endp



; @清屏
cls proc
      push ax
      mov ah, 0fh ;BIOS-读取显示器模式
      int 10h     ;出口参数之一AL为显示模式
      mov ah, 0h  ;BIOS-设置显示器模式
      int 10h     ;入口参数AL为显示模式(已保存)
      pop ax
      ret
cls endp



; 显示系统日期和时间
display_time proc
      pushall
      push es
      push si

 show:
      mov ax, 0b800h
      mov es, ax

      mov si, 0
      mov al, 8   ;月
      call disp_num

      add si, 6
      mov al, 7   ;日
      call disp_num

      add si, 6
      mov al, 4   ;时
      call disp_num

      add si, 6
      mov al, 2   ;分
      call disp_num

      add si, 6
      mov al, 0   ;秒
      call disp_num

      in  al, 60h
      cmp al, 11h ;W扫描码
      je r

      jmp short show

    r:
      pop si
      pop es
      popall
      ret
display_time endp



disp_num proc
      out 70h, al
      in  al, 71h
      mov ah, al
      mov cl, 4
      shr ah, cl
      and al, 00001111b
      add ah, 30h
      add al, 30h
      mov bx, 0b800h
      mov es, bx
      mov byte ptr es:[160*12+40*2+si], ah
      mov byte ptr es:[160*12+40*2+si+1], 01001111b
      mov byte ptr es:[160*12+40*2+si+2], al
      mov byte ptr es:[160*12+40*2+si+2+1], 01001111b
      mov byte ptr es:[160*12+40*2+si+4], '-'
      mov byte ptr es:[160*12+40*2+si+4+1], 01001111b
      ret
disp_num endp


end start
title piano(exe)
public piano
include macro.mac
.8086
.model small
.stack 10H
.data
      scale dw 262, 294, 330, 349, 392, 440, 494, 524, 587, 659
      ;1 2 3 4 5 6 7 高1 高2 高3  对应键盘 “1234567890”
      piano_exit db ' You can press 0-9. CTRL+Z exit.', 10, 13, '$'
.code
piano proc
      pushall

      mov ax, @data
      mov ds, ax

      print piano_exit

   co:
      mov ah, 0
      int 16h

      cmp ah, 2ch ;Ctrl+Z扫描码
      je r

      cmp al, '0'
      jb co
      cmp al, '9'
      ja co

      mov bh, ah
      sub bh, 2
      mov bl, 0
      add bx, bx
      mov si, ds:[scale+bx]

      call sound1

      jmp co

   r:
      popall
      retf
piano endp


sound1 proc
      pushall

      mov al, 0b6h  ;8253初始化
      out 43h, al   ;43H是8253芯片控制口的端口地址
      mov dx, 12h
      mov ax, 34dch
      div si        ;计算分频值,赋给ax
      out 42h, al   ;先送低8位到计数器,42h是8253芯片通道2的端口地址
      mov al, ah
      out 42h, al   ;后送高8位计数器

      ; 扬声器的开关
      in al, 61h    ;读取8255 B端口原值
      mov ah, al    ;保存原值
      or al, 3      ;使低两位置1,以便打开开关
      out 61h, al   ;开扬声器, 发声

      mov dx, 10    ;保持
delay:
      mov cx, 7000h
    w:nop
      loop w
      dec dx
      jnz delay

 off: mov al, ah    ;恢复扬声器端口原值
      out 61h, al

      popall
      ret
sound1 endp


end

;===========================宏定义===========================
; # PUSH ax bx cx dx
pushall macro
      push ax
      push bx
      push cx
      push dx
endm
; # POP dx cx bx ax
popall macro
      pop  dx
      pop  cx
      pop  bx
      pop  ax
endm

; # 设置光标位置
point macro row, column
      pushall
      mov  ah, 02h      ;INT10h的2号中断-设置光标位置
      mov  bh, 0        ;页码-第0页
      mov  dh, row      ;行Y -第row行
      mov  dl, column   ;列X -第column列
      int  10h
      popall
endm

; # 显示到屏幕  以'$'结尾
print macro label
      lea dx, label     ;取偏移地址
      mov ah, 09h       ;21h的9号中断-显示字符串
      int 21h
endm

; # 输出一个字符
putchar macro char
      mov ah, 02h
      mov dl, char
      int 21h
endm

; # 输入一个字符
input_21h macro
      mov ah, 0ch       ;21h中断的c号功能 AL保存读入字符的ASCII码
      mov al, 01h
      int 21h
endm

; # 从键盘读入字符,AH保存扫描码 AL保存ASCII码
input_16h macro
      mov ah, 0h
      int 16h
endm

; # 进入下一行
newline macro
      print nextline
endm

; # 选择功能显示
enter_cmd macro
      newline
      putchar '$'
      putchar ' '
endm

setsidi macro S, D
      lea si, S
      lea di, D
endm
;==========================宏定义完==========================

4.2

title main(exe)
include macro.mac
.8086
.model small
.stack 1000H
.data
      zd dw ?, ?     ;存原中断入口地址
      menu db 22 dup(0), 0c9h, 34 dup(0cdh), 0bbh, 10, 13
           db 22 dup(0), 0bah, '            Menu                  ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   1.Clear the screen.            ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   2.Play like a piano.           ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   3.Select a song to play.       ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   4.Displays the current time.   ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   5.Press F1 to change the color.', 0bah, 10, 13
           db 22 dup(0), 0bah, '   0.Exit the program.            ', 0bah, 10, 13
           db 22 dup(0), 0c8h, 34 dup(0cdh), 0bch, 10, 13
      tips db 10, 13, 22 dup(0), 'Please enter a number from 0 to 4. ', 14, 10, 13, '$'

      tip4 db ' !!!Press W to return to the menu!!!', '$'

      tip5 db ' !!!Press F1 to change the color!!!', '$'

      nextline db 10, 13, 22 dup(0), '$'

      error db '  Error! Please re-enter.', '$'

      songs_name db 22 dup(0), '         Song track              ', 10, 13
                 db 22 dup(0), '1.Two tigers.(liang zhi lao hu)  ', 10, 13
                 db 22 dup(0), '2.Whitewasher.(fen shua jiang)   ', 10, 13
                 db 22 dup(0), '3.A Good New Year.(xin nian hao) ', 10, 13
                 db 10, 13
                 db 22 dup(0), '    Press Q to quit playing.     ', 10, 13
                 db 22 dup(0), 'Press ESC to return to the menu. ', 10, 13
                 db 10, 13, '$'

      play_finished db '  Play finished.', '$'
      play_exited db '  Playback exited.', '$'

      place db 7*80 dup(0), '$'

      ; 两只老虎
      mus_freq1 dw 2 dup(262,294,330,262)
                dw 2 dup(330,349,392)
                dw 2 dup(392,440,392,349,330,262)
                dw 2 dup(294,196,262)
                dw -1 ;结束
      mus_time1 dw 2 dup(20,20,20,20)
                dw 2 dup(20,20,40)
                dw 2 dup(10,10,10,10,20,20)
                dw 2 dup(20,20,40)

      ; 粉刷匠
      mus_freq2 dw 392,330,392,330,392,330,262
                dw 294,349,330,294,392
                dw 392,330,392,330,392,330,262
                dw 294,349,330,294,262
                dw 294,294,349,349,330,262,392
                dw 294,349,330,294,392
                dw 392,330,392,330,392,330,262
                dw 294,349,330,294,262
                dw -1 ;结束
      mus_time2 dw 3 dup(10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,40h)
                dw 10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,20h

      ; 新年好
      mus_freq3 dw 262,262,262,196
                dw 330,330,330,262
                dw 262,330,392,392
                dw 349,330,294
                dw 294,330,349,349
                dw 330,294,330,262
                dw 262,330,294,196
                dw 247,294,262
                dw -1 ;结束
      mus_time3 dw 3 dup(12,12,25,25),12,12,50
                dw 3 dup(12,12,25,25),12,12,50

      ; 直接定址表
      table dw m1, m2, m3

.code

main proc

start:
      mov ax, @data
      mov ds, ax

; 改中断例程入口地址
      mov  ax, 0
      mov  es, ax
      push es:[9*4]
      pop  ds:[0]
      push es:[9*4+2]
      pop  ds:[2]
      mov  word ptr es:[9*4], offset int9
      mov  es:[9*4+2], cs

begin:
      call cls
      point 6, 0
      print menu

  lop:
      enter_cmd
      input_21h
      sub al, 48
      cmp al, 0
      je exit
      call call_fun
      jmp lop

 exit:
      ; 恢复原来的地址
      mov  ax, 0
      mov  es, ax
      push ds:[0]
      pop  es:[9*4]
      push ds:[2]
      pop  es:[9*4+2]

      call cls

      mov ax, 4c00h
      int 21h
main endp


; 定义中断例程
int9:
      pushall
      push es

      in al, 60h
      pushf
      call dword ptr ds:[0]

      cmp al, 03bh  ;F1扫描码
      jne int9ret
      ;改变颜色
      mov ax, 0b800h
      mov es, ax
      mov bx, 1     ;指向显存属性单元
      mov cx, 80*25 ;改变整个页面的颜色
    s1:
      inc byte ptr es:[bx]    ;属性值+1
      add bx, 2
      loop s1

int9ret:
      pop es
      popall
      iret



; @功能选择 al:选择的功能号
call_fun proc
    f1:
      cmp al, 1
      jne f2
      jmp retu
    f2:
      cmp al, 2
      jne f3
      ; call piano
      jmp retu
    f3:
      cmp al, 3
      jne f4
      call choose_song
      jmp retu
    f4:
      cmp al, 4
      jne f5
      call display_time
      jmp retu
    f5:
      cmp al, 5
      jne f_
      print tip5
      jmp return
    f_:
      in al, 60h
      cmp al, 3bh  ;F1扫描码
      je isf1

      print error
      jmp return

  isf1:
      point 17, 0
      jmp return

  retu:
      call cls
      point 6, 0
      print menu
return:
      ret
call_fun endp



; @选择播放的歌曲
choose_song proc
      call cls
      point 7, 0
      print songs_name

    cc:
      enter_cmd
    ff:
      input_16h

      cmp ah, 3bh ;F1扫描码
      je ff
      cmp ah, 01h ;ESC扫描码
      je over

      cmp ah, 02h
      je con
      cmp ah, 03h
      je con
      cmp ah, 04h
      je con

      ; print error
      jmp ff

   con:
      sub al, 48
      mov bl, al
      mov bh, 0
      call play_music
      jmp cc

  over:
      ret
choose_song endp



play_music proc
      add bx, bx
      jmp word ptr table[bx-2]

  m1: setsidi mus_freq1, mus_time1
      jmp play
  m2: setsidi mus_freq2, mus_time2
      jmp play
  m3: setsidi mus_freq3, mus_time3

  play:
      mov dx, [si]
      cmp dx, -1
      je end1play

      in  al, 60h
      cmp al, 10h ;Q扫描码
      je end2play

      call sound
      add si, 2
      add di, 2
      jmp play

end1play:
      print play_finished
      jmp endplay
end2play:
      print play_exited
endplay:
      ret
play_music endp



; @演奏一个音符  si频率 di音长
sound proc
      pushall

      ; 8253 芯片(定时/计数器)的设置
      mov al, 0b6h  ;8253初始化
      out 43h, al   ;43H是8253芯片控制口的端口地址
      mov dx, 12h
      mov ax, 34dch
      div word ptr [si] ;计算分频值,赋给ax
      out 42h, al   ;先送低8位到计数器,42h是8253芯片通道2的端口地址
      mov al, ah
      out 42h, al   ;后送高8位计数器

      ; 设置8255芯片 控制扬声器的开/关
      in al, 61h    ;读取8255 B端口原值
      mov ah, al    ;保存原值
      or al, 3      ;使低两位置1,以便打开开关
      out 61h, al   ;开扬声器, 发声

      mov dx, [di]  ;保持[di]时长
delay:
      mov cx, 7000h
wait_:
      nop
      loop wait_
      dec dx
      jnz delay

  off:
      mov al, ah    ;恢复扬声器端口原值
      out 61h, al

      popall
      ret
sound endp



; @清屏
cls proc
      push ax
      mov ah, 0fh ;BIOS-读取显示器模式
      int 10h     ;出口参数之一AL为显示模式
      mov ah, 0h  ;BIOS-设置显示器模式
      int 10h     ;入口参数AL为显示模式(已保存)
      pop ax
      ret
cls endp



; 显示系统日期和时间
display_time proc
      pushall
      push es
      push si

 show:
      mov ax, 0b800h
      mov es, ax

      mov si, 0
      mov al, 8   ;月
      call disp_num

      add si, 6
      mov al, 7   ;日
      call disp_num

      add si, 6
      mov al, 4   ;时
      call disp_num

      add si, 6
      mov al, 2   ;分
      call disp_num

      add si, 6
      mov al, 0   ;秒
      call disp_num

      in  al, 60h
      cmp al, 11h ;W扫描码
      je r

      jmp short show

    r:
      pop si
      pop es
      popall
      ret
display_time endp



disp_num proc
      out 70h, al
      in  al, 71h
      mov ah, al
      mov cl, 4
      shr ah, cl
      and al, 00001111b
      add ah, 30h
      add al, 30h
      mov bx, 0b800h
      mov es, bx
      mov byte ptr es:[160*12+40*2+si], ah
      mov byte ptr es:[160*12+40*2+si+1], 01001111b
      mov byte ptr es:[160*12+40*2+si+2], al
      mov byte ptr es:[160*12+40*2+si+2+1], 01001111b
      mov byte ptr es:[160*12+40*2+si+4], '-'
      mov byte ptr es:[160*12+40*2+si+4+1], 01001111b
      ret
disp_num endp


end start

;===========================宏定义===========================
; # PUSH ax bx cx dx
pushall macro
      push ax
      push bx
      push cx
      push dx
endm
; # POP dx cx bx ax
popall macro
      pop  dx
      pop  cx
      pop  bx
      pop  ax
endm

; # 设置光标位置
point macro row, column
      pushall
      mov  ah, 02h      ;INT10h的2号中断-设置光标位置
      mov  bh, 0        ;页码-第0页
      mov  dh, row      ;行Y -第row行
      mov  dl, column   ;列X -第column列
      int  10h
      popall
endm

; # 显示到屏幕 '$'结尾
print macro label
      lea dx, label     ;取偏移地址
      mov ah, 09h       ;21h的9号中断-显示字符串
      int 21h
endm

; # 输出一个字符
putchar macro char
      mov ah, 02h
      mov dl, char
      int 21h
endm

; # 输入一个字符
input_21h macro
      mov ah, 0ch       ;21h中断的c号功能 AL保存读入字符的ASCII码
      mov al, 01h
      int 21h
endm

; # 从键盘读入字符,AH保存扫描码 AL保存ASCII码
input_16h macro
      mov ah, 10h
      int 16h
endm

; # 进入下一行
newline macro
      print nextline
endm

; # 选择功能显示
enter_cmd macro
      newline
      putchar '$'
      putchar ' '
endm

setsidi macro S, D
      lea si, S
      lea di, D
endm
;==========================宏定义完==========================

4.1

title main(exe)
.8086
.model small
.stack 100H
.data
      zd dw ?, ?     ;存原中断入口地址
      menu db 22 dup(0), 0c9h, 34 dup(0cdh), 0bbh, 10, 13
           db 22 dup(0), 0bah, '            Menu                  ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   1.Clear the screen.            ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   2.Play like a piano.           ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   3.Select a song to play.       ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   4.Displays the current time.   ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   5.Press F1 to change the color.', 0bah, 10, 13
           db 22 dup(0), 0bah, '   0.Exit the program.            ', 0bah, 10, 13
           db 22 dup(0), 0c8h, 34 dup(0cdh), 0bch, 10, 13
      tips db 10, 13, 22 dup(0), 'Please enter a number from 0 to 4. ', 14, 10, 13, '$'

      tip4 db ' !!!Press W to return to the menu!!!', '$'

      tip5 db ' !!!Press F1 to change the color!!!', '$'

      nextline db 10, 13, 22 dup(0), '$'

      error db '  Error! Please re-enter.', '$'

      songs_name db 22 dup(0), '         Song track              ', 10, 13
                 db 22 dup(0), '1.Two tigers.(liang zhi lao hu)  ', 10, 13
                 db 22 dup(0), '2.Whitewasher.(fen shua jiang)   ', 10, 13
                 db 22 dup(0), '3.A Good New Year.(xin nian hao) ', 10, 13
                 db 10, 13
                 db 22 dup(0), '    Press Q to quit playing.     ', 10, 13
                 db 22 dup(0), 'Press ESC to return to the menu. ', 10, 13
                 db 10, 13, '$'

      play_finished db '  Play finished.', '$'
      play_exited db '  Playback exited.', '$'

      ; 两只老虎
      mus_freq1 dw 2 dup(262,294,330,262)
                dw 2 dup(330,349,392)
                dw 2 dup(392,440,392,349,330,262)
                dw 2 dup(294,196,262)
                dw -1 ;结束
      mus_time1 dw 2 dup(20,20,20,20)
                dw 2 dup(20,20,40)
                dw 2 dup(10,10,10,10,20,20)
                dw 2 dup(20,20,40)

      ; 粉刷匠
      mus_freq2 dw 392,330,392,330,392,330,262
                dw 294,349,330,294,392
                dw 392,330,392,330,392,330,262
                dw 294,349,330,294,262
                dw 294,294,349,349,330,262,392
                dw 294,349,330,294,392
                dw 392,330,392,330,392,330,262
                dw 294,349,330,294,262
                dw -1 ;结束
      mus_time2 dw 3 dup(10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,40h)
                dw 10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,20h

      ; 新年好
      mus_freq3 dw 262,262,262,196
                dw 330,330,330,262
                dw 262,330,392,392
                dw 349,330,294
                dw 294,330,349,349
                dw 330,294,330,262
                dw 262,330,294,196
                dw 247,294,262
                dw -1 ;结束
      mus_time3 dw 3 dup(12,12,25,25),12,12,50
                dw 3 dup(12,12,25,25),12,12,50

      ; 直接定址表
      table dw m1, m2, m3

.code

main proc

;===========================宏定义===========================
; # PUSH ax bx cx dx
pushall macro
      push ax
      push bx
      push cx
      push dx
endm
; # POP dx cx bx ax
popall macro
      pop  dx
      pop  cx
      pop  bx
      pop  ax
endm

; # 设置光标位置
point macro row, column
      pushall
      mov  ah, 02h      ;INT10h的2号中断-设置光标位置
      mov  bh, 0        ;页码-第0页
      mov  dh, row      ;行Y -第row行
      mov  dl, column   ;列X -第column列
      int  10h
      popall
endm

; # 显示到屏幕 '$'结尾
print macro label
      lea dx, label     ;取偏移地址
      mov ah, 09h       ;21h的9号中断-显示字符串
      int 21h
endm

; # 输出一个字符
putchar macro char
      mov ah, 02h
      mov dl, char
      int 21h
endm

; # 输入一个字符
input_21h macro
      mov ah, 0ch       ;21h中断的c号功能 AL保存读入字符的ASCII码
      mov al, 01h
      int 21h
endm

; # 从键盘读入字符,AH保存扫描码 AL保存ASCII码
input_16h macro
      mov ah, 10h
      int 16h
endm

; # 进入下一行
newline macro
      print nextline
endm

; # 选择功能显示
enter_cmd macro
      newline
      putchar '$'
      putchar ' '
endm

setsidi macro S, D
    lea si, S
    lea di, D
endm
;==========================宏定义完==========================

start:
      mov ax, @data
      mov ds, ax

; 改中断例程入口地址
      mov  ax, 0
      mov  es, ax
      push es:[9*4]
      pop  ds:[0]
      push es:[9*4+2]
      pop  ds:[2]
      mov  word ptr es:[9*4], offset int9
      mov  es:[9*4+2], cs

begin:
      call cls
      point 6, 0
      print menu

  lop:
      enter_cmd
      input_21h
      sub al, 48
      cmp al, 0
      je exit
      call call_fun
      jmp lop

 exit:
      ; 恢复原来的地址
      mov  ax, 0
      mov  es, ax
      push ds:[0]
      pop  es:[9*4]
      push ds:[2]
      pop  es:[9*4+2]

      call cls

      mov ax, 4c00h
      int 21h
main endp


; 定义中断例程
int9:
      pushall
      push es

      in al, 60h
      pushf
      call dword ptr ds:[0]

      cmp al, 03bh  ;F1扫描码
      jne int9ret
      ;改变颜色
      mov ax, 0b800h
      mov es, ax
      mov bx, 1     ;指向显存属性单元
      mov cx, 80*25 ;改变整个页面的颜色
    s1:
      inc byte ptr es:[bx]    ;属性值+1
      add bx, 2
      loop s1

int9ret:
      pop es
      popall
      iret



; @功能选择 al:选择的功能号
call_fun proc
    f1:
      cmp al, 1
      jne f2
      call cls
      point 6, 0
      print menu
      jmp return
    f2:
      cmp al, 2
      jne f3
      ; call piano
      jmp return
    f3:
      cmp al, 3
      jne f4
      call choose_song
      jmp return
    f4:
      cmp al, 4
      jne f5
      call display_time
      ; call cls
      ; point 6, 0
      ; print menu
      jmp return
    f5:
      cmp al, 5
      jne f_
      print tip5
      jmp return
    f_:
      in al, 60h
      cmp al, 3bh  ;F1扫描码
      je return

      print error
return:
      ret
call_fun endp



; @选择播放的歌曲
choose_song proc
      call cls
      point 7, 0
      print songs_name

    cc:
      enter_cmd
    ff:
      input_16h

      cmp ah, 3bh ;F1扫描码
      je ff
      cmp ah, 01h ;ESC扫描码
      je over

      cmp ah, 02h
      je con
      cmp ah, 03h
      je con
      cmp ah, 04h
      je con

      ; print error
      jmp ff

   con:
      sub al, 48
      mov bl, al
      mov bh, 0
      call play_music
      jmp cc

  over:
      call cls
      point 6,0
      print menu

      ret
choose_song endp



play_music proc
      add bx, bx
      jmp word ptr table[bx-2]

    m1:
      setsidi mus_freq1, mus_time1
      jmp play
    m2:
      setsidi mus_freq2, mus_time2
      jmp play
    m3:
      setsidi mus_freq3, mus_time3

  play:
      mov dx, [si]
      cmp dx, -1
      je end1play

      in  al, 60h
      cmp al, 10h ;Q扫描码
      je end2play

      call sound
      add si, 2
      add di, 2
      jmp play

end1play:
      print play_finished
      jmp endplay
end2play:
      print play_exited
endplay:
      ret
play_music endp



; @演奏一个音符  si频率 di音长
sound proc
      pushall

      ; 8253 芯片(定时/计数器)的设置
      mov al, 0b6h  ;8253初始化
      out 43h, al   ;43H是8253芯片控制口的端口地址
      mov dx, 12h
      mov ax, 34dch
      div word ptr [si] ;计算分频值,赋给ax
      out 42h, al   ;先送低8位到计数器,42h是8253芯片通道2的端口地址
      mov al, ah
      out 42h, al   ;后送高8位计数器

      ; 设置8255芯片 控制扬声器的开/关
      in al, 61h    ;读取8255 B端口原值
      mov ah, al    ;保存原值
      or al, 3      ;使低两位置1,以便打开开关
      out 61h, al   ;开扬声器, 发声

      mov dx, [di]  ;保持[di]时长
delay:
      mov cx, 7000h
wait_:
      nop
      loop wait_
      dec dx
      jnz delay

  off:
      mov al, ah    ;恢复扬声器端口原值
      out 61h, al

      popall
      ret
sound endp



; @清屏
cls proc
      push ax
      mov ah, 0fh ;BIOS-读取显示器模式
      int 10h     ;出口参数之一AL为显示模式
      mov ah, 0h  ;BIOS-设置显示器模式
      int 10h     ;入口参数AL为显示模式(已保存)
      pop ax
      ret
cls endp
; cls proc
;       pushall
;       mov ax, 0600h     ;向上滚行清窗口
;       mov bh, 3ch
;       mov cx, 0000h     ;左上角位置(Y,X)
;       mov dx, 184fh     ;右下角位置(Y,X)
;       int 10h
;       popall
;       ret
; cls endp



; 显示系统日期和时间
display_time proc
      pushall
      push es
      push si

 show:
      ; mov al, 8
      ; mov si, 0
      ; call disp_num
      ; mov byte ptr es:[4], '.'
      ; mov byte ptr es:[5], 01001111b
 
      ; mov al, 7
      ; mov si, 6
      ; call disp_num
      ; mov byte ptr es:[10], ' '
      ; mov byte ptr es:[11], 01001111b

      ; mov al, 4
      ; mov si, 12
      ; call disp_num
      ; mov byte ptr es:[16], ':'
      ; mov byte ptr es:[17], 01001111b

      ; mov al, 2    ;分
      ; mov si, 18
      ; call disp_num
      ; mov byte ptr es:[22], ':'
      ; mov byte ptr es:[23], 01001111b
 
      ; mov al, 0    ;秒
      ; mov si, 24
      ; call disp_num

      mov ax, 0b800h
      mov es, ax

      mov si, 0
      mov al, 8
      call disp_num

      add si, 6
      mov al, 7
      call disp_num

      add si, 6
      mov al, 4
      call disp_num

      add si, 6
      mov al, 2
      call disp_num

      in  al, 60h
      cmp al, 11h ;W扫描码
      je r

      jmp short show

    r:
      call cls
      point 6, 0
      print menu

      pop si
      pop es
      popall
      ret
display_time endp



disp_num proc
      out 70h, al
      in  al, 71h
      mov ah, al
      mov cl, 4
      shr ah, cl
      and al, 00001111b
      add ah, 30h
      add al, 30h
      mov bx, 0b800h
      mov es, bx
      mov byte ptr es:[160*12+40*2+si], ah
      mov byte ptr es:[160*12+40*2+si+1], 01001111b
      mov byte ptr es:[160*12+40*2+si+2], al
      mov byte ptr es:[160*12+40*2+si+2+1], 01001111b
      mov byte ptr es:[160*12+40*2+si+4], '/'
      mov byte ptr es:[160*12+40*2+si+4+1], 01001111b
      ret
disp_num endp


end start

4.0

title main(exe)
.8086
.model small
.stack 100H
.data
      zd dw ?, ?     ;存原中断入口地址
      menu db 22 dup(0), 0c9h, 34 dup(0cdh), 0bbh, 10, 13
           db 22 dup(0), 0bah, '            Menu                  ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   1.Clear the screen.            ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   2.Play like a piano.           ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   3.Select a song to play.       ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   4.Displays the current time.   ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   5.Press F1 to change the color.', 0bah, 10, 13
           db 22 dup(0), 0bah, '   0.Exit the program.            ', 0bah, 10, 13
           db 22 dup(0), 0c8h, 34 dup(0cdh), 0bch, 10, 13
      tips db 10, 13, 22 dup(0), 'Please enter a number from 0 to 4. ', 14, 10, 13, '$'

      tip5 db ' !!!Press F1 to change the color!!!', '$'

      nextline db 10, 13, 22 dup(0), '$'

      error db '  Error! Please re-enter.', '$'

      songs_name db 22 dup(0), '         Song track              ', 10, 13
                 db 22 dup(0), '1.Two tigers.(liang zhi lao hu)  ', 10, 13
                 db 22 dup(0), '2.Whitewasher.(fen shua jiang)   ', 10, 13
                 db 22 dup(0), '3.A Good New Year.(xin nian hao) ', 10, 13
                 db 10, 13
                 db 22 dup(0), '    Press Q to quit playing.     ', 10, 13
                 db 22 dup(0), 'Press ESC to return to the menu. ', 10, 13
                 db 10, 13, '$'

      play_finished db '  Play finished.', '$'
      play_exited db '  Playback exited.', '$'

      ; 两只老虎
      mus_freq1 dw 2 dup(262,294,330,262)
                dw 2 dup(330,349,392)
                dw 2 dup(392,440,392,349,330,262)
                dw 2 dup(294,196,262)
                dw -1 ;结束
      mus_time1 dw 2 dup(20,20,20,20)
                dw 2 dup(20,20,40)
                dw 2 dup(10,10,10,10,20,20)
                dw 2 dup(20,20,40)

      ; 粉刷匠
      mus_freq2 dw 392,330,392,330,392,330,262
                dw 294,349,330,294,392
                dw 392,330,392,330,392,330,262
                dw 294,349,330,294,262
                dw 294,294,349,349,330,262,392
                dw 294,349,330,294,392
                dw 392,330,392,330,392,330,262
                dw 294,349,330,294,262
                dw -1 ;结束
      mus_time2 dw 3 dup(10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,40h)
                dw 10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,20h

      ; 新年好
      mus_freq3 dw 262,262,262,196
                dw 330,330,330,262
                dw 262,330,392,392
                dw 349,330,294
                dw 294,330,349,349
                dw 330,294,330,262
                dw 262,330,294,196
                dw 247,294,262
                dw -1 ;结束
      mus_time3 dw 3 dup(12,12,25,25),12,12,50
                dw 3 dup(12,12,25,25),12,12,50

      ; 直接定址表
      table dw m1, m2, m3

.code

main proc

;===========================宏定义===========================
; # PUSH ax bx cx dx
pushall macro
      push ax
      push bx
      push cx
      push dx
endm
; # POP dx cx bx ax
popall macro
      pop  dx
      pop  cx
      pop  bx
      pop  ax
endm

; # 设置光标位置
point macro row, column
      pushall
      mov  ah, 02h      ;INT10h的2号中断-设置光标位置
      mov  bh, 0        ;页码-第0页
      mov  dh, row      ;行Y -第row行
      mov  dl, column   ;列X -第column列
      int  10h
      popall
endm

; # 显示到屏幕 '$'结尾
print macro label
      lea dx, label     ;取偏移地址
      mov ah, 09h       ;21h的9号中断-显示字符串
      int 21h
endm

; # 输出一个字符
putchar macro char
      mov ah, 02h
      mov dl, char
      int 21h
endm

; # 输入一个字符
input_21h macro
      mov ah, 0ch       ;21h中断的c号功能 AL保存读入字符的ASCII码
      mov al, 01h
      int 21h
endm

; # 从键盘读入字符,AH保存扫描码 AL保存ASCII码
input_16h macro
      mov ah, 10h
      int 16h
endm

; # 进入下一行
newline macro
      print nextline
endm

; # 选择功能显示
enter_cmd macro
      newline
      putchar '$'
      putchar ' '
endm

setsidi macro S, D
    lea si, S
    lea di, D
endm
;==========================宏定义完==========================

start:
      mov ax, @data
      mov ds, ax

; 改中断例程入口地址
      mov  ax, 0
      mov  es, ax
      push es:[9*4]
      pop  ds:[0]
      push es:[9*4+2]
      pop  ds:[2]
      mov  word ptr es:[9*4], offset int9
      mov  es:[9*4+2], cs

begin:
      call cls
      point 6, 0
      print menu

  lop:
      enter_cmd
      input_21h
      sub al, 48
      cmp al, 0
      je exit
      call call_fun
      jmp lop

 exit:
      ; 恢复原来的地址
      mov  ax, 0
      mov  es, ax
      push ds:[0]
      pop  es:[9*4]
      push ds:[2]
      pop  es:[9*4+2]

      call cls

      mov ax, 4c00h
      int 21h
main endp


; 定义中断例程
int9:
      pushall
      push es

      in al, 60h
      pushf
      call dword ptr ds:[0]

      cmp al, 03bh  ;F1扫描码
      jne int9ret
      ;改变颜色
      mov ax, 0b800h
      mov es, ax
      mov bx, 1     ;指向显存属性单元
      mov cx, 80*25 ;改变整个页面的颜色
    s1:
      inc byte ptr es:[bx]    ;属性值+1
      add bx, 2
      loop s1

int9ret:
      pop es
      popall
      iret



; @功能选择 al:选择的功能号
call_fun proc
    f1:
      cmp al, 1
      jne f2
      call cls
      point 6, 0
      print menu
      jmp return
    f2:
      cmp al, 2
      jne f3
      ; call piano
      jmp return
    f3:
      cmp al, 3
      jne f4
      call choose_song
      jmp return
    f4:
      cmp al, 4
      jne f5
      call display_time
      call cls
      point 6, 0
      print menu
      jmp return
    f5:
      cmp al, 5
      jne f_
      print tip5
      jmp return
    f_:
      in al, 60h
      cmp al, 3bh  ;F1扫描码
      je return

      print error
return:
      ret
call_fun endp



; @选择播放的歌曲
choose_song proc
      call cls
      point 7, 0
      print songs_name

    cc:
      enter_cmd
    ff:
      input_16h

      cmp ah, 3bh ;F1扫描码
      je ff
      cmp ah, 01h ;ESC扫描码
      je over

      cmp ah, 02h
      je con
      cmp ah, 03h
      je con
      cmp ah, 04h
      je con

      ; print error
      jmp ff

   con:
      sub al, 48
      mov bl, al
      mov bh, 0
      call play_music
      ; print play_finished
      jmp cc

  over:
      call cls
      point 6,0
      print menu

      ret
choose_song endp



play_music proc
      add bx, bx
      jmp word ptr table[bx-2]

    m1:
      setsidi mus_freq1, mus_time1
      jmp play
    m2:
      setsidi mus_freq2, mus_time2
      jmp play
    m3:
      setsidi mus_freq3, mus_time3

  play:
      mov dx, [si]
      cmp dx, -1
      je end1play

      in  al, 60h
      cmp al, 10h ;Q扫描码
      je end2play

      call sound
      add si, 2
      add di, 2
      jmp play

end1play:
      print play_finished
      jmp endplay
end2play:
      print play_exited
endplay:
      ret
play_music endp



; @演奏一个音符  si频率 di音长
sound proc
      pushall

      ; 8253 芯片(定时/计数器)的设置
      mov al, 0b6h  ;8253初始化
      out 43h, al   ;43H是8253芯片控制口的端口地址
      mov dx, 12h
      mov ax, 34dch
      div word ptr [si] ;计算分频值,赋给ax
      out 42h, al   ;先送低8位到计数器,42h是8253芯片通道2的端口地址
      mov al, ah
      out 42h, al   ;后送高8位计数器

      ; 设置8255芯片 控制扬声器的开/关
      in al, 61h    ;读取8255 B端口原值
      mov ah, al    ;保存原值
      or al, 3      ;使低两位置1,以便打开开关
      out 61h, al   ;开扬声器, 发声

      mov dx, [di]  ;保持[di]时长
delay:
      mov cx, 7000h
wait_:
      nop
      loop wait_
      dec dx
      jnz delay

  off:
      mov al, ah    ;恢复扬声器端口原值
      out 61h, al

      popall
      ret
sound endp



; @清屏
cls proc
      push ax
      mov ah, 0fh ;BIOS-读取显示器模式
      int 10h     ;出口参数之一AL为显示模式
      mov ah, 0h  ;BIOS-设置显示器模式
      int 10h     ;入口参数AL为显示模式(已保存)
      pop ax
      ret
cls endp
; cls proc
;       pushall
;       mov ax, 0600h     ;向上滚行清窗口
;       mov bh, 3ch
;       mov cx, 0000h     ;左上角位置(Y,X)
;       mov dx, 184fh     ;右下角位置(Y,X)
;       int 10h
;       popall
;       ret
; cls endp



; 显示系统日期和时间
display_time proc
      pushall
      push es
      push si
 show:
      ; mov al, 8
      ; mov si, 0
      ; call disp_num
      ; mov byte ptr es:[4], '.'
      ; mov byte ptr es:[5], 01001111b
 
      ; mov al, 7
      ; mov si, 6
      ; call disp_num
      ; mov byte ptr es:[10], ' '
      ; mov byte ptr es:[11], 01001111b

      ; mov al, 4
      ; mov si, 12
      ; call disp_num
      ; mov byte ptr es:[16], ':'
      ; mov byte ptr es:[17], 01001111b

      ; mov al, 2    ;分
      ; mov si, 18
      ; call disp_num
      ; mov byte ptr es:[22], ':'
      ; mov byte ptr es:[23], 01001111b
 
      ; mov al, 0    ;秒
      ; mov si, 24
      ; call disp_num

      mov ax, 0b800h
      mov es, ax

      mov si, 0
      mov al, 8
      call disp_num

      add si, 6
      mov al, 7
      call disp_num

      add si, 6
      mov al, 4
      call disp_num

      add si, 6
      mov al, 2
      call disp_num

      in  al, 60h
      cmp al, 11h ;W扫描码
      je r

      jmp short show

    r:
      pop si
      pop es
      popall
      ret
display_time endp



disp_num proc
      out 70h, al
      in  al, 71h
      mov ah, al
      mov cl, 4
      shr ah, cl
      and al, 00001111b
      add ah, 30h
      add al, 30h
      mov bx, 0b800h
      mov es, bx
      mov byte ptr es:[160*12+40*2+si], ah
      mov byte ptr es:[160*12+40*2+si+1], 01001111b
      mov byte ptr es:[160*12+40*2+si+2], al
      mov byte ptr es:[160*12+40*2+si+2+1], 01001111b
      mov byte ptr es:[160*12+40*2+si+4], '/'
      mov byte ptr es:[160*12+40*2+si+4+1], 01001111b
      ret
disp_num endp



end start

3.0

title main(exe)
.8086
.model small
.stack 100H
.data
      dw ?, ?     ;存原中断入口地址
      flag db 0
      menu db 22 dup(0), 0c9h, 34 dup(0cdh), 0bbh, 10, 13
           db 22 dup(0), 0bah, '            Menu                  ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   1.Clear the screen.            ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   2.Play like a piano.           ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   3.Select a song to play.       ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   4.Displays the current time.   ', 0bah, 10, 13
           db 22 dup(0), 0bah, '   5.Press F1 to change the color.', 0bah, 10, 13
           db 22 dup(0), 0bah, '   0.Exit the program.            ', 0bah, 10, 13
           db 22 dup(0), 0c8h, 34 dup(0cdh), 0bch, 10, 13
      tips db 10, 13, 22 dup(0), 'Please enter a number from 0 to 4. ', 14, 10, 13, '$'

      tip5 db '  Press F1 to change the color.', '$'

      nextline db 10, 13, 22 dup(0), '$'

      error db '  Error! Please re-enter.', '$'

      songs_name db 22 dup(0), '         Song track              ', 10, 13
                 db 22 dup(0), '1.Two tigers.(liang zhi lao hu)  ', 10, 13
                 db 22 dup(0), '2.Whitewasher.(fen shua jiang)   ', 10, 13
                 db 22 dup(0), '3.A Good New Year.(xin nian hao) ', 10, 13
                 db 10, 13
                 db 22 dup(0), '      Press ESC to exit.         ', 10, 13
                 db 10, 13, '$'

      play_finished db '  Play finished.', '$'

      ; 两只老虎
      mus_freq1 dw 2 dup(262,294,330,262)
                dw 2 dup(330,349,392)
                dw 2 dup(392,440,392,349,330,262)
                dw 2 dup(294,196,262)
                dw -1 ;结束
      mus_time1 dw 2 dup(20,20,20,20)
                dw 2 dup(20,20,40)
                dw 2 dup(10,10,10,10,20,20)
                dw 2 dup(20,20,40)

      ; 粉刷匠
      mus_freq2 dw 392,330,392,330,392,330,262
                dw 294,349,330,294,392
                dw 392,330,392,330,392,330,262
                dw 294,349,330,294,262
                dw 294,294,349,349,330,262,392
                dw 294,349,330,294,392
                dw 392,330,392,330,392,330,262
                dw 294,349,330,294,262
                dw -1 ;结束
      mus_time2 dw 3 dup(10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,40h)
                dw 10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,20h

      ; 新年好
      mus_freq3 dw 262,262,262,196
                dw 330,330,330,262
                dw 262,330,392,392
                dw 349,330,294
                dw 294,330,349,349
                dw 330,294,330,262
                dw 262,330,294,196
                dw 247,294,262
                dw -1 ;结束
      mus_time3 dw 3 dup(12,12,25,25),12,12,50
                dw 3 dup(12,12,25,25),12,12,50

      ; 直接定址表
      table dw m1, m2, m3

.code

main proc

;===========================宏定义===========================
; # 设置光标位置
point macro row, column
      push ax
      push bx
      push cx
      push dx
      mov  ah, 02h      ;INT10h的2号中断-设置光标位置
      mov  bh, 0        ;页码-第0页
      mov  dh, row      ;行Y -第row行
      mov  dl, column   ;列X -第column列
      int  10h
      pop  dx
      pop  cx
      pop  bx
      pop  ax
endm

; # 显示到屏幕 '$'结尾
print macro label
      lea dx, label     ;取偏移地址
      mov ah, 09h       ;21h的9号中断-显示字符串
      int 21h
endm

; # 输出一个字符
putchar macro char
      mov ah, 02h
      mov dl, char
      int 21h
endm

; # 输入一个字符
input_21h macro
      mov ah, 0ch       ;21h中断的c号功能 AL保存读入字符的ASCII码
      mov al, 01h
      int 21h
endm

; # 从键盘读入字符,AH保存扫描码 AL保存ASCII码
input_16h macro
      mov ah, 10h
      int 16h
endm

; # 进入下一行
newline macro
      print nextline
endm

; # 选择功能显示
enter_cmd macro
      newline
      putchar '$'
      putchar ' '
endm

setsidi macro S, D
    lea si, S
    lea di, D
endm
;==========================宏定义完==========================

start:
      mov ax, @data
      mov ds, ax


; 改中断例程入口地址
      mov  ax, 0
      mov  es, ax
      push es:[9*4]
      pop  ds:[0]
      push es:[9*4+2]
      pop  ds:[2]
      mov  word ptr es:[9*4], offset int9
      mov  es:[9*4+2], cs


      call cls
      point 6, 0
      print menu

  lop:
      enter_cmd
      input_21h
      sub al, 48
      cmp al, 0
      je exit
      call call_fun
      jmp lop

 exit:
      ; 恢复原来的地址
      mov  ax, 0
      mov  es, ax
      push ds:[0]
      pop  es:[9*4]
      push ds:[2]
      pop  es:[9*4+2]

      call cls

      mov ax, 4c00h
      int 21h
main endp



;       ; 定义中断例程
; int9: push ax
;       push bx
;       push dx
;       push es
;       in al, 60h
;       pushf
;       pushf
;       pop bx
;       and bh, 11111100b
;       push bx
;       popf
;       call dword ptr ds:[0]

;       mov dl, al ;保存al
; ;       cmp al,47h ; 47h是HOME键的扫描码
; ;       jne ifend
      
; ;       ;处理HOME
; ;       lea dx, home
; ;       mov ah,9
; ;       int 21h
; ;       jmp int9ret
 
; ; ifend:
; ;       cmp dl, 4Fh ;4Fh是end键的扫描码
; ;       jne int9ret
      
; ;       ;处理END,使程序结束,注意在此要恢复中断向量
; ;       mov ax,0
; ;       mov es,ax
 
; ;       push ds:[0]
; ;       pop es:[9*4]
; ;       push ds:[2]
; ;       pop es:[9*4+2]
 
; ;       mov ax,4c00h
; ;       int 21h
 
; int9ret:
;       pop es
;       pop dx
;       pop bx
;       pop ax
;       iret



; 定义中断例程
int9:
      push ax
      push bx
      push cx
      push es

      in al, 60h
      pushf
      call dword ptr ds:[0]

      cmp al, 03bh  ;F1扫描码
      jne int9ret
      ;改变颜色
      mov ax, 0b800h
      mov es, ax
      mov bx, 1     ;指向显存属性单元
      mov cx, 80*25 ;改变整个页面的颜色
    s:
      inc byte ptr es:[bx]    ;属性值+1
      add bx, 2
      loop s

int9ret:
      pop es
      pop cx
      pop bx
      pop ax
      iret



; @功能选择 al:选择的功能号
call_fun proc
    f1:
      cmp al, 1
      jne f2
      call cls
      point 6,0
      print menu
      jmp return
    f2:
      cmp al, 2
      jne f3
      ; call piano
      jmp return
    f3:
      cmp al, 3
      jne f4
      call choose_song
      jmp return
    f4:
      cmp al, 4
      jne f5
      call display_time
      jmp return
    f5:
      cmp al, 5
      jne f_else
      print tip5
      jmp return
f_else:
      in al, 60h
      cmp al, 3bh  ;F1扫描码
      je return
      print error
return:
      ret
call_fun endp


; @选择播放的歌曲
choose_song proc
      call cls
      point 7, 0
      print songs_name

    cc:
      enter_cmd
    ff:
      input_16h

      cmp ah, 3bh ;F1扫描码
      je ff
      cmp ah, 01h ;ESC扫描码
      je over

      cmp ah, 02h
      je con
      cmp ah, 03h
      je con
      cmp ah, 04h
      je con

      ; print error
      jmp ff

   con:
      sub al, 48
      mov bl, al
      mov bh, 0
      call play_music
      print play_finished
      jmp cc

  over:
      call cls
      point 6,0
      print menu

      ret
choose_song endp


play_music proc
      add bx, bx
      jmp word ptr table[bx-2]

    m1:
      setsidi mus_freq1, mus_time1
      jmp play
    m2:
      setsidi mus_freq2, mus_time2
      jmp play
    m3:
      setsidi mus_freq3, mus_time3

  play:
      mov dx, [si]
      cmp dx, -1
      je end_play
      call sound
      add si, 2
      add di, 2
      jmp play

end_play:
      ret
play_music endp



; @演奏一个音符  si频率 di音长
sound proc
      push ax
      push dx
      push cx

      ; 8253 芯片(定时/计数器)的设置
      mov al, 0b6h  ;8253初始化
      out 43h, al   ;43H是8253芯片控制口的端口地址
      mov dx, 12h
      mov ax, 34dch
      div word ptr [si] ;计算分频值,赋给ax
      out 42h, al   ;先送低8位到计数器,42h是8253芯片通道2的端口地址
      mov al, ah
      out 42h, al   ;后送高8位计数器

      ; 设置8255芯片 控制扬声器的开/关
      in al, 61h    ;读取8255 B端口原值
      mov ah, al    ;保存原值
      or al, 3      ;使低两位置1,以便打开开关
      out 61h, al   ;开扬声器, 发声

      mov dx, [di]  ;保持[di]时长
delay:
      mov cx, 7000h
wait_:
      nop
      loop wait_
      dec dx
      jnz delay

      mov al, ah    ;恢复扬声器端口原值
      out 61h, al

      pop cx
      pop dx
      pop ax
      ret
sound endp



; @清屏
cls proc
      push ax
      mov ah, 0fh ;BIOS-读取显示器模式
      int 10h     ;出口参数之一AL为显示模式
      mov ah, 0h  ;BIOS-设置显示器模式
      int 10h     ;入口参数AL为显示模式(已保存)
      pop ax
      ret
cls endp



; 显示系统日期和时间
display_time proc
      push ax
      push bx
      push cx
      push dx
      push es
 show:
      mov al, 2    ;分
      out 70h, al
      in  al, 71h
      mov ah, al
      mov cl, 4
      shr ah, cl
      and al, 00001111b
      add ah, 30h
      add al, 30h
      mov bx, 0b800h
      mov es, bx
      mov byte ptr es:[0], ah
      mov byte ptr es:[1], 01001111b
      mov byte ptr es:[2], al
      mov byte ptr es:[3], 01001111b
 
      mov byte ptr es:[4], ':'
      mov byte ptr es:[5], 01001111b
 
      mov al, 0    ;秒
      out 70h, al
      in  al, 71h
      mov ah, al
      mov cl, 4
      shr ah, cl
      and al, 00001111b
      add ah, 30h
      add al, 30h
      mov bx, 0b800h
      mov es, bx
      mov byte ptr es:[6], ah
      mov byte ptr es:[7], 01001111b
      mov byte ptr es:[8], al
      mov byte ptr es:[9], 01001111b
      jmp show

    r:
      pop es
      pop dx
      pop cx
      pop bx
      pop ax
      ret
display_time endp


end start

1.0

title main(exe)
extrn play_music:far
.8086
.model small
.stack 1000H
.data
	dw 0, 0	;存原中断入口地址
	menu db 22 dup(0), 0c9h, 34 dup(0cdh), 0bbh, 10, 13
    	 db 22 dup(0), 0bah, '             Menu                 ', 0bah, 10, 13
		 db 22 dup(0), 0bah, '   1.Play like a piano.           ', 0bah, 10, 13
		 db 22 dup(0), 0bah, '   2.Select a song to play.       ', 0bah, 10, 13
		 db 22 dup(0), 0bah, '   3.Displays the current time.   ', 0bah, 10, 13
		 db 22 dup(0), 0bah, '   4.Press F1 to change the color.', 0bah, 10, 13
		 db 22 dup(0), 0bah, '   0.Exit the program.            ', 0bah, 10, 13
		 db 22 dup(0), 0c8h, 34 dup(0cdh), 0bch, 10, 13
	tips db 10, 13, 22 dup(0), 'Please enter a number from 0 to 4. ', 14, 10, 13, '$'

	nextline db 10, 13, 22 dup(0), '$'

	error db '  Error! Please re-enter.', '$'

	songs_name db 22 dup(0), '         Song track              ', 10, 13
			   db 22 dup(0), '1.Two tigers.(liang zhi lao hu)  ', 10, 13
			   db 22 dup(0), '2.Whitewasher.(fen shua jiang)   ', 10, 13
			   db 22 dup(0), '3.A Good New Year.(xin nian hao) ', 10, 13
			   db 10, 13
			   db 22 dup(0), '      Press ESC to exit.         ', 10, 13
			   db 10, 13, '$'

	play_finished db '  Play finished.', '$'

.code

main proc

;===========================宏定义===========================
; # 设置光标位置
point macro row, column
	push ax
	push bx
	push cx
	push dx
	mov  ah, 02h	;INT10h的2号中断-设置光标位置
	mov  bh, 0		;页码-第0页
	mov  dh, row	;行Y -第row行
	mov  dl, column	;列X -第column列
	int  10h
	pop  dx
	pop  cx
	pop  bx
	pop  ax
endm

; # 显示到屏幕 '$'结尾
print macro label
	lea dx, label	;取偏移地址
	mov ah, 09h		;21h的9号中断-显示字符串
	int 21h
endm

; # 输出一个字符
putchar macro char
	mov ah, 02h
	mov dl, char
	int 21h
endm

; # 输入一个字符
input_21h macro
	; mov ah, 0ch		;21h中断的c号功能 AL保存读入字符的ASCII码
	; mov al, 01h
	mov ah, 08h
	int 21h
endm

; # 从键盘读入字符,AH保存扫描码 AL保存ASCII码
input_16h macro
	mov ah, 10h
	int 16h
endm

; # 进入下一行
newline macro
	print nextline
endm

; # 选择功能显示
enter_cmd macro
	newline
	putchar '$'
	putchar ' '
endm
;==========================宏定义完==========================

start:
	mov ax, @data
	mov ds, ax
	mov es, ax

; 改中断例程入口地址
	mov ax, 0
	mov es, ax
	push es:[9*4]
	pop  ds:[0]
	push es:[9*4+2]
	pop  ds:[2]
	mov word ptr es:[9*4], offset int9
	mov es:[9*4+2], cs

	call cls
	point 7, 0
	print menu

  l1:
	enter_cmd
  l2:
	input_21h	;输入数字
	sub al, 48	;ASCII码转数字
	cmp al, 0	;判断0退出
	je exit
  f1:
	cmp al, 1
	jne f2
	; call piano
	jmp l1
  f2:
	cmp al, 2
	jne f3
	call choose_song
	jmp l1
  f3:
	cmp al, 3
	jne f4
	; call time
	jmp l1
  f4:
	cmp al, 4
	jne f_else
	; call change_color
	jmp l1
f_else:
	; print error
	jmp l2

exit:
	; 恢复原来的地址
	push ds:[0]
	pop es:[9*4]
	push ds:[2]
	pop es:[9*4+2]

	mov ax, 4c00h
	int 21h
main endp


; 定义中断例程
int9:
	push ax
	push bx
	push es

	in al, 60h
	pushf
	call dword ptr ds:[0]	;调用旧中断例程

	cmp al, 03bh	;F1扫描码
	jne int9ret
	;改变颜色
	mov ax, 0b800h
	mov es, ax
	; inc byte ptr es:[160*12+40*2+1]
	mov bx, 1		;指向显存属性单元
	mov cx, 80*25	;改变整个页面的颜色
  s:
	inc byte ptr es:[bx]	;属性值+1
	add bx, 2
	loop s

int9ret:
	pop es
	pop bx
	pop ax
	iret



; @选择播放的歌曲
choose_song proc
	call cls
	point 7,0
	print songs_name
 cc:
	enter_cmd
 ff:
 	input_16h

	cmp ah, 3bh	;F1扫描码
	je ff
	cmp ah, 01h	;ESC扫描码
	je over

	cmp ah, 02h
	je con
	cmp ah, 03h
	je con
	cmp ah, 04h
	je con

	print error
	jmp cc

con:
	sub al, 48
	mov bl, al
	mov bh, 0
	call far ptr play_music
	print play_finished
	jmp cc

over:
	call cls
	point 7,0
	print menu

	ret
choose_song endp


; @清屏
cls proc
	push ax
	mov ah, 0fh	;BIOS-读取显示器模式
	int 10h		;出口参数之一AL为显示模式
	mov ah, 0h	;BIOS-设置显示器模式
	int 10h		;入口参数AL为显示模式(已保存)
	pop ax
	ret
cls endp

end start

title sing(exe)
public play_music
.8086
.model small
.stack 10H
.data
; 两只老虎
mus_freq1 dw 2 dup(262,294,330,262)
          dw 2 dup(330,349,392)
          dw 2 dup(392,440,392,349,330,262)
          dw 2 dup(294,196,262)
          dw -1 ;结束
mus_time1 dw 2 dup(20,20,20,20)
          dw 2 dup(20,20,40)
          dw 2 dup(10,10,10,10,20,20)
          dw 2 dup(20,20,40)

; 粉刷匠
mus_freq2 dw 392,330,392,330,392,330,262
          dw 294,349,330,294,392
          dw 392,330,392,330,392,330,262
          dw 294,349,330,294,262
          dw 294,294,349,349,330,262,392
          dw 294,349,330,294,392
          dw 392,330,392,330,392,330,262
          dw 294,349,330,294,262
          dw -1 ;结束
mus_time2 dw 3 dup(10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,40h)
		  dw 10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,20h

; 新年好
mus_freq3 dw 262,262,262,196
          dw 330,330,330,262
          dw 262,330,392,392
          dw 349,330,294
          dw 294,330,349,349
          dw 330,294,330,262
          dw 262,330,294,196
          dw 247,294,262
          dw -1 ;结束
mus_time3 dw 3 dup(12,12,25,25),12,12,50
          dw 3 dup(12,12,25,25),12,12,50

; 直接定址表
table dw m1, m2, m3

.code

; 宏定义
setsidi macro S,D
    lea si, S
    lea di, D
endm

; @播放音乐  入口参数bx:选第几首歌
play_music proc
    mov ax, @data
    mov ds, ax

    add bx, bx
    jmp word ptr table[bx-2]

m1: setsidi mus_freq1, mus_time1
    jmp play
m2: setsidi mus_freq2, mus_time2
    jmp play
m3: setsidi mus_freq3, mus_time3

play:
    mov dx, [si]
    cmp dx, -1
    je end_play
    call sound
    add si, 2
    add di, 2
    jmp play

end_play:
    retf
play_music endp


; @演奏一个音符  si频率 di音长
sound proc
    push ax
    push dx
    push cx

    ; 8253 芯片(定时/计数器)的设置
    mov al, 0b6h    ;8253初始化
    out 43h, al     ;43H是8253芯片控制口的端口地址
    mov dx, 12h
    mov ax, 34dch
    div word ptr [si] ;计算分频值,赋给ax
    out 42h, al       ;先送低8位到计数器,42h是8253芯片通道2的端口地址
    mov al, ah
    out 42h, al       ;后送高8位计数器

    ; 设置8255芯片 控制扬声器的开/关
    in al, 61h      ;读取8255 B端口原值
    mov ah, al      ;保存原值
    or al, 3        ;使低两位置1,以便打开开关
    out 61h, al     ;开扬声器, 发声

    mov dx, [di]    ;保持[di]时长
delay:
    mov cx, 7000h
wait_:
    nop
    loop wait_
    dec dx
    jnz delay

    mov al, ah      ;恢复扬声器端口原值
    out 61h, al

    pop cx
    pop dx
    pop ax
    ret
sound endp
end

2.0 无中断

title main(exe)
extrn play_music:far
.8086
.model small
.stack 1000H
.data
	dw 0, 0	;存原中断入口地址
	menu db 22 dup(0), 0c9h, 34 dup(0cdh), 0bbh, 10, 13
    	 db 22 dup(0), 0bah, '             Menu                 ', 0bah, 10, 13
		 db 22 dup(0), 0bah, '   1.Play like a piano.           ', 0bah, 10, 13
		 db 22 dup(0), 0bah, '   2.Select a song to play.       ', 0bah, 10, 13
		 db 22 dup(0), 0bah, '   3.Displays the current time.   ', 0bah, 10, 13
		 db 22 dup(0), 0bah, '   4.Press F1 to change the color.', 0bah, 10, 13
		 db 22 dup(0), 0bah, '   0.Exit the program.            ', 0bah, 10, 13
		 db 22 dup(0), 0c8h, 34 dup(0cdh), 0bch, 10, 13
	tips db 10, 13, 22 dup(0), 'Please enter a number from 0 to 4. ', 14, 10, 13, '$'

	nextline db 10, 13, 22 dup(0), '$'

	error db '  Error! Please re-enter.', '$'

	songs_name db 22 dup(0), '         Song track              ', 10, 13
			   db 22 dup(0), '1.Two tigers.(liang zhi lao hu)  ', 10, 13
			   db 22 dup(0), '2.Whitewasher.(fen shua jiang)   ', 10, 13
			   db 22 dup(0), '3.A Good New Year.(xin nian hao) ', 10, 13
			   db 10, 13
			   db 22 dup(0), '      Press ESC to exit.         ', 10, 13
			   db 10, 13, '$'

	play_finished db '  Play finished.', '$'

	tip5 db 'Press F1 to change the color.', '$'

.code

main proc

;===========================宏定义===========================
; # 设置光标位置
point macro row, column
	push ax
	push bx
	push cx
	push dx
	mov  ah, 02h	;INT10h的2号中断-设置光标位置
	mov  bh, 0		;页码-第0页
	mov  dh, row	;行Y -第row行
	mov  dl, column	;列X -第column列
	int  10h
	pop  dx
	pop  cx
	pop  bx
	pop  ax
endm

; # 显示到屏幕 '$'结尾
print macro label
	lea dx, label	;取偏移地址
	mov ah, 09h		;21h的9号中断-显示字符串
	int 21h
endm

; # 输出一个字符
putchar macro char
	mov ah, 02h
	mov dl, char
	int 21h
endm

; # 输入一个字符
input_21h macro
	mov ah, 0ch		;21h中断的c号功能 AL保存读入字符的ASCII码
	mov al, 01h
	int 21h
endm

; # 从键盘读入字符,AH保存扫描码 AL保存ASCII码
input_16h macro
	mov ah, 10h
	int 16h
endm

; # 进入下一行
newline macro
	print nextline
endm

; # 选择功能显示
enter_cmd macro
	newline
	putchar '$'
	putchar ' '
endm
;==========================宏定义完==========================

start:
	mov ax, @data
	mov ds, ax
	mov es, ax

; ; 改中断例程入口地址
; 	mov ax, 0
; 	mov es, ax
; 	push es:[9*4]
; 	pop  ds:[0]
; 	push es:[9*4+2]
; 	pop  ds:[2]
; 	mov word ptr es:[9*4], offset int9
; 	mov es:[9*4+2], cs


	call cls
	point 7, 0
	print menu

lop:
	enter_cmd
	input_21h
	sub al, 48
	cmp al, 0
	je exit
	call call_fun
	jmp lop

exit:
	; 恢复原来的地址
	push ds:[0]
	pop es:[9*4]
	push ds:[2]
	pop es:[9*4+2]

	mov ax, 4c00h
	int 21h
main endp

; ; 定义中断例程
; int9:
; 	push ax
; 	push bx
; 	push es

; 	in al, 60h
; 	pushf
; 	call dword ptr ds:[0]

; 	cmp al, 03bh	;F1扫描码
; 	jne int9ret
; 	;改变颜色
; 	mov ax, 0b800h
; 	mov es, ax
; 	mov bx, 1		;指向显存属性单元
; 	mov cx, 80*25	;改变整个页面的颜色
;   s:
; 	inc byte ptr es:[bx]	;属性值+1
; 	add bx, 2
; 	loop s

; int9ret:
; 	pop es
; 	pop bx
; 	pop ax
; 	iret



; @功能选择 al:选择的功能号
call_fun proc
  f1:
	cmp al, 1
	jne f2
	; call piano
	jmp return
  f2:
	cmp al, 2
	jne f3
	call choose_song
	jmp return
  f3:
	cmp al, 3
	jne f4
	; call time
	jmp return
  f4:
	cmp al, 4
	jne f_else
	; call change_color
	jmp return
f_else:
	print error
return:
	ret
call_fun endp


; @选择播放的歌曲
choose_song proc
	call cls
	point 7,0
	print songs_name
 cc:
	enter_cmd
 ff:
 	input_16h

	cmp ah, 3bh	;F1扫描码
	je ff
	cmp ah, 01h	;ESC扫描码
	je over

	cmp ah, 02h
	je con
	cmp ah, 03h
	je con
	cmp ah, 04h
	je con

	; print error
	jmp ff

con:
	sub al, 48
	mov bl, al
	mov bh, 0
	call far ptr play_music
	print play_finished
	jmp cc

over:
	call cls
	point 7,0
	print menu

	ret
choose_song endp


; @清屏
cls proc
	push ax
	mov ah, 0fh	;BIOS-读取显示器模式
	int 10h		;出口参数之一AL为显示模式
	mov ah, 0h	;BIOS-设置显示器模式
	int 10h		;入口参数AL为显示模式(已保存)
	pop ax
	ret
cls endp

end start
title sing(exe)
public play_music
.8086
.model small
.stack 10H
.data
; 两只老虎
mus_freq1 dw 2 dup(262,294,330,262)
          dw 2 dup(330,349,392)
          dw 2 dup(392,440,392,349,330,262)
          dw 2 dup(294,196,262)
          dw -1 ;结束
mus_time1 dw 2 dup(20,20,20,20)
          dw 2 dup(20,20,40)
          dw 2 dup(10,10,10,10,20,20)
          dw 2 dup(20,20,40)

; 粉刷匠
mus_freq2 dw 392,330,392,330,392,330,262
          dw 294,349,330,294,392
          dw 392,330,392,330,392,330,262
          dw 294,349,330,294,262
          dw 294,294,349,349,330,262,392
          dw 294,349,330,294,392
          dw 392,330,392,330,392,330,262
          dw 294,349,330,294,262
          dw -1 ;结束
mus_time2 dw 3 dup(10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,40h)
		  dw 10h,10h,10h,10h,10h,10h,20h,10h,10h,10h,10h,20h

; 新年好
mus_freq3 dw 262,262,262,196
          dw 330,330,330,262
          dw 262,330,392,392
          dw 349,330,294
          dw 294,330,349,349
          dw 330,294,330,262
          dw 262,330,294,196
          dw 247,294,262
          dw -1 ;结束
mus_time3 dw 3 dup(12,12,25,25),12,12,50
          dw 3 dup(12,12,25,25),12,12,50

; 直接定址表
table dw m1, m2, m3

.code

; 宏定义
setsidi macro S,D
    lea si, S
    lea di, D
endm

; @播放音乐  入口参数bx:选第几首歌
play_music proc
    mov ax, @data
    mov ds, ax

    add bx, bx
    jmp word ptr table[bx-2]

m1: setsidi mus_freq1, mus_time1
    jmp play
m2: setsidi mus_freq2, mus_time2
    jmp play
m3: setsidi mus_freq3, mus_time3

play:
    mov dx, [si]
    cmp dx, -1
    je end_play
    call sound
    add si, 2
    add di, 2
    jmp play

end_play:
    retf
play_music endp


; @演奏一个音符  si频率 di音长
sound proc
    push ax
    push dx
    push cx

    ; 8253 芯片(定时/计数器)的设置
    mov al, 0b6h    ;8253初始化
    out 43h, al     ;43H是8253芯片控制口的端口地址
    mov dx, 12h
    mov ax, 34dch
    div word ptr [si] ;计算分频值,赋给ax
    out 42h, al       ;先送低8位到计数器,42h是8253芯片通道2的端口地址
    mov al, ah
    out 42h, al       ;后送高8位计数器

    ; 设置8255芯片 控制扬声器的开/关
    in al, 61h      ;读取8255 B端口原值
    mov ah, al      ;保存原值
    or al, 3        ;使低两位置1,以便打开开关
    out 61h, al     ;开扬声器, 发声

    mov dx, [di]    ;保持[di]时长
delay:
    mov cx, 7000h
wait_:
    nop
    loop wait_
    dec dx
    jnz delay

    mov al, ah      ;恢复扬声器端口原值
    out 61h, al

    pop cx
    pop dx
    pop ax
    ret
sound endp
end

你可能感兴趣的:(汇编语言)