home *** CD-ROM | disk | FTP | other *** search
- NAME PLAY.S
- ; Copyright MicroWay, Inc., 1989
- .386 ; the Phar Lap default
- .387 ; enable 80387 instructions
- ; Purpose: to demonstrate interfacing between protected
- ; and real mode code. Both types are linked into the same
- ; EXP file - in fact, both types are here in the same
- ; source file. This file is assembled for 80386 protected
- ; mode. Real mode segments in this file are defined as such
- ; by being declared 'use16'. The -REALBREAK switch is used
- ; to signal the linker how much of the program must go into
- ; conventional MS-DOS memory. The segment order is defined
- ; by the dummy segment declarations below.
-
- ; The linker will order segments in the order in which
- ; it encounters them. This ordering is usually controlled
- ; by linking in DOS386.OBJ before any other modules. In
- ; this case, however, we must declare two real mode segments
- ; and place them ahead of all others, except WTL1167seg, so
- ; that they will end up in conventional memory at runtime.
- ; Under the Phar Lap system, all segments are bundled into
- ; one huge segment when the program is actually running.
- ; The most convenient way to represent this to the assembler
- ; is to place the real mode data segment and the protected
- ; mode data segment into DGROUP, and to use this name in
- ; the ASSUME directives. If this, or other modules containing
- ; the group name DGROUP are assembled with the -twocase
- ; assembler switch, be sure to have the name DGROUP in
- ; uppercase characters, or the linker will complain.
-
- ; Remember that this module must be placed ahead of all
- ; others in the link order, followed by DOS386.OBJ.
-
- WTL1167seg segment
- WTL1167seg ends
-
- rmcode segment byte public use16
- rmcode ends
-
- rmdata segment dword public use16
- rmdata ends
-
- dataseg segment dword rw use32 public 'data'
- dataseg ends
-
- codeseg segment dword er use32 public 'code'
- codeseg ends
-
- ; Note: file must be assembled and linked with '-twocase',
- ; DGROUP must be entirely uppercase, all segments names
- ; normally used by NDP compilers must be entirely lower case
- ; Segments 'codeseg' and 'dataseg' are the segments used by
- ; the NDP family of compilers for code and data, respectively
- ifdef FORTRAN
- DGROUP group rmdata, dataseg,__BLNK__@
- else
- DGROUP group rmdata, dataseg
- endif
-
- TRUE equ 1
- FALSE equ 0
- ERROR_VAL equ -1
-
- ; The following values are used by 'do_sound', a flag used
- ; to communicate between the protected mode routines and
- ; the real mode timer tick Interrupt Service Routine (ISR)
- NOT_PLAYING equ 0
- START_SOUND equ 1
- COUNTING_DOWN equ 2
-
- ; The following port turns the speaker on and off
- SPEAKER_PORT equ 61h
-
- ; The following values are used to send an End of Interrupt
- ; signal to the 8259A Programmable Interrupt Controller
- EOI_PORT equ 20h
- EOI equ 20h
-
- ; the following values are used in communicating with the
- ; 8253 Timer Chip, or the 8254 Timer Chip, or equivalent
- TIMER0_PORT equ 40h ; Timer Channel 0 register count
- TIMER2_PORT equ 42h ; Timer Channel 2 register count
- TIMER_CTL_PORT equ 43h ; Control word for timer chip
-
- COUNT_BINARY equ 0h ; Count is binary rather than BCD
- MODE3 equ 6h ; Set mode to Square Wave Rate Generator
- LSB_MSB equ 30h ; Read/Load Least Significant Byte then
- ; Most Significant Byte
- TIMER0 equ 0h ; Timer Channel 0 selector
- TIMER2 equ 80h ; Timer Channel 2 selector
- DONT_CARE equ 0
- LATCH equ 0 ; Latch Counter for stable read
-
- ; The timer chip (or equivalent) has 3 (sometimes 4) channels.
- ; Each of these channels has a 16 bit internal register which
- ; is initialized with a count value. The count value is
- ; decremented - when it reaches 0, the chip emits a pulse and
- ; reinitializes the count.
- ; Channel 0 sends an IRQ0 which invokes the Timer Tick Interrupt
- ; Channel 1 controls DMA refresh
- ; Channel 2 sends a pulse to the speaker
- ; Any of the initializing values can be modified under
- ; program control by writing to ports. We shall use the
- ; timer chip to play musical notes. We shall modify
- ; Channel 0 to control the duration of the note's sound,
- ; and Channel 2 to control its pitch.
- ; WARNING: Channel 1 must be left strictly alone.
- ;
- ; The Channel 0 counter is normally initialized to start
- ; counting from 65535, which invokes the Timer Tick
- ; Interrupt 18.2 times/sec. We shall change this counter
- ; so that the Timer Tick Interrupt Service Routine is
- ; invoked four times as often, and compensate by calling
- ; the old Timer Tick ISR every fourth time.
- TIMER_FACTOR equ 4 ; divisor of Timer Channel 0 counter
-
- ; The following structure acts as a template to overlay
- ; the Phar Lap Intermode Call Data Buffer, which is used
- ; to communicate data between real and protected mode code.
- ; When running this program, it is necessary to invoke
- ; RUN386.EXE with the -CALLBUFS parameter, with a value of
- ; at least 1, or no buffer will be allocated.
- intermode_buffer struc
- duration dd 0 ; number of timer ticks to sound a note
- do_sound db 0 ; generate / don't generate sound flag
- call_orig db 0 ; call / don't call old timer ISR counter
- intermode_buffer ends
-
- tone struc
- tonename db ' ' ; name of tone
- db 0 ; null byte at end of name
- freq dd 0.0 ; frequency
- count dd 0 ; interval between pulses
- tone ends
-
- ; The following segment is the standard protected mode
- ; data segment used by the NDP family of compilers
- dataseg segment dword rw use32 public 'data'
-
- ; The following code generates an array of structures
- ; Each structure represents a tone in the tempered chromatic scale
- ; Each tone has three fields: name, frequency, interval count
- ; The names take one of two forms:
- ; 'A-G' ['#'|'b'] '0-8' 0
- ; or 'A-G' '0-8' ' ' 0
- ; The first letter of every name must be in upper case - lower
- ; case 'b' is reserved to indicate the tone is a flat. Every
- ; name occupies 4 bytes and has exactly 3 characters - if the
- ; tone is not a sharp or flat, the name is padded with a blank
- ; in the third position. The fourth and final byte of the name
- ; is a null byte (value 0, not character 0). The frequency and
- ; count fields will be calculated at runtime. Since this is a
- ; tempered scale, the frequency and count fields of adjacent
- ; sharps and flats are the same, e.g., G#0 and Ab0 have the
- ; same pitch.
-
- .LALL ; make sure we see all macro expansions in .LST file
-
- ; If the array of tempered chromatic scales defined below
- ; is to be accessed from a Fortran program, we must place it
- ; in COMMON. This is unnecessary with NDP C and NDP Pascal,
- ; with which we use the external declaration. The following
- ; segment is used by NDP Fortran for blank COMMON.
- ifdef FORTRAN
- __BLNK__@ segment dword rw use32 common 'data'
- endif
- align 4
- _tcs label byte ; Tempered Chromatic Scales
- public _tcs
- IRPC octv,01234567
- o&octv& tone <'C&octv& ',,,>
- tone <'C#&octv&',,,>
- tone <'Db&octv&',,,>
- tone <'D&octv& ',,,>
- tone <'D#&octv&',,,>
- tone <'Eb&octv&',,,>
- tone <'E&octv& ',,,>
- tone <'F&octv& ',,,>
- tone <'F#&octv&',,,>
- tone <'Gb&octv&',,,>
- tone <'G&octv& ',,,>
- tone <'G#&octv&',,,>
- tone <'Ab&octv&',,,>
- tone <'A&octv& ',,,>
- tone <'A#&octv&',,,>
- tone <'Bb&octv&',,,>
- tone <'B&octv& ',,,>
- ENDM
- o8 tone <'C8 ',,,> ; highest tone we use
- end_tcs dd 0 ; marks end of Tempered Chromatic Scale array
- ifdef FORTRAN
- __BLNK__@ ends
- endif
- align 4
- _octp label byte ; Octave Pointers
- IRPC octv,01234567
- _octp&octv& dd offset o&octv&
- ENDM
- align 4
- twelve dd 12.0
- start_frequency dd 16.35
- iof dq 1193180.0 ; Input Oscillator Frequency of Timer Chip
- timer_vector db ? ;
- align 4
- pdb_offset dd ?
- pdb_seg dw ?
- old_timer2_count dw ?
- octave_numbers db '012345678'
- end_octave_numbers label byte
- dataseg ends
-
-
- assume cs:codeseg
- assume ds:DGROUP
-
- ; The following segment is the standard protected mode
- ; code segment used by the NDP family of compilers
- codeseg segment dword er use32 public 'code'
-
- align 4
- ; ************************************************************ */
- ; _init_tcs_
- ; Purpose: initialize array of structures, each array
- ; element being a tone in the tempered chromatic scale.
- ; Parameters: none
- ; Return value: none
- ; In the tempered chromatic scale, the frequency of
- ; each tone equals the frequency of the preceding tone
- ; times the twelfth root of two. To produce a given
- ; tone on a PC, it is necessary to send pulses at a
- ; certain interval count to the speaker. This count
- ; equals the frequency of the timer chip's clock
- ; divided by the frequency of the given tone. For
- ; example, to produce middle C, the count is the integer
- ; quotient of 1.19318 MHz / 261.63, or 4561. Rather than
- ; calculating the frequencies and counts and entering
- ; them manually ourselves in the source code, we shall
- ; let the computer do the work for us at runtime.
- ; ************************************************************ */
-
- _init_tcs_ proc near ; label for Fortran
- _init_tcs: ; label for C and Pascal
- public _init_tcs_
- public _init_tcs
-
- ; First, load frequency for timer chip - we'll use it later
- fld iof ; Input Oscillator Frequency for Timer Chip
- ; Second, compute twelfth root of 2. Result left in ST0.
- fld1
- fld twelve
- fdiv
- f2xm1
- fld1
- fadd
- ; Third, compute tone frequencies and interval between pulses
- lea ebx, _tcs ; offset start of array -> ebx
- lea edx, end_tcs ; offset end of array -> edx
- fld start_frequency ; lowest frequency -> ST0
- fst [ebx.freq] ; store it in 1st frequency field
- fld st(0) ; reduplicate tone's frequency
- fdivr st(0),st(3) ; timer freq / tone's freq
- fistp [ebx.count] ; store count in 1st count field
- add ebx,SIZE tone ; advance to next element
- compute_next_frequency:
- fmul st(0),st(1) ; current freq*12th root of 2
- fst [ebx.freq] ; store it in frequency field
- fld st(0) ; reduplicate tone's frequency
- fdivr st(0),st(3) ; timer freq / tone's freq
- fistp [ebx.count] ; store count in count field
- add ebx,SIZE tone ; advance to next element
- mov al,[ebx+1] ; if this note is a flat
- cmp al,'b' ; previous note must be a sharp
- jnz not_same_pitch ; and of the same frequency
- mov eax,[ebx.freq-SIZE tone] ;previous frequency -> eax
- mov [ebx.freq],eax ; eax -> current frequency
- mov eax,[ebx.count-SIZE tone] ; previous count -> eax
- mov [ebx.count],eax ; eax -> current count
- add ebx,SIZE tone ; advance to next element
- not_same_pitch:
- cmp ebx,edx ; end of array ?
- jb compute_next_frequency ; if not, circle back
- ret ; otherwise, return
- _init_tcs_ endp
-
- ; Phar Lap takes over MS-DOS Interrupt 21h, function 25h,
- ; and uses it to extend system calls. In the remainder
- ; of this file, we make extensive use of these Phar Lap
- ; services. For more information on these. please refer
- ; to the approriate section of your Phar Lap manual.
-
- align 4
- ; ************************************************************ */
- ; _init_timer_isr_
- ; Purpose: initialize our Timer Tick ISR
- ; Parameters: none
- ; Return value in eax: 0 if ok, -1 if problem
- ; ************************************************************ */
- _init_timer_isr_ proc near ; label for Fortran
- _init_timer_isr: ; label for C and Pascal
- public _init_timer_isr_
- public _init_timer_isr
-
- mov ax,250ch ; get hardware interrupt vectors
- int 21h ; returns IRQ0 (timer) in al
- mov cl,al ; timer vector -> cl
- mov timer_vector,al ; save value for later
- mov ax,2503h ; get real mode interrupt vector
- int 21h ; for timer, return value in ebx
- ; Since the linker combines all segments into one big segment,
- ; all offsets are from the beginning of this one big segment.
- ; The following ASSUME is to pacify the assembler.
- assume ds:rmcode
- mov old_timer_isr,ebx
- ; get protected and real mode addresses of intermode call buffer
- assume ds:DGROUP
- mov ax,250dh ; returns real mode address in ebx
- int 21h ; and prot mode address in es:edx
- jc #err
- ; The size of the Intermode Call Data Buffer is returned in ecx
- ; This buffer is allocated by passing RUN386.EXE the -CALLBUFS
- ; parameter of at least 1. If ecx = 0, then this was not done.
- jecxz #err
- ; The following ASSUME is to pacify the assembler.
- assume ds:rmcode
- mov rdb,ebx ; real intermode data buffer
- assume ds:DGROUP,es:nothing
- mov pdb_offset,edx ; prot intermode data buffer offset
- mov ax,es
- mov pdb_seg,ax ; prot intermode data buffer seg
- ; size of our template to overlay intermode data buffer
- mov ecx,SIZE intermode_buffer
- ; zero out our section of the intermode data buffer. Note that
- ; the flag 'do_sound' is in this buffer and that it is essential
- ; to initialize it to the value NOT_PLAYING before installing
- ; our Timer Tick ISR. Since NOT_PLAYING is equal to zero, the
- ; following code will do this as a side effect . We have, however,
- ; chosen to separately initialize 'do_sound' to avoid potential
- ; problems if the value NOT_PLAYING is ever changed.
- until_buffer_clear:
- mov byte ptr es:[edx+ecx-1],0
- loop until_buffer_clear
- mov es:[edx.do_sound],NOT_PLAYING
- mov ax,ds ; restore segment selector to es
- assume es:DGROUP
- mov es,ax
- ; get real mode address of _timer_isr
- ; The following ASSUME is to pacify the assembler.
- assume cs:rmcode
- lea ebx,_timer_isr
- assume cs:codeseg
- xor ecx,ecx ; zero out ecx
- mov ax,250fh ; returns real mode address
- int 21h ; in ecx
- jc #err
- ; Set real mode timer interrupt vector to our timer ISR
- mov ebx,ecx
- mov cl,timer_vector
- mov ax,2505h
- int 21h
- jc #err
- xor eax,eax ; zero out eax
- #exit:
- ret
- #err:
- mov eax,ERROR_VAL
- jmp #exit
-
- align 4
- _init_timer_isr_ endp
-
- align 4
- ; ************************************************************ */
- ; _restore_timer_isr_
- ; Purpose: reset real mode timer interrupt vector
- ; to original timer interrupt service routine
- ; Parameters: none
- ; Return value in eax: 0 if ok, -1 if problem
- ; ************************************************************ */
-
- _restore_timer_isr_ proc near ; label for Fortran
- _restore_timer_isr: ; label for C and Pascal
- public _restore_timer_isr_
- public _restore_timer_isr
-
- ; The following ASSUME is to pacify the assembler.
- assume ds:rmcode
- mov ebx,old_timer_isr
- assume ds:DGROUP
- mov ax,2505h
- int 21h
- jc #err
- xor eax,eax
- #exit:
- ret
- #err:
- mov eax,ERROR_VAL
- jmp #exit
- _restore_timer_isr_ endp
-
-
- note struc
- notelength dd 0 ; length of time to sound the note
- notename db ' '; name of the note
- db 0 ; null byte at end of name
- note ends
-
- align 4
- ; ************************************************************ */
- ; _play_
- ; Purpose: plays a tune
- ; Parameter: pointer to (possibly empty) array of
- ; structures of type note
- ; Return value in eax: 0 if ok, -1 if problem
- ; ************************************************************ */
- _play_ proc near ; label for Fortran
- _play: ; label for C and Pascal
- public _play_
- public _play
-
- mov ebx,[esp+4] ; address of notes -> ebx
- ; get original value of Timer Chip Channel 2 count register
- mov al,DONT_CARE+LATCH+TIMER2
- out TIMER_CTL_PORT,al
- in al,TIMER0_PORT ; get least significant byte
- mov ah,al ; and save it in ah
- in al,TIMER0_PORT ; get most significant byte
- xchg ah,al ; put bytes in correct order
- mov old_timer2_count,ax ; and save the original count
-
- ; protected mode address of Intermode Call Data Buffer -> es:esi
- mov esi,pdb_offset
- mov ax,pdb_seg
- assume es:nothing
- mov es,ax
- ; The variable 'call_orig', in the Intermode Call Data Buffer,
- ; acts as a counter. When it goes to 0, control is passed to
- ; the original Timer Tick ISR. Be sure to initialize this
- ; counter before changing Timer Channel 0 count register
- mov es:[esi.call_orig],TIMER_FACTOR
- mov al,COUNT_BINARY+MODE3+LSB_MSB+TIMER0
- out TIMER_CTL_PORT,al
- mov ax,65536/TIMER_FACTOR
- out TIMER0_PORT,al ; send least significant byte
- mov al,ah
- out TIMER0_PORT,al ; send most significant byte
- more_notes:
- mov ecx,[ebx.notelength] ; get next note length
- jecxz no_more_notes ; if zero, we are done
- mov es:[esi.duration],ecx ; set the note's duration
- cmp [ebx.notename],'R' ; R - the Rest is silence
- jnz not_a_pause
- mov es:[esi.do_sound],COUNTING_DOWN
- jmp short until_sound_done
- not_a_pause:
- call _get_count ; get count for Timer Channel 2
- jc #err
- mov ecx,65000 ; check for out of range count
- cmp eax,ecx
- ja #err ; frequency too low
- mov ecx,eax
- ; Change Timer Channel 0 count register to new count
- mov al,COUNT_BINARY+MODE3+LSB_MSB+TIMER2
- out TIMER_CTL_PORT,al
- mov ax,cx
- out TIMER2_PORT,al
- mov al,ah
- out TIMER2_PORT,al
- ; start the note playing
- mov es:[esi.do_sound],START_SOUND
- until_sound_done:
- cmp es:[esi.duration],0
- jne until_sound_done
- ; signal the Timer Tick ISR that note is done
- mov es:[esi.do_sound],NOT_PLAYING
- add ebx, SIZE note ; advance to the next note
- jmp more_notes
- clc ; Clear carry flag denotes All OK
- no_more_notes:
- #exit:
- ; restore normal Timer Channel 0 count
- mov al,COUNT_BINARY+MODE3+LSB_MSB+TIMER0
- out TIMER_CTL_PORT,al
- mov al,0h
- out TIMER0_PORT,al
- out TIMER0_PORT,al
- ; restore normal Timer Channel 2 count
- mov al,COUNT_BINARY+MODE3+LSB_MSB+TIMER2
- out TIMER_CTL_PORT,al
- mov ax,old_timer2_count
- out TIMER2_PORT,al
- mov al,ah
- out TIMER2_PORT,al
- mov ax,ds ; restore DGROUP to es
- mov es,ax
- assume es:DGROUP
- sbb eax,eax ; eax = 0 if okay, -1 if error
- ret
- #err:
- stc
- jmp #exit
-
- align 4
- _play_ endp
-
- ; ************************************************************ */
- ; _get_count
- ; Purpose: returns count field of tone
- ; Parameter: pointer to structure of type note
- ; Return value in eax. If error, carry flag set.
- ; ************************************************************ */
- _get_count proc near
- push ecx
- push edi
- push es ; es holds segment of protected data buffer
- mov ax,ds
- assume es:DGROUP
- mov es,ax
- ; The octave number may be in the second position
- movzx eax,byte ptr [ebx.notename+1]
- lea edi,octave_numbers
- mov ecx,end_octave_numbers-octave_numbers
- cld
- repne scas byte ptr es:[edi]
- jz found_octave_number
- ; Or the octave number may be in the third position
- movzx eax,byte ptr [ebx.notename+2]
- lea edi,octave_numbers
- mov ecx,end_octave_numbers-octave_numbers
- repne scas byte ptr es:[edi]
- ; If not in second or third, its an error
- jnz #err
- found_octave_number:
- ; Test for special case that note is C8
- cmp [ebx.notename+1],'8'
- jl not_octave8
- cmp [ebx.notename],'C'
- jne #err
- lea edi,o8
- mov eax,[edi.count]
- clc ; carry flag clear signals that all is ok
- jmp short #exit
- not_octave8:
- mov ecx,(offset o1 - offset o0) / SIZE tone
- sub eax,'0' ; calculate subscript into array of
- shl eax,2 ; pointers to octaves
- lea edi, _octp
- mov edi,[edi+eax] ; pointer to current octave -> edi
- mov ax,word ptr [ebx.notename]
- find_name:
- scasw
- jz found_name
- add edi,SIZE tone-2
- loop find_name
- jnz #err
- found_name:
- sub edi,2
- mov eax,[edi.count]
- clc ; carry flag clear signals that all is ok
- #exit:
- assume es:nothing
- pop es ; restore segment of protected data buffer
- pop edi
- pop ecx
- ret
- #err:
- assume es:DGROUP
- stc ; carry flag set signals a problem
- jmp #exit
-
- _get_count endp
-
- codeseg ends
-
- ; real mode code segment
- rmcode segment byte public use16
- assume cs:rmcode,ds:DGROUP
- old_timer_isr dd ? ; address of old timer ISR (real mode)
- rdb label dword ; real address of intermode call data buffer
- rdb_offset dw ? ; real offset of intermode call data buffer
- rdb_seg dw ? ; real segment of intermode call data buffer
- old_spkr_port db ? ; old value of speaker port
- ; ************************************************************ */
- ; _timer_isr
- ; Purpose: service timer tick interrupt in real mode
- ; and, every fourth invocation, pass control to
- ; original timer tick ISR (Interrupt Service Routine)
- ; Parameters: none
- ; Return value: none
- ; ************************************************************ */
- _timer_isr proc near
- assume ds:NOTHING
- push ax
- push ds
- push si
- mov si,rdb_offset
- mov ax,rdb_seg
- mov ds,ax
- cmp ds:[si.do_sound],NOT_PLAYING
- jz #exit
- cmp ds:[si.do_sound],START_SOUND
- jnz dont_start_sound
- in al,SPEAKER_PORT
- mov old_spkr_port,al
- or al,3
- out SPEAKER_PORT,al
- mov [si.do_sound],COUNTING_DOWN
- jmp short dont_stop_sound
- dont_start_sound:
- dec ds:[si.duration]
- jnz dont_stop_sound
- mov al,old_spkr_port
- out SPEAKER_PORT,al
- dont_stop_sound:
- dec ds:[si.call_orig]
- jnz dont_reinit
- mov ds:[si.call_orig],TIMER_FACTOR
- dont_reinit:
- #exit:
- assume ds:DGROUP
- pop si
- pop ds
- pop ax
- jz do_call_orig
- ; Normally, the EOI (End of Interrupt) Signal is sent by the
- ; Timer Tick ISR in the ROM BIOS, but since we are bypassing
- ; it 3 times out of 4, we must send the EOI ourselves.
- mov al,EOI
- out EOI_PORT,al
- iret
- do_call_orig:
- jmp old_timer_isr
-
- _timer_isr endp
-
- rmcode ends
-
- ; real mode data segment
- rmdata segment dword public use16
-
- end_real label byte
- public end_real ; marks end of real mode code and data
- rmdata ends
-
- end
-