home *** CD-ROM | disk | FTP | other *** search
/ Liren Large Software Subsidy 7 / 07.iso / c / c329 / 2.img / EXAMPLES / PLAY.S < prev    next >
Encoding:
Text File  |  1989-08-23  |  24.2 KB  |  645 lines

  1.         NAME    PLAY.S
  2. ; Copyright MicroWay, Inc., 1989
  3.         .386            ; the Phar Lap default
  4.         .387            ; enable 80387 instructions
  5. ; Purpose: to demonstrate interfacing between protected
  6. ; and real mode code. Both types are linked into the same
  7. ; EXP file - in fact, both types are here in the same
  8. ; source file. This file is assembled for 80386 protected
  9. ; mode. Real mode segments in this file are defined as such
  10. ; by being declared 'use16'. The -REALBREAK switch is used
  11. ; to signal the linker how much of the program must go into
  12. ; conventional MS-DOS memory. The segment order is defined
  13. ; by the dummy segment declarations below.
  14.  
  15. ; The linker will order segments in the order in which
  16. ; it encounters them. This ordering is usually controlled
  17. ; by linking in DOS386.OBJ before any other modules. In
  18. ; this case, however, we must declare two real mode segments
  19. ; and place them ahead of all others, except WTL1167seg, so
  20. ; that they will end up in conventional memory at runtime.
  21. ; Under the Phar Lap system, all segments are bundled into
  22. ; one huge segment when the program is actually running.
  23. ; The most convenient way to represent this to the assembler
  24. ; is to place the real mode data segment and the protected
  25. ; mode data segment into DGROUP, and to use this name in
  26. ; the ASSUME directives. If this, or other modules containing
  27. ; the group name DGROUP are assembled with the -twocase
  28. ; assembler switch, be sure to have the name DGROUP in
  29. ; uppercase characters, or the linker will complain.
  30.  
  31. ; Remember that this module must be placed ahead of all
  32. ; others in the link order, followed by DOS386.OBJ.
  33.  
  34. WTL1167seg segment
  35. WTL1167seg ends
  36.  
  37. rmcode segment byte public use16
  38. rmcode ends
  39.  
  40. rmdata segment dword public use16
  41. rmdata ends
  42.  
  43. dataseg segment dword rw use32 public 'data'
  44. dataseg ends
  45.  
  46. codeseg segment dword er use32 public 'code'
  47. codeseg ends
  48.  
  49. ; Note: file must be assembled and linked with '-twocase',
  50. ; DGROUP must be entirely uppercase, all segments names
  51. ; normally used by NDP compilers must be entirely lower case
  52. ; Segments 'codeseg' and 'dataseg' are the segments used by
  53. ; the NDP family of compilers for code and data, respectively
  54. ifdef FORTRAN
  55. DGROUP group rmdata, dataseg,__BLNK__@
  56. else
  57. DGROUP group rmdata, dataseg
  58. endif
  59.  
  60. TRUE equ 1
  61. FALSE equ 0
  62. ERROR_VAL equ -1
  63.  
  64. ; The following values are used by 'do_sound', a flag used
  65. ; to communicate between the protected mode routines and
  66. ; the real mode timer tick Interrupt Service Routine (ISR)
  67. NOT_PLAYING equ 0
  68. START_SOUND equ 1
  69. COUNTING_DOWN equ 2
  70.  
  71. ; The following port turns the speaker on and off
  72. SPEAKER_PORT equ 61h
  73.  
  74. ; The following values are used to send an End of Interrupt
  75. ; signal to the 8259A Programmable Interrupt Controller
  76. EOI_PORT equ 20h
  77. EOI equ 20h
  78.  
  79. ; the following values are used in communicating with the
  80. ; 8253 Timer Chip, or the 8254 Timer Chip, or equivalent 
  81. TIMER0_PORT equ 40h     ; Timer Channel 0 register count
  82. TIMER2_PORT equ 42h     ; Timer Channel 2 register count
  83. TIMER_CTL_PORT equ 43h  ; Control word for timer chip
  84.  
  85. COUNT_BINARY equ 0h     ; Count is binary rather than BCD
  86. MODE3 equ 6h            ; Set mode to Square Wave Rate Generator
  87. LSB_MSB equ 30h         ; Read/Load Least Significant Byte then
  88.                         ; Most Significant Byte
  89. TIMER0 equ 0h           ; Timer Channel 0 selector
  90. TIMER2 equ 80h          ; Timer Channel 2 selector
  91. DONT_CARE equ 0
  92. LATCH equ 0             ; Latch Counter for stable read
  93.  
  94. ; The timer chip (or equivalent) has 3 (sometimes 4) channels.
  95. ; Each of these channels has a 16 bit internal register which
  96. ; is initialized with a count value. The count value is
  97. ; decremented - when it reaches 0, the chip emits a pulse and
  98. ; reinitializes the count.
  99. ;    Channel 0 sends an IRQ0 which invokes the Timer Tick Interrupt
  100. ;    Channel 1 controls DMA refresh
  101. ;    Channel 2 sends a pulse to the speaker
  102. ; Any of the initializing values can be modified under
  103. ; program control by writing to ports. We shall use the
  104. ; timer chip to play musical notes. We shall modify
  105. ; Channel 0 to control the duration of the note's sound,
  106. ; and Channel 2 to control its pitch.
  107. ; WARNING: Channel 1 must be left strictly alone.
  108. ;
  109. ; The Channel 0 counter is normally initialized to start
  110. ; counting from 65535, which invokes the Timer Tick
  111. ; Interrupt 18.2 times/sec. We shall change this counter
  112. ; so that the Timer Tick Interrupt Service Routine is
  113. ; invoked four times as often, and compensate by calling
  114. ; the old Timer Tick ISR every fourth time.
  115. TIMER_FACTOR equ 4      ; divisor of Timer Channel 0 counter
  116.  
  117. ; The following structure acts as a template to overlay
  118. ; the Phar Lap Intermode Call Data Buffer, which is used
  119. ; to communicate data between real and protected mode code.
  120. ; When running this program, it is necessary to invoke
  121. ; RUN386.EXE with the -CALLBUFS parameter, with a value of
  122. ; at least 1, or no buffer will be allocated.
  123. intermode_buffer struc
  124.         duration dd 0   ; number of timer ticks to sound a note
  125.         do_sound db 0   ; generate / don't generate sound flag
  126.         call_orig db 0  ; call / don't call old timer ISR counter
  127. intermode_buffer ends
  128.  
  129. tone struc
  130.         tonename db '   '       ; name of tone
  131.                  db 0           ; null byte at end of name
  132.         freq     dd 0.0         ; frequency 
  133.         count    dd 0           ; interval between pulses
  134. tone ends
  135.  
  136. ; The following segment is the standard protected mode
  137. ; data segment used by the NDP family of compilers
  138. dataseg segment dword rw use32 public 'data'
  139.  
  140. ; The following code generates an array of structures
  141. ; Each structure represents a tone in the tempered chromatic scale
  142. ; Each tone has three fields: name, frequency, interval count
  143. ; The names take one of two forms:
  144. ;       'A-G' ['#'|'b'] '0-8' 0
  145. ;  or   'A-G' '0-8' ' ' 0
  146. ; The first letter of every name must be in upper case - lower
  147. ; case 'b' is reserved to indicate the tone is a flat. Every
  148. ; name occupies 4 bytes and has exactly 3 characters - if the
  149. ; tone is not a sharp or flat, the name is padded with a blank
  150. ; in the third position. The fourth and final byte of the name
  151. ; is a null byte (value 0, not character 0). The frequency and
  152. ; count fields will be calculated at runtime. Since this is a
  153. ; tempered scale, the frequency and count fields of adjacent
  154. ; sharps and flats are the same, e.g., G#0 and Ab0 have the
  155. ; same pitch.
  156.  
  157. .LALL   ; make sure we see all macro expansions in .LST file
  158.  
  159. ; If the array of tempered chromatic scales defined below
  160. ; is to be accessed from a Fortran program, we must place it
  161. ; in COMMON. This is unnecessary with NDP C and NDP Pascal,
  162. ; with which we use the external declaration. The following
  163. ; segment is used by NDP Fortran for blank COMMON.
  164. ifdef FORTRAN
  165. __BLNK__@ segment dword rw use32 common 'data'
  166. endif
  167.         align   4
  168. _tcs label byte                 ; Tempered Chromatic Scales
  169. public _tcs
  170. IRPC octv,01234567
  171. o&octv& tone    <'C&octv& ',,,>
  172.         tone    <'C#&octv&',,,>
  173.         tone    <'Db&octv&',,,>
  174.         tone    <'D&octv& ',,,>
  175.         tone    <'D#&octv&',,,>
  176.         tone    <'Eb&octv&',,,>
  177.         tone    <'E&octv& ',,,>
  178.         tone    <'F&octv& ',,,>
  179.         tone    <'F#&octv&',,,>
  180.         tone    <'Gb&octv&',,,>
  181.         tone    <'G&octv& ',,,>
  182.         tone    <'G#&octv&',,,>
  183.         tone    <'Ab&octv&',,,>
  184.         tone    <'A&octv& ',,,>
  185.         tone    <'A#&octv&',,,>
  186.         tone    <'Bb&octv&',,,>
  187.         tone    <'B&octv& ',,,>
  188. ENDM
  189. o8      tone    <'C8 ',,,>      ; highest tone we use
  190.         end_tcs dd 0 ; marks end of Tempered Chromatic Scale array
  191. ifdef FORTRAN
  192. __BLNK__@ ends
  193. endif
  194.         align 4
  195. _octp label byte                        ; Octave Pointers
  196. IRPC octv,01234567
  197. _octp&octv&     dd offset o&octv&
  198. ENDM
  199.         align 4
  200.         twelve  dd 12.0
  201.         start_frequency dd 16.35
  202.         iof dq 1193180.0 ; Input Oscillator Frequency of Timer Chip
  203.         timer_vector db ?     ; 
  204.         align 4
  205.         pdb_offset dd ?
  206.         pdb_seg dw ?
  207.         old_timer2_count dw ?
  208.         octave_numbers db '012345678'
  209.         end_octave_numbers label byte
  210. dataseg ends
  211.  
  212.  
  213.         assume  cs:codeseg
  214.         assume  ds:DGROUP
  215.  
  216. ; The following segment is the standard protected mode
  217. ; code segment used by the NDP family of compilers
  218. codeseg segment dword er use32 public 'code'
  219.  
  220.         align   4
  221. ; ************************************************************ */
  222. ; _init_tcs_
  223. ;       Purpose: initialize array of structures, each array
  224. ;       element being a tone in the tempered chromatic scale.
  225. ;       Parameters: none
  226. ;       Return value: none
  227. ;       In the tempered chromatic scale, the frequency of
  228. ;       each tone equals the frequency of the preceding tone
  229. ;       times the twelfth root of two.  To produce a given
  230. ;       tone on a PC, it is necessary to send pulses at a
  231. ;       certain interval count to the speaker. This count
  232. ;       equals the frequency of the timer chip's clock
  233. ;       divided by the frequency of the given tone. For
  234. ;       example, to produce middle C, the count is the integer
  235. ;       quotient of 1.19318 MHz / 261.63, or 4561. Rather than
  236. ;       calculating the frequencies and counts and entering
  237. ;       them manually ourselves in the source code, we shall
  238. ;       let the computer do the work for us at runtime.
  239. ; ************************************************************ */
  240.  
  241. _init_tcs_ proc near            ; label for Fortran
  242. _init_tcs:                      ; label for C and Pascal
  243. public _init_tcs_
  244. public _init_tcs
  245.  
  246. ; First, load frequency for timer chip - we'll use it later
  247.         fld     iof  ; Input Oscillator Frequency for Timer Chip
  248. ; Second, compute twelfth root of 2. Result left in ST0.
  249.         fld1
  250.         fld     twelve
  251.         fdiv
  252.         f2xm1
  253.         fld1
  254.         fadd
  255.  ; Third, compute tone frequencies and interval between pulses
  256.         lea     ebx, _tcs         ; offset start of array -> ebx
  257.         lea     edx, end_tcs      ; offset end of array -> edx
  258.         fld     start_frequency   ; lowest frequency -> ST0
  259.         fst     [ebx.freq]        ; store it in 1st frequency field
  260.         fld     st(0)             ; reduplicate tone's frequency
  261.         fdivr   st(0),st(3)       ; timer freq / tone's freq
  262.         fistp   [ebx.count]       ; store count in 1st count field
  263.         add     ebx,SIZE tone     ; advance to next element
  264. compute_next_frequency:
  265.         fmul    st(0),st(1)       ; current freq*12th root of 2
  266.         fst     [ebx.freq]        ; store it in frequency field
  267.         fld     st(0)             ; reduplicate tone's frequency
  268.         fdivr   st(0),st(3)       ; timer freq / tone's freq
  269.         fistp   [ebx.count]       ; store count in count field
  270.         add     ebx,SIZE tone     ; advance to next element
  271.         mov     al,[ebx+1]        ; if this note is a flat
  272.         cmp     al,'b'            ; previous note must be a sharp
  273.         jnz     not_same_pitch    ; and of the same frequency
  274.         mov     eax,[ebx.freq-SIZE tone] ;previous frequency -> eax
  275.         mov     [ebx.freq],eax    ; eax -> current frequency
  276.         mov     eax,[ebx.count-SIZE tone] ; previous count -> eax
  277.         mov     [ebx.count],eax   ; eax -> current count
  278.         add     ebx,SIZE tone     ; advance to next element
  279. not_same_pitch: 
  280.         cmp     ebx,edx           ; end of array ?
  281.         jb      compute_next_frequency  ; if not, circle back
  282.         ret                       ; otherwise, return
  283. _init_tcs_ endp
  284.  
  285. ; Phar Lap takes over MS-DOS Interrupt 21h, function 25h,
  286. ; and uses it to extend system calls. In the remainder
  287. ; of this file, we make extensive use of these Phar Lap
  288. ; services. For more information on these. please refer
  289. ; to the approriate section of your Phar Lap manual.
  290.  
  291.         align   4
  292. ; ************************************************************ */
  293. ; _init_timer_isr_
  294. ;       Purpose: initialize our Timer Tick ISR
  295. ;       Parameters: none
  296. ;       Return value in eax: 0 if ok, -1 if problem
  297. ; ************************************************************ */
  298. _init_timer_isr_ proc   near    ; label for Fortran
  299. _init_timer_isr:                ; label for C and Pascal
  300. public _init_timer_isr_
  301. public _init_timer_isr
  302.  
  303.         mov     ax,250ch        ; get hardware interrupt vectors
  304.         int     21h             ; returns IRQ0 (timer) in al
  305.         mov     cl,al           ; timer vector -> cl
  306.         mov     timer_vector,al ; save value for later
  307.         mov     ax,2503h        ; get real mode interrupt vector
  308.         int     21h             ; for timer, return value in ebx
  309. ; Since the linker combines all segments into one big segment,
  310. ; all offsets are from the beginning of this one big segment.
  311. ; The following ASSUME is to pacify the assembler.
  312. assume ds:rmcode
  313.         mov     old_timer_isr,ebx
  314. ; get protected and real mode addresses of intermode call buffer
  315. assume ds:DGROUP
  316.         mov     ax,250dh   ; returns real mode address in ebx
  317.         int     21h        ; and prot mode address in es:edx
  318.         jc      #err
  319. ; The size of the Intermode Call Data Buffer is returned in ecx
  320. ; This buffer is allocated by passing RUN386.EXE the -CALLBUFS
  321. ; parameter of at least 1. If ecx = 0, then this was not done.
  322.         jecxz   #err
  323. ; The following ASSUME is to pacify the assembler.
  324. assume ds:rmcode
  325.         mov     rdb,ebx         ; real intermode data buffer
  326. assume ds:DGROUP,es:nothing
  327.         mov     pdb_offset,edx ; prot intermode data buffer offset
  328.         mov     ax,es
  329.         mov     pdb_seg,ax      ; prot intermode data buffer seg
  330. ; size of our template to overlay intermode data buffer
  331.         mov     ecx,SIZE intermode_buffer
  332. ; zero out our section of the intermode data buffer. Note that
  333. ; the flag 'do_sound' is in this buffer and that it is essential
  334. ; to initialize it to the value NOT_PLAYING before installing
  335. ; our Timer Tick ISR. Since NOT_PLAYING is equal to zero, the
  336. ; following code will do this as a side effect . We have, however,
  337. ; chosen to separately initialize 'do_sound' to avoid potential
  338. ; problems if the value NOT_PLAYING is ever changed.
  339. until_buffer_clear:
  340.         mov     byte ptr es:[edx+ecx-1],0
  341.         loop    until_buffer_clear
  342.         mov     es:[edx.do_sound],NOT_PLAYING
  343.         mov     ax,ds           ; restore segment selector to es
  344. assume es:DGROUP
  345.         mov     es,ax
  346. ; get real mode address of _timer_isr
  347. ; The following ASSUME is to pacify the assembler.
  348. assume cs:rmcode
  349.         lea     ebx,_timer_isr
  350. assume cs:codeseg
  351.         xor     ecx,ecx         ; zero out ecx
  352.         mov     ax,250fh        ; returns real mode address
  353.         int     21h             ; in ecx
  354.         jc      #err
  355. ; Set real mode timer interrupt vector to our timer ISR
  356.         mov     ebx,ecx
  357.         mov     cl,timer_vector
  358.         mov     ax,2505h
  359.         int     21h
  360.         jc      #err
  361.         xor     eax,eax         ; zero out eax
  362. #exit:
  363.         ret
  364. #err:
  365.         mov     eax,ERROR_VAL
  366.         jmp     #exit
  367.  
  368.         align   4
  369. _init_timer_isr_ endp
  370.  
  371.         align 4
  372. ; ************************************************************ */
  373. ; _restore_timer_isr_
  374. ;       Purpose: reset real mode timer interrupt vector
  375. ;          to original timer interrupt service routine
  376. ;       Parameters: none
  377. ;       Return value in eax: 0 if ok, -1 if problem
  378. ; ************************************************************ */
  379.  
  380. _restore_timer_isr_ proc near   ; label for Fortran
  381. _restore_timer_isr:             ; label for C and Pascal
  382. public _restore_timer_isr_
  383. public _restore_timer_isr
  384.  
  385. ; The following ASSUME is to pacify the assembler.
  386. assume ds:rmcode
  387.         mov     ebx,old_timer_isr
  388. assume ds:DGROUP
  389.         mov     ax,2505h
  390.         int     21h
  391.         jc      #err
  392.         xor     eax,eax
  393. #exit:
  394.         ret
  395. #err:
  396.         mov     eax,ERROR_VAL
  397.         jmp     #exit
  398. _restore_timer_isr_ endp
  399.  
  400.  
  401. note struc
  402.         notelength dd 0    ; length of time to sound the note
  403.         notename   db '   '; name of the note
  404.                    db 0    ; null byte at end of name
  405. note ends
  406.  
  407.         align 4
  408. ; ************************************************************ */
  409. ; _play_
  410. ;       Purpose: plays a tune
  411. ;       Parameter: pointer to (possibly empty) array of
  412. ;          structures of type note
  413. ;       Return value in eax: 0 if ok, -1 if problem
  414. ; ************************************************************ */
  415. _play_  proc    near    ; label for Fortran
  416. _play:                  ; label for C and Pascal
  417. public _play_
  418. public _play
  419.  
  420.         mov     ebx,[esp+4]     ; address of notes -> ebx
  421. ; get original value of Timer Chip Channel 2 count register
  422.         mov     al,DONT_CARE+LATCH+TIMER2
  423.         out     TIMER_CTL_PORT,al
  424.         in      al,TIMER0_PORT       ; get least significant byte
  425.         mov     ah,al                ; and save it in ah
  426.         in      al,TIMER0_PORT       ; get most significant byte
  427.         xchg    ah,al                ; put bytes in correct order
  428.         mov     old_timer2_count,ax  ; and save the original count
  429.  
  430. ; protected mode address of Intermode Call Data Buffer -> es:esi
  431.         mov     esi,pdb_offset
  432.         mov     ax,pdb_seg
  433. assume es:nothing
  434.         mov     es,ax
  435. ; The variable 'call_orig', in the Intermode Call Data Buffer,
  436. ; acts as a counter. When it goes to 0, control is passed to
  437. ; the original Timer Tick ISR. Be sure to initialize this
  438. ; counter before changing Timer Channel 0 count register
  439.         mov     es:[esi.call_orig],TIMER_FACTOR
  440.         mov     al,COUNT_BINARY+MODE3+LSB_MSB+TIMER0
  441.         out     TIMER_CTL_PORT,al
  442.         mov     ax,65536/TIMER_FACTOR
  443.         out     TIMER0_PORT,al      ; send least significant byte
  444.         mov     al,ah
  445.         out     TIMER0_PORT,al      ; send most significant byte
  446. more_notes:
  447.         mov     ecx,[ebx.notelength]    ; get next note length
  448.         jecxz   no_more_notes           ; if zero, we are done
  449.         mov     es:[esi.duration],ecx   ; set the note's duration
  450.         cmp     [ebx.notename],'R'      ; R - the Rest is silence
  451.         jnz     not_a_pause
  452.         mov     es:[esi.do_sound],COUNTING_DOWN
  453.         jmp     short until_sound_done
  454. not_a_pause:
  455.         call    _get_count      ; get count for Timer Channel 2
  456.         jc      #err
  457.         mov     ecx,65000       ; check for out of range count
  458.         cmp     eax,ecx
  459.         ja      #err            ; frequency too low
  460.         mov     ecx,eax
  461. ; Change Timer Channel 0 count register to new count
  462.         mov     al,COUNT_BINARY+MODE3+LSB_MSB+TIMER2
  463.         out     TIMER_CTL_PORT,al
  464.         mov     ax,cx
  465.         out     TIMER2_PORT,al
  466.         mov     al,ah
  467.         out     TIMER2_PORT,al
  468. ; start the note playing
  469.         mov     es:[esi.do_sound],START_SOUND
  470. until_sound_done:
  471.         cmp     es:[esi.duration],0
  472.         jne     until_sound_done
  473. ; signal the Timer Tick ISR that note is done
  474.         mov     es:[esi.do_sound],NOT_PLAYING
  475.         add     ebx, SIZE note  ; advance to the next note
  476.         jmp     more_notes
  477.         clc             ; Clear carry flag denotes All OK
  478. no_more_notes:
  479. #exit:
  480. ; restore normal Timer Channel 0 count
  481.         mov     al,COUNT_BINARY+MODE3+LSB_MSB+TIMER0
  482.         out     TIMER_CTL_PORT,al
  483.         mov     al,0h
  484.         out     TIMER0_PORT,al
  485.         out     TIMER0_PORT,al
  486. ; restore normal Timer Channel 2 count
  487.         mov     al,COUNT_BINARY+MODE3+LSB_MSB+TIMER2
  488.         out     TIMER_CTL_PORT,al
  489.         mov     ax,old_timer2_count
  490.         out     TIMER2_PORT,al
  491.         mov     al,ah
  492.         out     TIMER2_PORT,al
  493.         mov     ax,ds           ; restore DGROUP to es
  494.         mov     es,ax
  495.  assume es:DGROUP
  496.         sbb     eax,eax         ; eax = 0 if okay, -1 if error
  497.         ret
  498. #err:
  499.         stc
  500.         jmp     #exit
  501.  
  502.         align   4
  503. _play_  endp
  504.  
  505. ; ************************************************************ */
  506. ; _get_count
  507. ;       Purpose: returns count field of tone
  508. ;       Parameter: pointer to structure of type note
  509. ;       Return value in eax. If error, carry flag set.
  510. ; ************************************************************ */
  511. _get_count      proc    near
  512.         push    ecx
  513.         push    edi
  514.         push    es    ; es holds segment of protected data buffer
  515.         mov     ax,ds
  516. assume es:DGROUP
  517.         mov     es,ax
  518. ; The octave number may be in the second position
  519.         movzx   eax,byte ptr [ebx.notename+1]
  520.         lea     edi,octave_numbers
  521.         mov     ecx,end_octave_numbers-octave_numbers
  522.         cld
  523.         repne   scas byte ptr es:[edi]
  524.         jz      found_octave_number
  525. ; Or the octave number may be in the third position
  526.         movzx   eax,byte ptr [ebx.notename+2]
  527.         lea     edi,octave_numbers
  528.         mov     ecx,end_octave_numbers-octave_numbers
  529.         repne   scas byte ptr es:[edi]
  530. ; If not in second or third, its an error
  531.         jnz     #err
  532. found_octave_number:
  533. ; Test for special case that note is C8
  534.         cmp     [ebx.notename+1],'8'
  535.         jl      not_octave8
  536.         cmp     [ebx.notename],'C'
  537.         jne     #err
  538.         lea     edi,o8
  539.         mov     eax,[edi.count]
  540.         clc         ; carry flag clear signals that all is ok
  541.         jmp     short #exit
  542. not_octave8:
  543.         mov     ecx,(offset o1 - offset o0) / SIZE tone
  544.         sub     eax,'0'         ; calculate subscript into array of 
  545.         shl     eax,2           ; pointers to octaves
  546.         lea     edi, _octp
  547.         mov     edi,[edi+eax]   ; pointer to current octave -> edi
  548.         mov     ax,word ptr [ebx.notename]
  549. find_name:
  550.         scasw
  551.         jz      found_name
  552.         add     edi,SIZE tone-2
  553.         loop    find_name
  554.         jnz     #err
  555. found_name:
  556.         sub     edi,2
  557.         mov     eax,[edi.count]
  558.         clc         ; carry flag clear signals that all is ok
  559. #exit:
  560. assume es:nothing
  561.         pop     es  ; restore segment of protected data buffer
  562.         pop     edi
  563.         pop     ecx
  564.         ret
  565. #err:
  566. assume es:DGROUP
  567.         stc             ; carry flag set signals a problem
  568.         jmp     #exit
  569.  
  570. _get_count endp
  571.  
  572. codeseg ends
  573.  
  574. ; real mode code segment
  575. rmcode segment byte public use16
  576.         assume cs:rmcode,ds:DGROUP
  577. old_timer_isr dd ? ; address of old timer ISR (real mode)
  578. rdb label dword    ; real address of intermode call data buffer
  579. rdb_offset dw ?    ; real offset of intermode call data buffer
  580. rdb_seg    dw ?    ; real segment of intermode call data buffer
  581. old_spkr_port  db ?        ; old value of speaker port
  582. ; ************************************************************ */
  583. ; _timer_isr
  584. ;       Purpose: service timer tick interrupt in real mode
  585. ;          and, every fourth invocation, pass control to
  586. ;          original timer tick ISR (Interrupt Service Routine)
  587. ;       Parameters: none
  588. ;       Return value: none
  589. ; ************************************************************ */
  590. _timer_isr proc near
  591. assume ds:NOTHING
  592.         push    ax
  593.         push    ds
  594.         push    si
  595.         mov     si,rdb_offset
  596.         mov     ax,rdb_seg
  597.         mov     ds,ax
  598.         cmp     ds:[si.do_sound],NOT_PLAYING
  599.         jz      #exit
  600.         cmp     ds:[si.do_sound],START_SOUND
  601.         jnz     dont_start_sound
  602.         in      al,SPEAKER_PORT
  603.         mov     old_spkr_port,al
  604.         or      al,3
  605.         out     SPEAKER_PORT,al
  606.         mov     [si.do_sound],COUNTING_DOWN
  607.         jmp     short dont_stop_sound
  608. dont_start_sound:
  609.         dec     ds:[si.duration]
  610.         jnz     dont_stop_sound
  611.         mov     al,old_spkr_port
  612.         out     SPEAKER_PORT,al
  613. dont_stop_sound:
  614.         dec     ds:[si.call_orig]
  615.         jnz     dont_reinit
  616.         mov     ds:[si.call_orig],TIMER_FACTOR
  617. dont_reinit:
  618. #exit:
  619. assume ds:DGROUP
  620.         pop     si
  621.         pop     ds
  622.         pop     ax
  623.         jz      do_call_orig
  624. ; Normally, the EOI (End of Interrupt) Signal is sent by the
  625. ; Timer Tick ISR in the ROM BIOS, but since we are bypassing
  626. ; it 3 times out of 4, we must send the EOI ourselves.
  627.         mov     al,EOI
  628.         out     EOI_PORT,al
  629.         iret
  630. do_call_orig:
  631.         jmp old_timer_isr
  632.  
  633. _timer_isr endp
  634.  
  635. rmcode ends
  636.  
  637. ; real mode data segment
  638. rmdata segment dword public use16
  639.  
  640. end_real label byte
  641.         public end_real   ; marks end of real mode code and data
  642. rmdata ends
  643.  
  644.         end
  645.