8253 sound functions
( Additional information / details; Part II )
| Tables of frequencies stored in the monitor
The monitor program 1Z-013A uses 2 tables to compute the output frequencies of the valid notes. The first table at location $026C called MTBL is used for the notes C, D, E, F, G, A, and B, and for a rest R. The second table at location $0284 called M#TBL is used for the seminotes #C, #D, #E ( ! ), #F, #G, #A, and #B ( ! ), and for a rest R if #R was coded.
Each of the 8 table entries contains 1 ASCII character for the name of the note ( e.g. C, D, E, etc. ) and at least an ASCII-character "R" for a rest. Each ASCII character is followed by a 2 byte hexadecimal value used to compute the load value for the counter's register. If the upper octave is coded without any tempo specification ( e.g. "+A" ) then the value ( i.e. $04EC or 1260 dec. ) is used without any computation. The last entry is for a rest and contains $0000.
The values in both tables of the monitor 1Z-013A are specified for the upper octave and the values for the other octaves are simply computed by a multiplication by 2 for the medium octave and by a multiplication by 4 for the lower octave.
The seminote #E isn't a valid musical note but the MZ plays the note F instead of #E! The frequency of the lower octave -#B results to that of the medium octave C and the medium octave #B results to that of the upper octave +C. They have the same output frequency but the upper octave +#B has the output frequency of note C of the octave that follows the upper octave.
To this, see the following table of the frequencies generated by the
8253 if the input frequency SOIN of your MZ-700 is 1.1088MHz exactly
otherwise other frequencies will be outputted. If exact frequencies
were needed try to measure the exact input frequency SOIN of your MZ
by a frequency counter. Then try to tune your MZ to 1.1088MHZ or calculate
the resulting frequencies for each note by:
For the medium octave multiply the table value by 2 and for the lower octave multiply the table value by 4 and next compute the frequencies again.
For example, if your MZ-frequency SOIN is 1.107MHz and you want to know the output frequency of the note +#B ( table value of note +#B is $0423 that's 1059 decimal ):
1.107MHz / 1059 = 1045.3Hz ( instead of 1046.5Hz, deviation is -1.2Hz ).
| Other frequencies of musical notes
The following table shows the frequencies of other notes within more lower and more upper octaves to enable you to program your own compositions with other, expanded notes ( all frequencies in Hz; middle A has 440Hz; values in paranthesis are the counter register values which are to load into the port $E004 for the associated note frequency ):
The inaccuracy/deviation of the real output frequency played by the 8253 grows with its nominal frequency value shown in the table.
For example the nominal frequency of the last entry for note G is 12,543.85Hz:
I calculated a counter register value of $0058 or 88 decimal. The real output frequency will be:
1.1088MHz / 88 = 12,600.00Hz ( deviation is +56.15Hz = +0.45 % ).
The same calculation but for the first entry, that is the lowest note #C having a frequency of 17.32Hz and a counter register value of $FA12 or 64,018 decimal:
1.1088MHz / 64,018 = 17.32Hz ( deviation is ±0 % ).
You must not include the values in a table of your program. You can compute the frequencies as shown in the following program written in BASIC but the first 13 values ( <16.9Hz, i.e. FREQ(0) to FREQ(12) ) can't be outputted by the 8253. The minimum output frequency is 16.9Hz with an input frequency of 1.1088MHz and these first 13 notes have lower frequency values. The resulting values in FREQ(I) base on A=440, the middle A that has a frequency of 440Hz.
10 DIM FREQ(127): REM insert table for 128 notes 20 A=440: REM middle A has 440Hz 30 FOR I=0 to 127: REM loop 128 times 40 FREQ(I)=(440/32)*(2((I-9)/12)):REM calculate frequency 50 NEXT I: REM next if not all
|Description of 8253 related subroutines|
| The monitor subroutine
BELL ( loc. $003E )
BELL invokes the subroutine ?BEL by a jump command: JP ?BEL at location $003E. ?BEL is located at $0577. Here is a copy of this subroutine:
003E C37705 BELL: JP ?BEL ;invoke ?BEL 0577 D5 ?BEL: PUSH DE ;save DE 0578 115203 LD DE,?BELD ;address to bell data 057B F7 RST 6 ;invoke MELDY 057C D1 POP DE ;restore DE 057D C9 RET ;goback to caller 0352 D7 ?BELD: DB 0D7H ; "+" upper octave 0353 4130 DB "A0" ; note "A" 1/32 0355 0D DB 0DH ; end of data char.
Everytime ?BEL is called a short tone, the 1/32 note "A" having 880Hz, comes over the loudspeaker. To this the subroutine MELDY at location $0030 is called by RST 6 at location $057B. Before invoking MELDY the bell data address to a simple note +A0 is stored into the register DE. This address in register DE is used by MELDY to play the note.
If you enter the monitor command B then ?BEL is called everytime a key is pressed. The status of the beep function will be inverted by the next command B. To this see what the monitor does in detail:
00CD FE42 CP 'B' ;BELL command? 00CF 2826 JZ Z,SG ;yes, goto SG ($00F7) 00F7 3A9D11 SG: LD A,(SWRK) ;get bell status bit 0 00FA 1F RRA ;shift bit 0 into carry flag 00FB 3F CCF ;complement carry flag 00FC 17 RLA ;shift carry flag into bit 0 00FD 18A5 JR SS+2 ;goto ss + 2 bytes ($00A4) 00A4 329D11 LD (SWRK),A ;store inverted status
If the B command was entered into the input line area of the monitor ( loc. $11A3 ) then the subroutine SG will be invoked by the jump command at location $00CF.
First the bell status SWRK at location $119D is loaded into the accumulator register A and then A is rotated right whereby bit 0 will be stored into the carry flag. The carry flag will be inverted by the command CCF at location $00FB and then the accumulator register is rotated left whereby the ( inverted ) carry flag is inserted into bit 0.
At least the contents of register A is stored back into the bell status SWRK at location $119D.
The subroutine GETL is active while your monitor 1Z-013A awaits any input / key and the cursor is active. The subroutine GETL is at location $0003 and invokes the subroutine ?GETL at location $07E6 by a simple jump command. ?GETL interprets the bell status and invokes the bell subroutine if necessary ( see the monitor program at loc. $07EF ):
0003 C3E607 GETL: JP ?GETL 07E6 ?GETL: ......... 07EF 3A9D11 LD A,(SWRK) ;get bell satus 07F2 0F RRCA ;rotate bit 0 into carry flag 07F3 DA7705 CALL NC,?BEL ;invoke ?BEL if carry flag not set
|The monitor subroutine
XTEMP can be called by CALL $0041 to set the tempo of a melody is to be played.
XTEMP jumps to ?TEMP at location $02E5 and sets the variable TEMPW. TEMPW is used by ONPU to set the correct value of the counter register.
The accu is to establish with the tempo before invoking XTEMP. The valid range is from $01 ( slow ) to $07 ( fast ). ?TEMP changes from $01 ( fast ) to $07 ( slow ), contrary to the input value stored by the caller into the accu before calling XTEMP:
02E5 F5 ?TEMP: PUSH AF ;save AF on stack 02E6 C5 PUSH BC ;save BC on stack 02E7 E60F AND 0FH ;turn off bits 7 - 4 02E9 47 LD B,A ;tempo value to reg. B 02EA 3E08 LD A,8 ;compute $01 - $07 02EC 90 SUB B ;to $07 - $01 02ED 329E11 LD (TEMPW),A ;set TEMPW used by ONPU subroutine 02F0 C1 POP BC ;restore callers register BC 02F1 F1 POP AF ;restore callers register BC 02F2 C9 RET ;goback to caller
|The monitor subroutines MSTA
These routines start / stop the counter #0.
MSTA at location $0044 invokes MLDST at location $02AB.
MSTP at location $0047 invokes MLDSP at location $02BE.
MSTP/MLDSP needs no further input but MSTA / MLDST uses RATIO at location $11A1 / 11A2 to set the correct counter register value for the note to be played.
This is a copy of these routines:
0044 C3AB02 MSTA: JP MLDST ;start output (input is RATIO) 0047 C3BE02 MSTP: JP MLDSP ;stop output 02AB 2AA111 MLDST: LD HL,(RATIO) ;get RATIO 02AE 7C LD A,H ;get most significant byte 02AF B7 OR A ;sets zero flag if A=0 02B0 280C JR Z,MLDSP ;RATIO <= $00FF not allowed ( why not? ) 02B2 D5 PUSH DE ;save callers register DE 02B3 EB EX DE,HL ;RATIO to DE 02B4 2104E0 LD HL,CONT0 ;address to port $E004 02B7 73 LD (HL),E ;load least significant byte (LSB) 02B8 72 LD (HL),D ;then MSB into port $E004 02B9 3E01 LD A,1 ;prepare to turn counter on 02BB D1 POP DE ;restore register 02BC 1806 JR MLDS1 ;jump to MLDS1 02BE 3E36 MLDSP: LD A,36H ;control word of $36 02C0 3207E0 LD (CONTF),A ;into port $E007 (counter #0) 02C3 AF XOR A ;prepare to turn counter off 02C4 3208E0 MLDS1: LD (SUNDG),A ;turn counter on/off 02C7 C9 RET ;return to caller
RATIO may not be lower than $0100 ( see $02AB to $02B0 ). If so, the current output will be stopped if present or no output will come over the loudspeaker.
But if RATIO is >= $00FF then it will be loaded into the counter register $E004 of counter #0 ( $02B4 to $02B8 ).
Next the counter will be started by setting bit D0 of port $E008 ( $02B9 and $02C4 ). This means, first, the GATE0 initiates counting on the rising edge ( GATE 0 goes high ) with the loaded value RATIO into the counter register and next counting will be enabled at the end of the rising edge ( GATE0 is high ). These steps will occur on the next pulse at CLK0 by the signal SOIN.
To stop an output the control word of $36 is written to the counter #0 ( $02BE and $02C0 ) and the GATE0 is set to low ( $02C3 and $02C4 ).
|The monitor subroutine
This routine is used by the routine MELDY / ?MLDY which plays a melody. RYTHM is used to control the tempo a melody is to be played and to control the time periods for a note / rest.
To this the appropriate numbers of cycles of the generator of 32Hz pulses ( tempo controller part3 in the schematic diagram ) were counted given by the register B on entry to RYTHM. See the following copy of tis monitor subroutine.
First the SHIFT / BREAK keys were checked to break the current play of a melody if the user entered these keys ( $02C8 to $02D4 ):
02C8 2100E0 RYTHM: LD HL,KEYPA ; address to port $E000 keyboard input 02CB 36F8 LD (HL),0F8H ; select column of SHIFT and BREAK keys 02CD 23 INC HL ; address to port $E001 keyboard output 02CE 7E LD A,(HL) ; read keyboard row 02CF E681 AND 81H ; bit 7=BREAK, bit 1=SHIFT 02D1 2002 JR NZ,L02D5 ; goto L02D5 if not break condition 02D3 37 SCF ; Break condition, carryflag will show 02D4 C9 RET ; break condition, goback to caller 02D5 3A08E0 L02D5: LD A,(TEMP) ; get cycle of generator 32Hz from 02D8 0F RRCA ; bit 0 of port $E008 (signal TEMPO OUT) 02D9 38FA JR C,L02D5 ; wait on low 02DB 3A08E0 L02DB: LD A,(TEMP) ; get TEMPO OUT again 02DE 0F RRCA ; 02DF 30FA JR NC,L02DB ; wait on high 02E1 10F2 DJNZ L02D5 ; wait for all defined cycles by B-reg. 02E3 AF XOR A ; indicate no break by carry flag. ; all defined cycles awaited. 02E4 C9 RET ; returning to caller
The number of cycles contained in the register B will be counted by $02E1.
The instruction set between $02D5 and $02E1 will be executed until the number of cycles given by the register B is reached.
First the routine waits for the end of the low part of the square wave ( $02D5 to $02D9 ) then for the end of the high part ( $02DB to $02DF ). If both the low part and the high part were detected, a full cycle is occured in bit 0 of port $E008 and the instruction at location $02E1 will be executed. This is a DJNZ instruction that counts down the contents of the register B until the contents is 0.
If the register contents is not 0 then a branch to L02D5 occurs too.
If the contents is 0 then the next instruction just behind the DJNZ instruction at location $02E3 will be executed.
The carry flag will be reset by XOR A to indicate that no break condition occured.
A description of the monitor subroutines ( ONPU, MLDY / ?MLDY ) and related work area variables $119E to $11A2 and a description of the tables stored in the monitor is in progress.
A description of the following hardware is in progress too: tempo controllers
1 - 3 for start / stop output by writing bit D0 of port $E008 ( IC #6F
and #7E ) to GATE0, tempo control / intervals ( reading bit D0 from
the buffer IC #4C and the timer 32Hz