С любовью к вам, Yandex.Direct
Размещение рекламы на форуме способствует его дальнейшему развитию
мне кажется это очень геморно.
возможно проще самому слать данные в порт AYшки
Так, отсюда - http://www.matthew-wilson.net/spectr.../128_ROM0.html
Оператор PLAY находится по адресу 0985h, я так понимаю.
- - - Добавлено - - -
К этому придем, я пока пытаюсь понять как и что. В идеале нужно миди файлы закидывать через последовательный порт AY-MIDI интерфейса.
http://www.fruitcake.plus.com/Sincla...isassembly.htm
ищи именно midi в дизасме rom0
(чуток опоздал с советом, вижу что уже копаешь в этом направлении)
Перевел по старинке бейсиковский файл Bach.tap в trd
загружать из TR-DOS
RUN "MIDIBACH"
Вложение MidiBach_trd.rar
Проверил, вроде играет.
nimdasys_inbox_ru(29.05.2021)
Небольшое видео с примером звучания Баха
https://vk.com/micklab?z=video-19161...3ac079b886751b
Продолжаем попытки исследования оператора PLAY
Исходя из исходников Basic-128 оператор PLAY располагается в адресах ПЗУ 0985h...11ECh
Непосредственно с миди каналом работают следующие процедуры
0CDDh - Play Command 'Y' (MIDI Channel)
0CEEh - Play Command 'Z' (MIDI Programming Code)
116Eh - Play Note on MIDI Channel
118Dh - Turn MIDI Channel Off
11A3h - Send Byte to MIDI Device
Собственно подпрограмму отправки байта в канал Миди можно использовать в своих программах, ибо она уже расчитана на скорость 31250
Код:; ===================== ; PLAY COMMAND ROUTINES ; ===================== ; Up to 3 channels of music/noise are supported by the AY-3-8912 sound generator. ; Up to 8 channels of music can be sent to support synthesisers, drum machines or sequencers via the MIDI interface, ; with the first 3 channels also played by the AY-3-8912 sound generator. For each channel of music, a MIDI channel ; can be assigned to it using the 'Y' command. ; ; The PLAY command reserves and initialises space for the PLAY command. This comprises a block of $003C bytes ; used to manage the PLAY command (IY points to this command data block) and a block of $0037 bytes for each ; channel string (IX is used to point to the channel data block for the current channel). [Note that the command ; data block is $04 bytes larger than it needs to be, and each channel data block is $11 bytes larger than it ; needs to be] ; ; Entry: B=The number of strings in the PLAY command (1..8). ; ------------------------- ; Command Data Block Format ; ------------------------- ; IY+$00 / IY+$01 = Channel 0 data block pointer. Points to the data for channel 0 (string 1). ; IY+$02 / IY+$03 = Channel 1 data block pointer. Points to the data for channel 1 (string 2). ; IY+$04 / IY+$05 = Channel 2 data block pointer. Points to the data for channel 2 (string 3). ; IY+$06 / IY+$07 = Channel 3 data block pointer. Points to the data for channel 3 (string 4). ; IY+$08 / IY+$09 = Channel 4 data block pointer. Points to the data for channel 4 (string 5). ; IY+$0A / IY+$0B = Channel 5 data block pointer. Points to the data for channel 5 (string 6). ; IY+$0C / IY+$0D = Channel 6 data block pointer. Points to the data for channel 6 (string 7). ; IY+$0E / IY+$0F = Channel 7 data block pointer. Points to the data for channel 7 (string 8). ; IY+$10 = Channel bitmap. Initialised to $FF and a 0 rotated in to the left for each string parameters ; of the PLAY command, thereby indicating the channels in use. ; IY+$11 / IY+$12 = Channel data block duration pointer. Points to duration length store in channel 0 data block (string 1). ; IY+$13 / IY+$14 = Channel data block duration pointer. Points to duration length store in channel 1 data block (string 2). ; IY+$15 / IY+$16 = Channel data block duration pointer. Points to duration length store in channel 2 data block (string 3). ; IY+$17 / IY+$18 = Channel data block duration pointer. Points to duration length store in channel 3 data block (string 4). ; IY+$19 / IY+$1A = Channel data block duration pointer. Points to duration length store in channel 4 data block (string 5). ; IY+$1B / IY+$1C = Channel data block duration pointer. Points to duration length store in channel 5 data block (string 6). ; IY+$1D / IY+$1E = Channel data block duration pointer. Points to duration length store in channel 6 data block (string 7). ; IY+$1F / IY+$20 = Channel data block duration pointer. Points to duration length store in channel 7 data block (string 8). ; IY+$21 = Channel selector. It is used as a shift register with bit 0 initially set and then shift to the left ; until a carry occurs, thereby indicating all 8 possible channels have been processed. ; IY+$22 = Temporary channel bitmap, used to hold a working copy of the channel bitmap at IY+$10. ; IY+$23 / IY+$24 = Address of the channel data block pointers, or address of the channel data block duration pointers ; (allows the routine at $0A6E (ROM 0) to be used with both set of pointers). ; IY+$25 / IY+$26 = Stores the smallest duration length of all currently playing channel notes. ; IY+$27 / IY+$28 = The current tempo timing value (derived from the tempo parameter 60..240 beats per second). ; IY+$29 = The current effect waveform value. ; IY+$2A = Temporary string counter selector. ; IY+$2B..IY+$37 = Holds a floating point calculator routine. ; IY+$38..IY+$3B = Not used. ; ------------------------- ; Channel Data Block Format ; ------------------------- ; IX+$00 = The note number being played on this channel (equivalent to index offset into the note table). ; IX+$01 = MIDI channel assigned to this string (range 0 to 15). ; IX+$02 = Channel number (range 0 to 7), i.e. index position of the string within the PLAY command. ; IX+$03 = 12*Octave number (0, 12, 24, 36, 48, 60, 72, 84 or 96). ; IX+$04 = Current volume (range 0 to 15, or if bit 4 set then using envelope). ; IX+$05 = Last note duration value as specified in the string (range 1 to 9). ; IX+$06 / IX+$07 = Address of current position in the string. ; IX+$08 / IX+$09 = Address of byte after the end of the string. ; IX+$0A = Flags: ; Bit 0 : 1=Single closing bracket found (repeat string indefinitely). ; Bits 1-7: Not used (always 0). ; IX+$0B = Open bracket nesting level (range $00 to $04). ; IX+$0C / IX+$0D = Return address for opening bracket nesting level 0 (points to character after the bracket). ; IX+$0E / IX+$0F = Return address for opening bracket nesting level 1 (points to character after the bracket). ; IX+$10 / IX+$11 = Return address for opening bracket nesting level 2 (points to character after the bracket). ; IX+$12 / IX+$13 = Return address for opening bracket nesting level 3 (points to character after the bracket). ; IX+$14 / IX+$15 = Return address for opening bracket nesting level 4 (points to character after the bracket). ; IX+$16 = Closing bracket nesting level (range $FF to $04). ; IX+$17...IX+$18 = Return address for closing bracket nesting level 0 (points to character after the bracket). ; IX+$19...IX+$1A = Return address for closing bracket nesting level 1 (points to character after the bracket). ; IX+$1B...IX+$1C = Return address for closing bracket nesting level 2 (points to character after the bracket). ; IX+$1D...IX+$1E = Return address for closing bracket nesting level 3 (points to character after the bracket). ; IX+$1F...IX+$20 = Return address for closing bracket nesting level 4 (points to character after the bracket). ; IX+$21 = Tied notes counter (for a single note the value is 1). ; IX+$22 / IX+$23 = Duration length, specified in 96ths of a note. ; IX+$24...IX+$25 = Subsequent note duration length (used only with triplets), specified in 96ths of a note. ; IX+$26...IX+$36 = Not used. L0985: DI ; Disable interrupts to ensure accurate timing. ;Create a workspace for the play channel command strings PUSH BC ; B=Number of channel string (range 1 to 8). Also used as string index number in the following loop. LD DE,$0037 ; LD HL,$003C ; L098D: ADD HL,DE ; Calculate HL=$003C + ($0037 * B). DJNZ L098D ; LD C,L ; LD B,H ; BC=Space required (maximum = $01F4). RST 28H ; DEFW BC_SPACES ; $0030. Make BC bytes of space in the workspace. DI ; Interrupts get re-enabled by the call mechanism to ROM 1 so disable them again. PUSH DE ; POP IY ; IY=Points at first new byte - the command data block. PUSH HL ; POP IX ; IX=Points at last new byte - byte after all channel information blocks. LD (IY+$10),$FF ; Initial channel bitmap with value meaning 'zero strings' ;Loop over each string to be played L09A0: LD BC,$FFC9 ; $-37 ($37 bytes is the size of a play channel string information block). ADD IX,BC ; IX points to start of space for the last channel. LD (IX+$03),$3C ; Default octave is 5. LD (IX+$01),$FF ; No MIDI channel assigned. LD (IX+$04),$0F ; Default volume is 15. LD (IX+$05),$05 ; Default note duration. LD (IX+$21),$00 ; Count of the number of tied notes. LD (IX+$0A),$00 ; Signal not to repeat the string indefinitely. LD (IX+$0B),$00 ; No opening bracket nesting level. LD (IX+$16),$FF ; No closing bracket nesting level. LD (IX+$17),$00 ; Return address for closing bracket nesting level 0. LD (IX+$18),$00 ; [No need to initialise this since it is written to before it is ever tested] ; [*BUG* - At this point interrupts are disabled and IY is now being used as a pointer to the master ; PLAY information block. Unfortunately, interrupts are enabled during the STK_FETCH call and ; IY is left containing the wrong value. This means that if an interrupt were to occur during ; execution of the subroutine then there would be a one in 65536 chance that (IY+$40) will be ; corrupted - this corresponds to the volume setting for music channel A. ; Rewriting the SWAP routine to only re-enable interrupts if they were originally enabled ; would cure this bug (see end of file for description of her suggested fix). Credit: Toni Baker, ZX Computing Monthly] ; [An alternative and simpler solution to the fix Toni Baker describes would be to stack IY, set IY to point ; to the system variables at $5C3A, call STK_FETCH, disable interrupts, then pop the stacked value back to IY. Credit: Paul Farrow] RST 28H ; Get the details of the string from the stack. DEFW STK_FETCH ; $2BF1. DI ; Interrupts get re-enabled by the call mechanism to ROM 1 so disable them again. LD (IX+$06),E ; Store the current position within in the string, i.e. the beginning of it. LD (IX+$07),D ; LD (IX+$0C),E ; Store the return position within the string for a closing bracket, LD (IX+$0D),D ; which is initially the start of the string in case a single closing bracket is found. EX DE,HL ; HL=Points to start of string. BC=Length of string. ADD HL,BC ; HL=Points to address of byte after the string. LD (IX+$08),L ; Store the address of the character just LD (IX+$09),H ; after the string. POP BC ; B=String index number (range 1 to 8). PUSH BC ; Save it on the stack again. DEC B ; Reduce the index so it ranges from 0 to 7. LD C,B ; LD B,$00 ; SLA C ; BC=String index*2. PUSH IY ; POP HL ; HL=Address of the command data block. ADD HL,BC ; Skip 8 channel data pointer words. PUSH IX ; POP BC ; BC=Address of current channel information block. LD (HL),C ; Store the pointer to the channel information block. INC HL ; LD (HL),B ; OR A ; Clear the carry flag. RL (IY+$10) ; Rotate one zero-bit into the least significant bit of the channel bitmap. ; This initially holds $FF but once this loop is over, this byte has ; a zero bit for each string parameter of the PLAY command. POP BC ; B=Current string index. DEC B ; Decrement string index so it ranges from 0 to 7. PUSH BC ; Save it for future use on the next iteration. LD (IX+$02),B ; Store the channel number. JR NZ,L09A0 ; Jump back while more channel strings to process. POP BC ; Drop item left on the stack. ;Entry point here from the vector table at $011B L0A05: LD (IY+$27),$1A ; Set the initial tempo timing value. LD (IY+$28),$0B ; Corresponds to a 'T' command value of 120, and gives two crotchets per second. PUSH IY ; POP HL ; HL=Points to the command data block. LD BC,$002B ; ADD HL,BC ; EX DE,HL ; DE=Address to store RAM routine. LD HL,L0A31 ; HL=Address of the RAM routine bytes. LD BC,$000D ; LDIR ; Copy the calculator routine to RAM. LD D,$07 ; Register 7 - Mixer. LD E,$F8 ; I/O ports are inputs, noise output off, tone output on. CALL L0E7C ; Write to sound generator register. LD D,$0B ; Register 11 - Envelope Period (Fine). LD E,$FF ; Set period to maximum. CALL L0E7C ; Write to sound generator register. INC D ; Register 12 - Envelope Period (Coarse). CALL L0E7C ; Write to sound generator register. JR L0A7D ; Jump ahead to continue. ; [Could have saved these 2 bytes by having the code at $0A7D (ROM 0) immediately follow] ; ------------------------------------------------- ; Calculate Timing Loop Counter <<< RAM Routine >>> ; ------------------------------------------------- ; This routine is copied into the command data block (offset $2B..$37) by ; the routine at $0A05 (ROM 0). ; It uses the floating point calculator found in ROM 1, which is usually ; invoked via a RST $28 instruction. Since ROM 0 uses RST $28 to call a ; routine in ROM 1, it is unable to invoke the floating point calculator ; this way. It therefore copies the following routine to RAM and calls it ; with ROM 1 paged in. ; ; The routine calculates (10/x)/7.33e-6, where x is the tempo 'T' parameter value ; multiplied by 4. The result is used an inner loop counter in the wait routine at $0F76 (ROM 0). ; Each iteration of this loop takes 26 T-states. The time taken by 26 T-states ; is 7.33e-6 seconds. So the total time for the loop to execute is 2.5/TEMPO seconds. ; ; Entry: The value 4*TEMPO exists on the calculator stack (where TEMPO is in the range 60..240). ; Exit : The calculator stack holds the result. L0A31: RST 28H ; Invoke the floating point calculator. DEFB $A4 ; stk-ten. = x, 10 DEFB $01 ; exchange. = 10, x DEFB $05 ; division. = 10/x DEFB $34 ; stk-data. = 10/x, 7.33e-6 DEFB $DF ; - exponent $6F (floating point number 7.33e-6). DEFB $75 ; - mantissa byte 1 DEFB $F4 ; - mantissa byte 2 DEFB $38 ; - mantissa byte 3 DEFB $75 ; - mantissa byte 4 DEFB $05 ; division. = (10/x)/7.33e-6 DEFB $38 ; end-calc. RET ; ; -------------- ; Test BREAK Key ; -------------- ; Test for BREAK being pressed. ; Exit: Carry flag reset if BREAK is being pressed. L0A3E: LD A,$7F ; IN A,($FE) ; RRA ; RET C ; Return with carry flag set if SPACE not pressed. LD A,$FE ; IN A,($FE) ; RRA ; RET ; Return with carry flag set if CAPS not pressed. ; ------------------------------------------- ; Select Channel Data Block Duration Pointers ; ------------------------------------------- ; Point to the start of the channel data block duration pointers within the command data block. ; Entry: IY=Address of the command data block. ; Exit : HL=Address of current channel pointer. L0A4A: LD BC,$0011 ; Offset to the channel data block duration pointers table. JR L0A52 ; Jump ahead to continue. ; ---------------------------------- ; Select Channel Data Block Pointers ; ---------------------------------- ; Point to the start of the channel data block pointers within the command data block. ; Entry: IY=Address of the command data block. ; Exit : HL=Address of current channel pointer. L0A4F: LD BC,$0000 ; Offset to the channel data block pointers table. L0A52: PUSH IY ; POP HL ; HL=Point to the command data block. ADD HL,BC ; Point to the desired channel pointers table. LD (IY+$23),L ; LD (IY+$24),H ; Store the start address of channels pointer table. LD A,(IY+$10) ; Fetch the channel bitmap. LD (IY+$22),A ; Initialise the working copy. LD (IY+$21),$01 ; Channel selector. Set the shift register to indicate the first channel. RET ; ; ------------------------------------------------- ; Get Channel Data Block Address for Current String ; ------------------------------------------------- ; Entry: HL=Address of channel data block pointer. ; Exit : IX=Address of current channel data block. L0A67: LD E,(HL) ; INC HL ; LD D,(HL) ; Fetch the address of the current channel data block. PUSH DE ; POP IX ; Return it in IX. RET ; ; ------------------------- ; Next Channel Data Pointer ; ------------------------- L0A6E: LD L,(IY+$23) ; The address of current channel data pointer. LD H,(IY+$24) ; INC HL ; INC HL ; Advance to the next channel data pointer. LD (IY+$23),L ; LD (IY+$24),H ; The address of new channel data pointer. RET ; ; --------------------------- ; PLAY Command (Continuation) ; --------------------------- ; This section is responsible for processing the PLAY command and is a continuation of the routine ; at $0985 (ROM 0). It begins by determining the first note to play on each channel and then enters ; a loop to play these notes, fetching the subsequent notes to play at the appropriate times. L0A7D: CALL L0A4F ; Select channel data block pointers. L0A80: RR (IY+$22) ; Working copy of channel bitmap. Test if next string present. JR C,L0A8C ; Jump ahead if there is no string for this channel. ;HL=Address of channel data pointer. CALL L0A67 ; Get address of channel data block for the current string into IX. CALL L0B5C ; Find the first note to play for this channel from its play string. L0A8C: SLA (IY+$21) ; Have all channels been processed? JR C,L0A97 ; Jump ahead if so. CALL L0A6E ; Advance to the next channel data block pointer. JR L0A80 ; Jump back to process the next channel. ;The first notes to play for each channel have now been determined. A loop is entered that coordinates playing ;the notes and fetching subsequent notes when required. Notes across channels may be of different lengths and ;so the shortest one is determined, the tones for all channels set and then a waiting delay entered for the shortest ;note delay. This delay length is then subtracted from all channel note lengths to leave the remaining lengths that ;each note needs to be played for. For the channel with the smallest note length, this will now have completely played ;and so a new note is fetched for it. The smallest length of the current notes is then determined again and the process ;described above repeated. A test is made on each iteration to see if all channels have run out of data to play, and if ;so this ends the PLAY command. L0A97: CALL L0F91 ; Find smallest duration length of the current notes across all channels. PUSH DE ; Save the smallest duration length. CALL L0F42 ; Play a note on each channel. POP DE ; DE=The smallest duration length. L0A9F: LD A,(IY+$10) ; Channel bitmap. CP $FF ; Is there anything to play? JR NZ,L0AAB ; Jump if there is. CALL L0E93 ; Turn off all sound and restore IY. EI ; Re-enable interrupts. RET ; End of play command. L0AAB: DEC DE ; DE=Smallest channel duration length, i.e. duration until the next channel state change. CALL L0F76 ; Perform a wait. CALL L0FC1 ; Play a note on each channel and update the channel duration lengths. CALL L0F91 ; Find smallest duration length of the current notes across all channels. JR L0A9F ; Jump back to see if there is more to process. ; ---------------------------- ; PLAY Command Character Table ; ---------------------------- ; Recognised characters in PLAY commands. L0AB7: DEFM "HZYXWUVMT)(NO!" ; ------------------ ; Get Play Character ; ------------------ ; Get the current character from the PLAY string and then increment the ; character pointer within the string. ; Exit: Carry flag set if string has been fully processed. ; Carry flag reset if character is available. ; A=Character available. L0AC5: CALL L0EE3 ; Get the current character from the play string for this channel. RET C ; Return if no more characters. INC (IX+$06) ; Increment the low byte of the string pointer. RET NZ ; Return if it has not overflowed. INC (IX+$07) ; Else increment the high byte of the string pointer. RET ; Returns with carry flag reset. ; -------------------------- ; Get Next Note in Semitones ; -------------------------- ; Finds the number of semitones above C for the next note in the string, ; Entry: IX=Address of the channel data block. ; Exit : A=Number of semitones above C, or $80 for a rest. L0AD1: PUSH HL ; Save HL. LD C,$00 ; Default is for a 'natural' note, i.e. no adjustment. L0AD4: CALL L0AC5 ; Get the current character from the PLAY string, and advance the position pointer. JR C,L0AE1 ; Jump if at the end of the string. CP '&' ; $26. Is it a rest? JR NZ,L0AEC ; Jump ahead if not. LD A,$80 ; Signal that it is a rest. L0ADF: POP HL ; Restore HL. RET ; L0AE1: LD A,(IY+$21) ; Fetch the channel selector. OR (IY+$10) ; Clear the channel flag for this string. LD (IY+$10),A ; Store the new channel bitmap. JR L0ADF ; Jump back to return. L0AEC: CP '#' ; $23. Is it a sharpen? JR NZ,L0AF3 ; Jump ahead if not. INC C ; Increment by a semitone. JR L0AD4 ; Jump back to get the next character. L0AF3: CP '$' ; $24. Is it a flatten? JR NZ,L0AFA ; Jump ahead if not. DEC C ; Decrement by a semitone. JR L0AD4 ; Jump back to get the next character. L0AFA: BIT 5,A ; Is it a lower case letter? JR NZ,L0B04 ; Jump ahead if lower case. PUSH AF ; It is an upper case letter so LD A,$0C ; increase an octave ADD A,C ; by adding 12 semitones. LD C,A ; POP AF ; L0B04: AND $DF ; Convert to upper case. SUB $41 ; Reduce to range 'A'->0 .. 'G'->6. JP C,L0F22 ; Jump if below 'A' to produce error report "k Invalid note name". CP $07 ; Is it 7 or above? JP NC,L0F22 ; Jump if so to produce error report "k Invalid note name". PUSH BC ; C=Number of semitones. LD B,$00 ; LD C,A ; BC holds 0..6 for 'a'..'g'. LD HL,L0DF9 ; Look up the number of semitones above note C for the note. ADD HL,BC ; LD A,(HL) ; A=Number of semitones above note C. POP BC ; C=Number of semitones due to sharpen/flatten characters. ADD A,C ; Adjust number of semitones above note C for the sharpen/flatten characters. POP HL ; Restore HL. RET ; ; ---------------------------------- ; Get Numeric Value from Play String ; ---------------------------------- ; Get a numeric value from a PLAY string, returning 0 if no numeric value present. ; Entry: IX=Address of the channel data block. ; Exit : BC=Numeric value, or 0 if no numeric value found. L0B1D: PUSH HL ; Save registers. PUSH DE ; LD L,(IX+$06) ; Get the pointer into the PLAY string. LD H,(IX+$07) ; LD DE,$0000 ; Initialise result to 0. L0B28: LD A,(HL) ; CP '0' ; $30. Is character numeric? JR C,L0B45 ; Jump ahead if not. CP ':' ; $3A. Is character numeric? JR NC,L0B45 ; Jump ahead if not. INC HL ; Advance to the next character. PUSH HL ; Save the pointer into the string. CALL L0B50 ; Multiply result so far by 10. SUB '0' ; $30. Convert ASCII digit to numeric value. LD H,$00 ; LD L,A ; HL=Numeric digit value. ADD HL,DE ; Add the numeric value to the result so far. JR C,L0B42 ; Jump ahead if an overflow to produce error report "l number too big". EX DE,HL ; Transfer the result into DE. POP HL ; Retrieve the pointer into the string. JR L0B28 ; Loop back to handle any further numeric digits. L0B42: JP L0F1A ; Jump to produce error report "l number too big". ; [Could have saved 1 byte by directly using JP C,L0F1A (ROM 0) instead of using this JP and ; the two JR C,L0B42 (ROM 0) instructions that come here] ;The end of the numeric value was reached L0B45: LD (IX+$06),L ; Store the new pointer position into the string. LD (IX+$07),H ; PUSH DE ; POP BC ; Return the result in BC. POP DE ; Restore registers. POP HL ; RET ; ; ----------------- ; Multiply DE by 10 ; ----------------- ; Entry: DE=Value to multiple by 10. ; Exit : DE=Value*10. L0B50: LD HL,$0000 ; LD B,$0A ; Add DE to HL ten times. L0B55: ADD HL,DE ; JR C,L0B42 ; Jump ahead if an overflow to produce error report "l number too big". DJNZ L0B55 ; EX DE,HL ; Transfer the result into DE. RET ; ; ---------------------------------- ; Find Next Note from Channel String ; ---------------------------------- ; Entry: IX=Address of channel data block. L0B5C: CALL L0A3E ; Test for BREAK being pressed. JR C,L0B69 ; Jump ahead if not pressed. CALL L0E93 ; Turn off all sound and restore IY. EI ; Re-enable interrupts. CALL L05AC ; Produce error report. [Could have saved 1 byte by using JP L05D6 (ROM 0)] DEFB $14 ; "L Break into program" L0B69: CALL L0AC5 ; Get the current character from the PLAY string, and advance the position pointer. JP C,L0DA2 ; Jump if at the end of the string. CALL L0DF0 ; Find the handler routine for the PLAY command character. LD B,$00 ; SLA C ; Generate the offset into the LD HL,L0DCA ; command vector table. ADD HL,BC ; HL points to handler routine for this command character. LD E,(HL) ; INC HL ; LD D,(HL) ; Fetch the handler routine address. EX DE,HL ; HL=Handler routine address for this command character. CALL L0B84 ; Make an indirect call to the handler routine. JR L0B5C ; Jump back to handle the next character in the string. ;Comes here after processing a non-numeric digit that does not have a specific command routine handler ;Hence the next note to play has been determined and so a return is made to process the other channels. L0B83: RET ; Just make a return. L0B84: JP (HL) ; Jump to the command handler routine. ; -------------------------- ; Play Command '!' (Comment) ; -------------------------- ; A comment is enclosed within exclamation marks, e.g. "! A comment !". ; Entry: IX=Address of the channel data block. L0B85: CALL L0AC5 ; Get the current character from the PLAY string, and advance the position pointer. JP C,L0DA1 ; Jump if at the end of the string. CP '!' ; $21. Is it the end-of-comment character? RET Z ; Return if it is. JR L0B85 ; Jump back to test the next character. ; ------------------------- ; Play Command 'O' (Octave) ; ------------------------- ; The 'O' command is followed by a numeric value within the range 0 to 8, ; although due to loose range checking the value MOD 256 only needs to be ; within 0 to 8. Hence O256 operates the same as O0. ; Entry: IX=Address of the channel data block. L0B90: CALL L0B1D ; Get following numeric value from the string into BC. LD A,C ; Is it between 0 and 8? CP $09 ; JP NC,L0F12 ; Jump if above 8 to produce error report "n Out of range". SLA A ; Multiply A by 12. SLA A ; LD B,A ; SLA A ; ADD A,B ; LD (IX+$03),A ; Store the octave value. RET ; ; ---------------------------- ; Play Command 'N' (Separator) ; ---------------------------- ; The 'N' command is simply a separator marker and so is ignored. ; Entry: IX=Address of the channel data block. L0BA5: RET ; Nothing to do so make an immediate return. ; ---------------------------------- ; Play Command '(' (Start of Repeat) ; ---------------------------------- ; A phrase can be enclosed within brackets causing it to be repeated, i.e. played twice. ; Entry: IX=Address of the channel data block. L0BA6: LD A,(IX+$0B) ; A=Current level of open bracket nesting. INC A ; Increment the count. CP $05 ; Only 4 levels supported. JP Z,L0F2A ; Jump if this is the fifth to produce error report "d Too many brackets". LD (IX+$0B),A ; Store the new open bracket nesting level. LD DE,$000C ; Offset to the bracket level return position stores. CALL L0C27 ; HL=Address of the pointer in which to store the return location of the bracket. LD A,(IX+$06) ; Store the current string position as the return address of the open bracket. LD (HL),A ; INC HL ; LD A,(IX+$07) ; LD (HL),A ; RET ; ; -------------------------------- ; Play Command ')' (End of Repeat) ; -------------------------------- ; A phrase can be enclosed within brackets causing it to be repeated, i.e. played twice. ; Brackets can also be nested within each other, to 4 levels deep. ; If a closing bracket if used without a matching opening bracket then the whole string up ; until that point is repeated indefinitely. ; Entry: IX=Address of the channel data block. L0BC2: LD A,(IX+$16) ; Fetch the nesting level of closing brackets. LD DE,$0017 ; Offset to the closing bracket return address store. OR A ; Is there any bracket nesting so far? JP M,L0BF0 ; Jump if none. [Could have been faster by jumping to L0BF3 (ROM 0)] ;Has the bracket level been repeated, i.e. re-reached the same position in the string as the closing bracket return address? CALL L0C27 ; HL=Address of the pointer to the corresponding closing bracket return address store. LD A,(IX+$06) ; Fetch the low byte of the current address. CP (HL) ; Re-reached the closing bracket? JR NZ,L0BF0 ; Jump ahead if not. INC HL ; Point to the high byte. LD A,(IX+$07) ; Fetch the high byte address of the current address. CP (HL) ; Re-reached the closing bracket? JR NZ,L0BF0 ; Jump ahead if not. ;The bracket level has been repeated. Now check whether this was the outer bracket level. DEC (IX+$16) ; Decrement the closing bracket nesting level since this level has been repeated. LD A,(IX+$16) ; [There is no need for the LD A,(IX+$16) and OR A instructions since the DEC (IX+$16) already set the flags] OR A ; Reached the outer bracket nesting level? RET P ; Return if not the outer bracket nesting level such that the character ; after the closing bracket is processed next. ;The outer bracket level has been repeated BIT 0,(IX+$0A) ; Was this a single closing bracket? RET Z ; Return if it was not. ;The repeat was caused by a single closing bracket so re-initialise the repeat LD (IX+$16),$00 ; Restore one level of closing bracket nesting. XOR A ; Select closing bracket nesting level 0. JR L0C0B ; Jump ahead to continue. ;A new level of closing bracket nesting L0BF0: LD A,(IX+$16) ; Fetch the nesting level of closing brackets. INC A ; Increment the count. CP $05 ; Only 5 levels supported (4 to match up with opening brackets and a 5th to repeat indefinitely). JP Z,L0F2A ; Jump if this is the fifth to produce error report "d Too many brackets". LD (IX+$16),A ; Store the new closing bracket nesting level. CALL L0C27 ; HL=Address of the pointer to the appropriate closing bracket return address store. LD A,(IX+$06) ; Store the current string position as the return address for the closing bracket. LD (HL),A ; INC HL ; LD A,(IX+$07) ; LD (HL),A ; LD A,(IX+$0B) ; Fetch the nesting level of opening brackets. L0C0B: LD DE,$000C ; CALL L0C27 ; HL=Address of the pointer to the opening bracket nesting level return address store. LD A,(HL) ; Set the return address of the nesting level's opening bracket LD (IX+$06),A ; as new current position within the string. INC HL ; LD A,(HL) ; For a single closing bracket only, this will be the start address of the string. LD (IX+$07),A ; DEC (IX+$0B) ; Decrement level of open bracket nesting. RET P ; Return if the closing bracket matched an open bracket. ;There is one more closing bracket then opening brackets, i.e. repeat string indefinitely LD (IX+$0B),$00 ; Set the opening brackets nesting level to 0. SET 0,(IX+$0A) ; Signal a single closing bracket only, i.e. to repeat the string indefinitely. RET ; ; ------------------------------------ ; Get Address of Bracket Pointer Store ; ------------------------------------ ; Entry: IX=Address of the channel data block. ; DE=Offset to the bracket pointer stores. ; A=Index into the bracket pointer stores. ; Exit : HL=Address of the specified pointer store. L0C27: PUSH IX ; POP HL ; HL=IX. ADD HL,DE ; HL=IX+DE. LD B,$00 ; LD C,A ; SLA C ; ADD HL,BC ; HL=IX+DE+2*A. RET ; ; ------------------------ ; Play Command 'T' (Tempo) ; ------------------------ ; A temp command must be specified in the first play string and is followed by a numeric ; value in the range 60 to 240 representing the number of beats (crotchets) per minute. ; Entry: IX=Address of the channel data block. L0C32: CALL L0B1D ; Get following numeric value from the string into BC. LD A,B ; OR A ; JP NZ,L0F12 ; Jump if 256 or above to produce error report "n Out of range". LD A,C ; CP $3C ; JP C,L0F12 ; Jump if 59 or below to produce error report "n Out of range". CP $F1 ; JP NC,L0F12 ; Jump if 241 or above to produce error report "n Out of range". ;A holds a value in the range 60 to 240 LD A,(IX+$02) ; Fetch the channel number. OR A ; Tempo 'T' commands have to be specified in the first string. RET NZ ; If it is in a later string then ignore it. LD B,$00 ; [Redundant instruction - B is already zero] PUSH BC ; C=Tempo value. POP HL ; ADD HL,HL ; ADD HL,HL ; HL=Tempo*4. PUSH HL ; POP BC ; BC=Tempo*4. [Would have been quicker to use the combination LD B,H and LD C,L] PUSH IY ; Save the pointer to the play command data block. RST 28H ; DEFW STACK_BC ; $2D2B. Place the contents of BC onto the stack. The call restores IY to $5C3A. DI ; Interrupts get re-enabled by the call mechanism to ROM 1 so disable them again. POP IY ; Restore IY to point at the play command data block. PUSH IY ; Save the pointer to the play command data block. PUSH IY ; POP HL ; HL=pointer to the play command data block. LD BC,$002B ; ADD HL,BC ; HL =IY+$002B. LD IY,$5C3A ; Reset IY to $5C3A since this is required by the floating point calculator. PUSH HL ; HL=Points to the calculator RAM routine. LD HL,L0C76 ; LD (RETADDR),HL ; $5B5A. Set up the return address. LD HL,YOUNGER ; EX (SP),HL ; Stack the address of the swap routine used when returning to this ROM. PUSH HL ; Re-stack the address of the calculator RAM routine. JP SWAP ; $5B00. Toggle to other ROM and make a return to the calculator RAM routine. ; -------------------- ; Tempo Command Return ; -------------------- ; The calculator stack now holds the value (10/(Tempo*4))/7.33e-6 and this is stored as the tempo value. ; The result is used an inner loop counter in the wait routine at $0F76 (ROM 0). Each iteration of this loop ; takes 26 T-states. The time taken by 26 T-states is 7.33e-6 seconds. So the total time for the loop ; to execute is 2.5/TEMPO seconds. L0C76: DI ; Interrupts get re-enabled by the call mechanism to ROM 1 so disable them again. RST 28H ; DEFW FP_TO_BC ; $2DA2. Fetch the value on the top of the calculator stack. DI ; Interrupts get re-enabled by the call mechanism to ROM 1 so disable them again. POP IY ; Restore IY to point at the play command data block. LD (IY+$27),C ; Store tempo timing value. LD (IY+$28),B ; RET ; ; ------------------------ ; Play Command 'M' (Mixer) ; ------------------------ ; This command is used to select whether to use tone and/or noise on each of the 3 channels. ; It is followed by a numeric value in the range 1 to 63, although due to loose range checking the ; value MOD 256 only needs to be within 0 to 63. Hence M256 operates the same as M0. ; Entry: IX=Address of the channel data block. L0C84: CALL L0B1D ; Get following numeric value from the string into BC. LD A,C ; A=Mixer value. CP $40 ; Is it 64 or above? JP NC,L0F12 ; Jump if so to produce error report "n Out of range". ;Bit 0: 1=Enable channel A tone. ;Bit 1: 1=Enable channel B tone. ;Bit 2: 1=Enable channel C tone. ;Bit 3: 1=Enable channel A noise. ;Bit 4: 1=Enable channel B noise. ;Bit 5: 1=Enable channel C noise. CPL ; Invert the bits since the sound generator's mixer register uses active low enable. ; This also sets bit 6 1, which selects the I/O port as an output. LD E,A ; E=Mixer value. LD D,$07 ; D=Register 7 - Mixer. CALL L0E7C ; Write to sound generator register to set the mixer. RET ; [Could have saved 1 byte by using JP L0E7C (ROM 0)] ; ------------------------- ; Play Command 'V' (Volume) ; ------------------------- ; This sets the volume of a channel and is followed by a numeric value in the range ; 0 (minimum) to 15 (maximum), although due to loose range checking the value MOD 256 ; only needs to be within 0 to 15. Hence V256 operates the same as V0. ; Entry: IX=Address of the channel data block. L0C95: CALL L0B1D ; Get following numeric value from the string into BC. LD A,C ; CP $10 ; Is it 16 or above? JP NC,L0F12 ; Jump if so to produce error report "n Out of range". LD (IX+$04),A ; Store the volume level. ; [*BUG* - An attempt to set the volume for a sound chip channel is now made. However, this routine fails to take into account ; that it is also called to set the volume for a MIDI only channel, i.e. play strings 4 to 8. As a result, corruption ; occurs to various sound generator registers, causing spurious sound output. There is in fact no need for this routine ; to set the volume for any channels since this is done every time a new note is played - see routine at $0A97 (ROM 0). ; the bug fix is to simply to make a return at this point. This routine therefore contains 11 surplus bytes. Credit: Ian Collier (+3), Paul Farrow (128)] LD E,(IX+$02) ; E=Channel number. LD A,$08 ; Offset by 8. ADD A,E ; A=8+index. LD D,A ; D=Sound generator register number for the channel. LD E,C ; E=Volume level. CALL L0E7C ; Write to sound generator register to set the volume for the channel. RET ; [Could have saved 1 byte by using JP L0E7C (ROM 0)] ; ------------------------------------ ; Play Command 'U' (Use Volume Effect) ; ------------------------------------ ; This command turns on envelope waveform effects for a particular sound chip channel. The volume level is now controlled by ; the selected envelope waveform for the channel, as defined by the 'W' command. MIDI channels do not support envelope waveforms ; and so the routine has the effect of setting the volume of a MIDI channel to maximum, i.e. 15. It might seem odd that the volume ; for MIDI channels is set to 15 rather than just filtered out. However, the three sound chip channels can also drive three MIDI ; channels and so it would be inconsistent for these MIDI channels to have their volume set to 15 but have the other MIDI channels ; behave differently. However, it could be argued that all MIDI channels should be unaffected by the 'U' command. ; There are no parameters to this command. ; Entry: IX=Address of the channel data block. L0CAD: LD E,(IX+$02) ; Get the channel number. LD A,$08 ; Offset by 8. ADD A,E ; A=8+index. LD D,A ; D=Sound generator register number for the channel. [This is not used and so there is no need to generate it. It was probably a left ; over from copying and modifying the 'V' command routine. Deleting it would save 7 bytes. Credit: Ian Collier (+3), Paul Farrow (128)] LD E,$1F ; E=Select envelope defined by register 13, and reset volume bits to maximum (though these are not used with the envelope). LD (IX+$04),E ; Store that the envelope is being used (along with the reset volume level). RET ; ; ------------------------------------------ ; Play command 'W' (Volume Effect Specifier) ; ------------------------------------------ ; This command selects the envelope waveform to use and is followed by a numeric value in the range ; 0 to 7, although due to loose range checking the value MOD 256 only needs to be within 0 to 7. ; Hence W256 operates the same as W0. ; Entry: IX=Address of the channel data block. L0CBA: CALL L0B1D ; Get following numeric value from the string into BC. LD A,C ; CP $08 ; Is it 8 or above? JP NC,L0F12 ; Jump if so to produce error report "n Out of range". LD B,$00 ; LD HL,L0DE8 ; Envelope waveform lookup table. ADD HL,BC ; HL points to the corresponding value in the table. LD A,(HL) ; LD (IY+$29),A ; Store new effect waveform value. RET ; ; ----------------------------------------- ; Play Command 'X' (Volume Effect Duration) ; ----------------------------------------- ; This command allows the duration of a waveform effect to be specified, and is followed by a numeric ; value in the range 0 to 65535. A value of 1 corresponds to the minimum duration, increasing up to 65535 ; and then maximum duration for a value of 0. If no numeric value is specified then the maximum duration is used. ; Entry: IX=Address of the channel data block. L0CCE: CALL L0B1D ; Get following numeric value from the string into BC. LD D,$0B ; Register 11 - Envelope Period Fine. LD E,C ; CALL L0E7C ; Write to sound generator register to set the envelope period (low byte). INC D ; Register 12 - Envelope Period Coarse. LD E,B ; CALL L0E7C ; Write to sound generator register to set the envelope period (high byte). RET ; [Could have saved 1 byte by using JP L0E7C (ROM 0)] ; ------------------------------- ; Play Command 'Y' (MIDI Channel) ; ------------------------------- ; This command sets the MIDI channel number that the string is assigned to and is followed by a numeric ; value in the range 1 to 16, although due to loose range checking the value MOD 256 only needs to be within 1 to 16. ; Hence Y257 operates the same as Y1. ; Entry: IX=Address of the channel data block. L0CDD: CALL L0B1D ; Get following numeric value from the string into BC. LD A,C ; DEC A ; Is it 0? JP M,L0F12 ; Jump if so to produce error report "n Out of range". CP $10 ; Is it 10 or above? JP NC,L0F12 ; Jump if so to produce error report "n Out of range". LD (IX+$01),A ; Store MIDI channel number that this string is assigned to. RET ; ; ---------------------------------------- ; Play Command 'Z' (MIDI Programming Code) ; ---------------------------------------- ; This command is used to send a programming code to the MIDI port. It is followed by a numeric ; value in the range 0 to 255, although due to loose range checking the value MOD 256 only needs ; to be within 0 to 255. Hence Z256 operates the same as Z0. ; Entry: IX=Address of the channel data block. L0CEE: CALL L0B1D ; Get following numeric value from the string into BC. LD A,C ; A=(low byte of) the value. CALL L11A3 ; Write byte to MIDI device. RET ; [Could have saved 1 byte by using JP L0E7C (ROM 0)] ; ----------------------- ; Play Command 'H' (Stop) ; ----------------------- ; This command stops further processing of a play command. It has no parameters. ; Entry: IX=Address of the channel data block. L0CF6: LD (IY+$10),$FF ; Indicate no channels to play, thereby causing RET ; the play command to terminate. ; -------------------------------------------------------- ; Play Commands 'a'..'g', 'A'..'G', '1'.."12", '&' and '_' ; -------------------------------------------------------- ; This handler routine processes commands 'a'..'g', 'A'..'G', '1'.."12", '&' and '_', ; and determines the length of the next note to play. It provides the handling of triplet and tied notes. ; It stores the note duration in the channel data block's duration length entry, and sets a pointer in the command ; data block's duration lengths pointer table to point at it. A single note letter is deemed to be a tied ; note count of 1. Triplets are deemed a tied note count of at least 2. ; Entry: IX=Address of the channel data block. ; A=Current character from play string. L0CFB: CALL L0E19 ; Is the current character a number? JP C,L0D81 ; Jump if not number digit. ;The character is a number digit CALL L0DAC ; HL=Address of the duration length within the channel data block. CALL L0DB4 ; Store address of duration length in command data block's channel duration length pointer table. XOR A ; LD (IX+$21),A ; Set no tied notes. CALL L0EC8 ; Get the previous character in the string, the note duration. CALL L0B1D ; Get following numeric value from the string into BC. LD A,C ; OR A ; Is the value 0? JP Z,L0F12 ; Jump if so to produce error report "n Out of range". CP $0D ; Is it 13 or above? JP NC,L0F12 ; Jump if so to produce error report "n Out of range". CP $0A ; Is it below 10? JR C,L0D32 ; Jump if so. ;It is a triplet semi-quaver (10), triplet quaver (11) or triplet crotchet (12) CALL L0E00 ; DE=Note duration length for the duration value. CALL L0D74 ; Increment the tied notes counter. LD (HL),E ; HL=Address of the duration length within the channel data block. INC HL ; LD (HL),D ; Store the duration length. L0D28: CALL L0D74 ; Increment the counter of tied notes. INC HL ; LD (HL),E ; INC HL ; Store the subsequent note duration length in the channel data block. LD (HL),D ; INC HL ; JR L0D38 ; Jump ahead to continue. ;The note duration was in the range 1 to 9 L0D32: LD (IX+$05),C ; C=Note duration value (1..9). CALL L0E00 ; DE=Duration length for this duration value. L0D38: CALL L0D74 ; Increment the tied notes counter. L0D3B: CALL L0EE3 ; Get the current character from the play string for this channel. CP '_' ; $5F. Is it a tied note? JR NZ,L0D6E ; Jump ahead if not. CALL L0AC5 ; Get the current character from the PLAY string, and advance the position pointer. CALL L0B1D ; Get following numeric value from the string into BC. LD A,C ; Place the value into A. CP $0A ; Is it below 10? JR C,L0D5F ; Jump ahead for 1 to 9 (semiquaver ... semibreve). ;A triplet note was found as part of a tied note PUSH HL ; HL=Address of the duration length within the channel data block. PUSH DE ; DE=First tied note duration length. CALL L0E00 ; DE=Note duration length for this new duration value. POP HL ; HL=Current tied note duration length. ADD HL,DE ; HL=Current+new tied note duration lengths. LD C,E ; LD B,D ; BC=Note duration length for the duration value. EX DE,HL ; DE=Current+new tied note duration lengths. POP HL ; HL=Address of the duration length within the channel data block. LD (HL),E ; INC HL ; LD (HL),D ; Store the combined note duration length in the channel data block. LD E,C ; LD D,B ; DE=Note duration length for the second duration value. JR L0D28 ; Jump back. ;A non-triplet tied note L0D5F: LD (IX+$05),C ; Store the note duration value. PUSH HL ; HL=Address of the duration length within the channel data block. PUSH DE ; DE=First tied note duration length. CALL L0E00 ; DE=Note duration length for this new duration value. POP HL ; HL=Current tied note duration length. ADD HL,DE ; HL=Current+new tied not duration lengths. EX DE,HL ; DE=Current+new tied not duration lengths. POP HL ; HL=Address of the duration length within the channel data block. JP L0D3B ; Jump back to process the next character in case it is also part of a tied note. ;The number found was not part of a tied note, so store the duration value L0D6E: LD (HL),E ; HL=Address of the duration length within the channel data block. INC HL ; (For triplet notes this could be the address of the subsequent note duration length) LD (HL),D ; Store the duration length. JP L0D9C ; Jump forward to make a return. ; This subroutine is called to increment the tied notes counter L0D74: LD A,(IX+$21) ; Increment counter of tied notes. INC A ; CP $0B ; Has it reached 11? JP Z,L0F3A ; Jump if so to produce to error report "o too many tied notes". LD (IX+$21),A ; Store the new tied notes counter. RET ; ;The character is not a number digit so is 'A'..'G', '&' or '_' L0D81: CALL L0EC8 ; Get the previous character from the string. LD (IX+$21),$01 ; Set the number of tied notes to 1. ;Store a pointer to the channel data block's duration length into the command data block CALL L0DAC ; HL=Address of the duration length within the channel data block. CALL L0DB4 ; Store address of duration length in command data block's channel duration length pointer table. LD C,(IX+$05) ; C=The duration value of the note (1 to 9). PUSH HL ; [Not necessary] CALL L0E00 ; Find the duration length for the note duration value. POP HL ; [Not necessary] LD (HL),E ; Store it in the channel data block. INC HL ; LD (HL),D ; JP L0D9C ; Jump to the instruction below. [Redundant instruction] L0D9C: POP HL ; INC HL ; INC HL ; Modify the return address to point to the RET instruction at $0B83 (ROM 0). PUSH HL ; RET ; [Over elaborate when a simple POP followed by RET would have sufficed, saving 3 bytes] ; ------------------- ; End of String Found ; ------------------- ;This routine is called when the end of string is found within a comment. It marks the ;string as having been processed and then returns to the main loop to process the next string. L0DA1: POP HL ; Drop the return address of the call to the comment command. ;Enter here if the end of the string is found whilst processing a string. L0DA2: LD A,(IY+$21) ; Fetch the channel selector. OR (IY+$10) ; Clear the channel flag for this string. LD (IY+$10),A ; Store the new channel bitmap. RET ; ; -------------------------------------------------- ; Point to Duration Length within Channel Data Block ; -------------------------------------------------- ; Entry: IX=Address of the channel data block. ; Exit : HL=Address of the duration length within the channel data block. L0DAC: PUSH IX ; POP HL ; HL=Address of the channel data block. LD BC,$0022 ; ADD HL,BC ; HL=Address of the store for the duration length. RET ; ; ------------------------------------------------------------------------- ; Store Entry in Command Data Block's Channel Duration Length Pointer Table ; ------------------------------------------------------------------------- ; Entry: IY=Address of the command data block. ; IX=Address of the channel data block for the current string. ; HL=Address of the duration length store within the channel data block. ; Exit : HL=Address of the duration length store within the channel data block. ; DE=Channel duration. L0DB4: PUSH HL ; Save the address of the duration length within the channel data block. PUSH IY ; POP HL ; HL=Address of the command data block. LD BC,$0011 ; ADD HL,BC ; HL=Address within the command data block of the channel duration length pointer table. LD B,$00 ; LD C,(IX+$02) ; BC=Channel number. SLA C ; BC=2*Index number. ADD HL,BC ; HL=Address within the command data block of the pointer to the current channel's data block duration length. POP DE ; DE=Address of the duration length within the channel data block. LD (HL),E ; Store the pointer to the channel duration length in the command data block's channel duration pointer table. INC HL ; LD (HL),D ; EX DE,HL ; RET ; ; ----------------------- ; PLAY Command Jump Table ; ----------------------- ; Handler routine jump table for all PLAY commands. L0DCA: DEFW L0CFB ; Command handler routine for all other characters. DEFW L0B85 ; '!' command handler routine. DEFW L0B90 ; 'O' command handler routine. DEFW L0BA5 ; 'N' command handler routine. DEFW L0BA6 ; '(' command handler routine. DEFW L0BC2 ; ')' command handler routine. DEFW L0C32 ; 'T' command handler routine. DEFW L0C84 ; 'M' command handler routine. DEFW L0C95 ; 'V' command handler routine. DEFW L0CAD ; 'U' command handler routine. DEFW L0CBA ; 'W' command handler routine. DEFW L0CCE ; 'X' command handler routine. DEFW L0CDD ; 'Y' command handler routine. DEFW L0CEE ; 'Z' command handler routine. DEFW L0CF6 ; 'H' command handler routine. ; ------------------------------ ; Envelope Waveform Lookup Table ; ------------------------------ ; Table used by the play 'W' command to find the corresponding envelope value ; to write to the sound generator envelope shape register (register 13). This ; filters out the two duplicate waveforms possible from the sound generator and ; allows the order of the waveforms to be arranged in a more logical fashion. L0DE8: DEFB $00 ; W0 - Single decay then off. (Continue off, attack off, alternate off, hold off) DEFB $04 ; W1 - Single attack then off. (Continue off, attack on, alternate off, hold off) DEFB $0B ; W2 - Single decay then hold. (Continue on, attack off, alternate on, hold on) DEFB $0D ; W3 - Single attack then hold. (Continue on, attack on, alternate off, hold on) DEFB $08 ; W4 - Repeated decay. (Continue on, attack off, alternate off, hold off) DEFB $0C ; W5 - Repeated attack. (Continue on, attack on, alternate off, hold off) DEFB $0E ; W6 - Repeated attack-decay. (Continue on, attack on, alternate on, hold off) DEFB $0A ; W7 - Repeated decay-attack. (Continue on, attack off, alternate on, hold off) ; -------------------------- ; Identify Command Character ; -------------------------- ; This routines attempts to match the command character to those in a table. ; The index position of the match indicates which command handler routine is required ; to process the character. Note that commands are case sensitive. ; Entry: A=Command character. ; Exit : Zero flag set if a match was found. ; BC=Indentifying the character matched, 1 to 15 for match and 0 for no match. L0DF0: LD BC,$000F ; Number of characters + 1 in command table. LD HL,L0AB7 ; Start of command table. CPIR ; Search for a match. RET ; ; --------------- ; Semitones Table ; --------------- ; This table contains an entry for each note of the scale, A to G, ; and is the number of semitones above the note C. L0DF9: DEFB $09 ; 'A' DEFB $0B ; 'B' DEFB $00 ; 'C' DEFB $02 ; 'D' DEFB $04 ; 'E' DEFB $05 ; 'F' DEFB $07 ; 'G' ; ------------------------- ; Find Note Duration Length ; ------------------------- ; Entry: C=Duration value (0 to 12, although a value of 0 is never used). ; Exit : DE=Note duration length. L0E00: PUSH HL ; Save HL. LD B,$00 ; LD HL,L0E0C ; Note duration table. ADD HL,BC ; Index into the table. LD D,$00 ; LD E,(HL) ; Fetch the length from the table. POP HL ; Restore HL. RET ; ; ------------------- ; Note Duration Table ; ------------------- ; A whole note is given by a value of 96d and other notes defined in relation to this. ; The value of 96d is the lowest common denominator from which all note durations ; can be defined. L0E0C: DEFB $80 ; Rest [Not used since table is always indexed into with a value of 1 or more] DEFB $06 ; Semi-quaver (sixteenth note). DEFB $09 ; Dotted semi-quaver (3/32th note). DEFB $0C ; Quaver (eighth note). DEFB $12 ; Dotted quaver (3/16th note). DEFB $18 ; Crotchet (quarter note). DEFB $24 ; Dotted crotchet (3/8th note). DEFB $30 ; Minim (half note). DEFB $48 ; Dotted minim (3/4th note). DEFB $60 ; Semi-breve (whole note). DEFB $04 ; Triplet semi-quaver (1/24th note). DEFB $08 ; Triplet quaver (1/12th note). DEFB $10 ; Triplet crochet (1/6th note). ; ----------------- ; Is Numeric Digit? ; ----------------- ; Tests whether a character is a number digit. ; Entry: A=Character. ; Exit : Carry flag reset if a number digit. L0E19: CP '0' ; $30. Is it '0' or less? RET C ; Return with carry flag set if so. CP ':' ; $3A. Is it more than '9'? CCF ; RET ; Return with carry flag set if so. ; ----------------------------------- ; Play a Note On a Sound Chip Channel ; ----------------------------------- ; This routine plays the note at the current octave and current volume on a sound chip channel. For play strings 4 to 8, ; it simply stores the note number and this is subsequently played later. ; Entry: IX=Address of the channel data block. ; A=Note value as number of semitones above C (0..11). L0E20: LD C,A ; C=The note value. LD A,(IX+$03) ; Octave number * 12. ADD A,C ; Add the octave number and the note value to form the note number. CP $80 ; Is note within range? JP NC,L0F32 ; Jump if not to produce error report "m Note out of range". LD C,A ; C=Note number. LD A,(IX+$02) ; Get the channel number. OR A ; Is it the first channel? JR NZ,L0E3F ; Jump ahead if not. ;Only set the noise generator frequency on the first channel LD A,C ; A=Note number (0..107), in ascending audio frequency. CPL ; Invert since noise register value is in descending audio frequency. AND $7F ; Mask off bit 7. SRL A ; SRL A ; Divide by 4 to reduce range to 0..31. LD D,$06 ; Register 6 - Noise pitch. LD E,A ; CALL L0E7C ; Write to sound generator register. L0E3F: LD (IX+$00),C ; Store the note number. LD A,(IX+$02) ; Get the channel number. CP $03 ; Is it channel 0, 1 or 2, i.e. a sound chip channel? RET NC ; Do not output anything for play strings 4 to 8. ;Channel 0, 1 or 2 LD HL,L1096 ; Start of note lookup table. LD B,$00 ; BC=Note number. LD A,C ; A=Note number. SUB $15 ; A=Note number - 21. JR NC,L0E57 ; Jump if note number was 21 or above. LD DE,$0FBF ; Note numbers $00 to $14 use the lowest note value. JR L0E5E ; [Could have saved 4 bytes by using XOR A and dropping through to L0E57 (ROM 0)] ;Note number 21 to 107 (range 0 to 86) L0E57: LD C,A ; SLA C ; Generate offset into the table. ADD HL,BC ; Point to the entry in the table. LD E,(HL) ; INC HL ; LD D,(HL) ; DE=Word to write to the sound chip registers to produce this note. L0E5E: EX DE,HL ; HL=Register word value to produce the note. LD D,(IX+$02) ; Get the channel number. SLA D ; D=2*Channel number, to give the tone channel register (fine control) number 0, 2, or 4. LD E,L ; E=The low value byte. CALL L0E7C ; Write to sound generator register. INC D ; D=Tone channel register (coarse control) number 1, 3, or 5. LD E,H ; E=The high value byte. CALL L0E7C ; Write to sound generator register. BIT 4,(IX+$04) ; Is the envelope waveform being used? RET Z ; Return if it is not. LD D,$0D ; Register 13 - Envelope Shape. LD A,(IY+$29) ; Get the effect waveform value. LD E,A ; CALL L0E7C ; Write to sound generator register. RET ; [Could have saved 4 bytes by dropping down into the routine below.] ; ---------------------------- ; Set Sound Generator Register ; ---------------------------- ; Entry: D=Register to write. ; E=Value to set register to. L0E7C: PUSH BC ; LD BC,$FFFD ; OUT (C),D ; Select the register. LD BC,$BFFD ; OUT (C),E ; Write out the value. POP BC ; RET ; ; ----------------------------- ; Read Sound Generator Register ; ----------------------------- ; Entry: A=Register to read. ; Exit : A=Value of currently selected sound generator register. L0E89: PUSH BC ; LD BC,$FFFD ; OUT (C),A ; Select the register. IN A,(C) ; Read the register's value. POP BC ; RET ; ; ------------------ ; Turn Off All Sound ; ------------------ L0E93: LD D,$07 ; Register 7 - Mixer. LD E,$FF ; I/O ports are inputs, noise output off, tone output off. CALL L0E7C ; Write to sound generator register. ;Turn off the sound from the AY-3-8912 LD D,$08 ; Register 8 - Channel A volume. LD E,$00 ; Volume of 0. CALL L0E7C ; Write to sound generator register to set the volume to 0. INC D ; Register 9 - Channel B volume. CALL L0E7C ; Write to sound generator register to set the volume to 0. INC D ; Register 10 - Channel C volume. CALL L0E7C ; Write to sound generator register to set the volume to 0. CALL L0A4F ; Select channel data block pointers. ;Now reset all MIDI channels in use L0EAC: RR (IY+$22) ; Working copy of channel bitmap. Test if next string present. JR C,L0EB8 ; Jump ahead if there is no string for this channel. CALL L0A67 ; Get address of channel data block for the current string into IX. CALL L118D ; Turn off the MIDI channel sound assigned to this play string. L0EB8: SLA (IY+$21) ; Have all channels been processed? JR C,L0EC3 ; Jump ahead if so. CALL L0A6E ; Advance to the next channel data block pointer. JR L0EAC ; Jump back to process the next channel. L0EC3: LD IY,$5C3A ; Restore IY. RET ; ; --------------------------------------- ; Get Previous Character from Play String ; --------------------------------------- ; Get the previous character from the PLAY string, skipping over spaces and 'Enter' characters. ; Entry: IX=Address of the channel data block. L0EC8: PUSH HL ; Save registers. PUSH DE ; LD L,(IX+$06) ; Get the current pointer into the PLAY string. LD H,(IX+$07) ; L0ED0: DEC HL ; Point to previous character. LD A,(HL) ; Fetch the character. CP ' ' ; $20. Is it a space? JR Z,L0ED0 ; Jump back if a space. CP $0D ; Is it an 'Enter'? JR Z,L0ED0 ; Jump back if an 'Enter'. LD (IX+$06),L ; Store this as the new current pointer into the PLAY string. LD (IX+$07),H ; POP DE ; Restore registers. POP HL ; RET ; ; -------------------------------------- ; Get Current Character from Play String ; -------------------------------------- ; Get the current character from the PLAY string, skipping over spaces and 'Enter' characters. ; Exit: Carry flag set if string has been fully processed. ; Carry flag reset if character is available. ; A=Character available. L0EE3: PUSH HL ; Save registers. PUSH DE ; PUSH BC ; LD L,(IX+$06) ; HL=Pointer to next character to process within the PLAY string. LD H,(IX+$07) ; L0EEC: LD A,H ; CP (IX+$09) ; Reached end-of-string address high byte? JR NZ,L0EFB ; Jump forward if not. LD A,L ; CP (IX+$08) ; Reached end-of-string address low byte? JR NZ,L0EFB ; Jump forward if not. SCF ; Indicate string all processed. JR L0F05 ; Jump forward to return. L0EFB: LD A,(HL) ; Get the next play character. CP ' ' ; $20. Is it a space? JR Z,L0F09 ; Ignore the space by jumping ahead to process the next character. CP $0D ; Is it 'Enter'? JR Z,L0F09 ; Ignore the 'Enter' by jumping ahead to process the next character. OR A ; Clear the carry flag to indicate a new character has been returned. L0F05: POP BC ; Restore registers. POP DE ; POP HL ; RET ; L0F09: INC HL ; Point to the next character. LD (IX+$06),L ; LD (IX+$07),H ; Update the pointer to the next character to process with the PLAY string. JR L0EEC ; Jump back to get the next character. ; -------------------------- ; Produce Play Error Reports ; -------------------------- L0F12: CALL L0E93 ; Turn off all sound and restore IY. EI ; CALL L05AC ; Produce error report. DEFB $29 ; "n Out of range" L0F1A: CALL L0E93 ; Turn off all sound and restore IY. EI ; CALL L05AC ; Produce error report. DEFB $27 ; "l Number too big" L0F22: CALL L0E93 ; Turn off all sound and restore IY. EI ; CALL L05AC ; Produce error report. DEFB $26 ; "k Invalid note name" L0F2A: CALL L0E93 ; Turn off all sound and restore IY. EI ; CALL L05AC ; Produce error report. DEFB $1F ; "d Too many brackets" L0F32: CALL L0E93 ; Turn off all sound and restore IY. EI ; CALL L05AC ; Produce error report. DEFB $28 ; "m Note out of range" L0F3A: CALL L0E93 ; Turn off all sound and restore IY. EI ; CALL L05AC ; Produce error report. DEFB $2A ; "o Too many tied notes" ; ------------------------- ; Play Note on Each Channel ; ------------------------- ; Play a note and set the volume on each channel for which a play string exists. L0F42: CALL L0A4F ; Select channel data block pointers. L0F45: RR (IY+$22) ; Working copy of channel bitmap. Test if next string present. JR C,L0F6C ; Jump ahead if there is no string for this channel. CALL L0A67 ; Get address of channel data block for the current string into IX. CALL L0AD1 ; Get the next note in the string as number of semitones above note C. CP $80 ; Is it a rest? JR Z,L0F6C ; Jump ahead if so and do nothing to the channel. CALL L0E20 ; Play the note if a sound chip channel. LD A,(IX+$02) ; Get channel number. CP $03 ; Is it channel 0, 1 or 2, i.e. a sound chip channel? JR NC,L0F69 ; Jump if not to skip setting the volume. ;One of the 3 sound chip generator channels so set the channel's volume for the new note LD D,$08 ; ADD A,D ; A=0 to 2. LD D,A ; D=Register (8 + string index), i.e. channel A, B or C volume register. LD E,(IX+$04) ; E=Volume for the current channel. CALL L0E7C ; Write to sound generator register to set the output volume. L0F69: CALL L116E ; Play a note and set the volume on the assigned MIDI channel. L0F6C: SLA (IY+$21) ; Have all channels been processed? RET C ; Return if so. CALL L0A6E ; Advance to the next channel data block pointer. JR L0F45 ; Jump back to process the next channel. ; ------------------ ; Wait Note Duration ; ------------------ ; This routine is the main timing control of the PLAY command. ; It waits for the specified length of time, which will be the ; lowest note duration of all active channels. ; The actual duration of the wait is dictated by the current tempo. ; Entry: DE=Note duration, where 96d represents a whole note. ;Enter a loop waiting for (135+ ((26*(tempo-100))-5) )*DE+5 T-states L0F76: PUSH HL ; (11) Save HL. LD L,(IY+$27) ; (19) Get the tempo timing value. LD H,(IY+$28) ; (19) LD BC,$0064 ; (10) BC=100 OR A ; (4) SBC HL,BC ; (15) HL=tempo timing value - 100. PUSH HL ; (11) POP BC ; (10) BC=tempo timing value - 100. POP HL ; (10) Restore HL. ;Tempo timing value = (10/(TEMPO*4))/7.33e-6, where 7.33e-6 is the time for 26 T-states. ;The loop below takes 26 T-states per iteration, where the number of iterations is given by the tempo timing value. ;So the time for the loop to execute is 2.5/TEMPO seconds. ; ;For a TEMPO of 60 beats (crotchets) per second, the time per crotchet is 1/24 second. ;The duration of a crotchet is defined as 24 from the table at $0E0C, therefore the loop will get executed 24 times ;and hence the total time taken will be 1 second. ; ;The tempo timing value above has 100 subtracted from it, presumably to approximately compensate for the overhead time ;previously taken to prepare the notes for playing. This reduces the total time by 2600 T-states, or 733us. L0F86: DEC BC ; (6) Wait for tempo-100 loops. LD A,B ; (4) OR C ; (4) JR NZ,L0F86 ; (12/7) DEC DE ; (6) Repeat DE times LD A,D ; (4) OR E ; (4) JR NZ,L0F76 ; (12/7) RET ; (10) ; ----------------------------- ; Find Smallest Duration Length ; ----------------------------- ; This routine finds the smallest duration length for all current notes ; being played across all channels. ; Exit: DE=Smallest duration length. L0F91: LD DE,$FFFF ; Set smallest duration length to 'maximum'. CALL L0A4A ; Select channel data block duration pointers. L0F97: RR (IY+$22) ; Working copy of channel bitmap. Test if next string present. JR C,L0FAF ; Jump ahead if there is no string for this channel. ;HL=Address of channel data pointer. DE holds the smallest duration length found so far. PUSH DE ; Save the smallest duration length. LD E,(HL) ; INC HL ; LD D,(HL) ; EX DE,HL ; DE=Channel data block duration length. LD E,(HL) ; INC HL ; LD D,(HL) ; DE=Channel duration length. PUSH DE ; POP HL ; HL=Channel duration length. POP BC ; Last channel duration length. OR A ; SBC HL,BC ; Is current channel's duration length smaller than the smallest so far? JR C,L0FAF ; Jump ahead if so, with the new smallest value in DE. ;The current channel's duration was not smaller so restore the last smallest into DE. PUSH BC ; POP DE ; DE=Smallest duration length. L0FAF: SLA (IY+$21) ; Have all channel strings been processed? JR C,L0FBA ; Jump ahead if so. CALL L0A6E ; Advance to the next channel data block duration pointer. JR L0F97 ; Jump back to process the next channel. L0FBA: LD (IY+$25),E ; LD (IY+$26),D ; Store the smallest channel duration length. RET ; ; --------------------------------------------------------------- ; Play a Note on Each Channel and Update Channel Duration Lengths ; --------------------------------------------------------------- ; This routine is used to play a note and set the volume on all channels. ; It subtracts an amount of time from the duration lengths of all currently ; playing channel note durations. The amount subtracted is equivalent to the ; smallest note duration length currently being played, and as determined earlier. ; Hence one channel's duration will go to 0 on each call of this routine, and the ; others will show the remaining lengths of their corresponding notes. ; Entry: IY=Address of the command data block. L0FC1: XOR A ; LD (IY+$2A),A ; Holds a temporary channel bitmap. CALL L0A4F ; Select channel data block pointers. L0FC8: RR (IY+$22) ; Working copy of channel bitmap. Test if next string present. JP C,L105A ; Jump ahead if there is no string for this channel. CALL L0A67 ; Get address of channel data block for the current string into IX. PUSH IY ; POP HL ; HL=Address of the command data block. LD BC,$0011 ; ADD HL,BC ; HL=Address of channel data block duration pointers. LD B,$00 ; LD C,(IX+$02) ; BC=Channel number. SLA C ; BC=2*Channel number. ADD HL,BC ; HL=Address of channel data block duration pointer for this channel. LD E,(HL) ; INC HL ; LD D,(HL) ; DE=Address of duration length within the channel data block. EX DE,HL ; HL=Address of duration length within the channel data block. PUSH HL ; Save it. LD E,(HL) ; INC HL ; LD D,(HL) ; DE=Duration length for this channel. EX DE,HL ; HL=Duration length for this channel. LD E,(IY+$25) ; LD D,(IY+$26) ; DE=Smallest duration length of all current channel notes. OR A ; SBC HL,DE ; HL=Duration length - smallest duration length. EX DE,HL ; DE=Duration length - smallest duration length. POP HL ; HL=Address of duration length within the channel data block. JR Z,L0FFC ; Jump if this channel uses the smallest found duration length. LD (HL),E ; INC HL ; Update the duration length for this channel with the remaining length. LD (HL),D ; JR L105A ; Jump ahead to update the next channel. ;The current channel uses the smallest found duration length ; [A note has been completed and so the channel volume is set to 0 prior to the next note being played. ; This occurs on both sound chip channels and MIDI channels. When a MIDI channel is assigned to more than ; one play string and a rest is used in one of those strings. As soon as the end of the rest period is ; encountered, the channel's volume is set to off even though one of the other play strings controlling ; the MIDI channel may still be playing. This can be seen using the command PLAY "Y1a&", "Y1N9a". Here, ; string 1 starts playing 'a' for the period of a crotchet (1/4 of a note), where as string 2 starts playing ; 'a' for nine periods of a crotchet (9/4 of a note). When string 1 completes its crotchet, it requests ; to play a period of silence via the rest '&'. This turns the volume of the MIDI channel off even though ; string 2 is still timing its way through its nine crotchets. The play command will therefore continue for ; a further seven crotchets but in silence. This is because the volume for note is set only at its start ; and no coordination occurs between strings to turn the volume back on for the second string. It is arguably ; what the correct behaviour should be in such a circumstance where the strings are providing conflicting instructions, ; but having the latest command or note take precedence seems a logical approach. Credit: Ian Collier (+3), Paul Farrow (128)] L0FFC: LD A,(IX+$02) ; Get the channel number. CP $03 ; Is it channel 0, 1 or 2, i.e. a sound chip channel? JR NC,L100C ; Jump ahead if not a sound generator channel. LD D,$08 ; ADD A,D ; LD D,A ; D=Register (8+channel number) - Channel volume. LD E,$00 ; E=Volume level of 0. CALL L0E7C ; Write to sound generator register to turn the volume off. L100C: CALL L118D ; Turn off the assigned MIDI channel sound. PUSH IX ; POP HL ; HL=Address of channel data block. LD BC,$0021 ; ADD HL,BC ; HL=Points to the tied notes counter. DEC (HL) ; Decrement the tied notes counter. [This contains a value of 1 for a single note] JR NZ,L1026 ; Jump ahead if there are more tied notes. CALL L0B5C ; Find the next note to play for this channel from its play string. LD A,(IY+$21) ; Fetch the channel selector. AND (IY+$10) ; Test whether this channel has further data in its play string. JR NZ,L105A ; Jump to process the next channel if this channel does not have a play string. JR L103D ; The channel has more data in its play string so jump ahead. ;The channel has more tied notes L1026: PUSH IY ; POP HL ; HL=Address of the command data block. LD BC,$0011 ; ADD HL,BC ; HL=Address of channel data block duration pointers. LD B,$00 ; LD C,(IX+$02) ; BC=Channel number. SLA C ; BC=2*Channel number. ADD HL,BC ; HL=Address of channel data block duration pointer for this channel. LD E,(HL) ; INC HL ; LD D,(HL) ; DE=Address of duration length within the channel data block. INC DE ; INC DE ; Point to the subsequent note duration length. LD (HL),D ; DEC HL ; LD (HL),E ; Store the new duration length. L103D: CALL L0AD1 ; Get next note in the string as number of semitones above note C. LD C,A ; C=Number of semitones. LD A,(IY+$21) ; Fetch the channel selector. AND (IY+$10) ; Test whether this channel has a play string. JR NZ,L105A ; Jump to process the next channel if this channel does not have a play string. LD A,C ; A=Number of semitones. CP $80 ; Is it a rest? JR Z,L105A ; Jump to process the next channel if it is. CALL L0E20 ; Play the new note on this channel at the current volume if a sound chip channel, or simply store the note for play strings 4 to 8. LD A,(IY+$21) ; Fetch the channel selector. OR (IY+$2A) ; Insert a bit in the temporary channel bitmap to indicate this channel has more to play. LD (IY+$2A),A ; Store it. ;Check whether another channel needs its duration length updated L105A: SLA (IY+$21) ; Have all channel strings been processed? JR C,L1066 ; Jump ahead if so. CALL L0A6E ; Advance to the next channel data pointer. JP L0FC8 ; Jump back to update the duration length for the next channel. ; [*BUG* - By this point, the volume for both sound chip and MIDI channels has been set to 0, i.e. off. So although the new notes have been ; set playing on the sound chip channels, no sound is audible. For MIDI channels, no new notes have yet been output and hence these ; are also silent. If the time from turning the volume off for the current note to the time to turn the volume on for the next note ; is short enough, then it will not be noticeable. However, the code at $1066 (ROM 0) introduces a 1/96th of a note delay and as a result a ; 1/96th of a note period of silence between notes. The bug can be resolved by simply deleting the two instructions below that introduce ; the delay. A positive side effect of the bug in the 'V' volume command at $0C95 (ROM 0) is that it can be used to overcome the gaps of silence ; between notes for sound chip channels. By interspersing volume commands between notes, a new volume level is immediately set before ; the 1/96th of a note delay is introduced for the new note. Therefore, the delay occurs when the new note is audible instead of when it ; is silent. For example, PLAY "cV15cV15c" instead of PLAY "ccc". The note durations are still 1/96th of a note longer than they should ; be though. This technique will only work on the sound chip channels and not for any MIDI channels. Credit: Ian Collier (+3), Paul Farrow (128)] L1066: LD DE,$0001 ; Delay for 1/96th of a note. CALL L0F76 ; CALL L0A4F ; Select channel data block pointers. ;All channel durations have been updated. Update the volume on each sound chip channel, and the volume and note on each MIDI channel L106F: RR (IY+$2A) ; Temporary channel bitmap. Test if next string present. JR NC,L108C ; Jump ahead if there is no string for this channel. CALL L0A67 ; Get address of channel data block for the current string into IX. LD A,(IX+$02) ; Get the channel number. CP $03 ; Is it channel 0, 1 or 2, i.e. a sound chip channel? JR NC,L1089 ; Jump ahead if so to process the next channel. LD D,$08 ; ADD A,D ; LD D,A ; D=Register (8+channel number) - Channel volume. LD E,(IX+$04) ; Get the current volume. CALL L0E7C ; Write to sound generator register to set the volume of the channel. L1089: CALL L116E ; Play a note and set the volume on the assigned MIDI channel. L108C: SLA (IY+$21) ; Have all channels been processed? RET C ; Return if so. CALL L0A6E ; Advance to the next channel data pointer. JR L106F ; Jump back to process the next channel. ; ----------------- ; Note Lookup Table ; ----------------- ; Each word gives the value of the sound generator tone registers for a given note. ; There are 9 octaves, containing a total of 108 notes. These represent notes 21 to ; 128. Notes 0 to 20 cannot be reproduced on the sound chip and so note 21 will be ; used for all of these (they will however be sent to a MIDI device if one is assigned ; to a channel). [Note that both the sound chip and the MIDI port can not play note 128 ; and so its inclusion in the table is a waste of 2 bytes]. The PLAY command does not allow ; octaves higher than 8 to be selected directly. Using PLAY "O8G" will select note 115. To ; select higher notes, sharps must be included, e.g. PLAY "O8#G" for note 116, PLAY "O8##G" ; for note 117, etc, up to PLAY "O8############G" for note 127. Attempting to access note ; 128 using PLAY "O8#############G" will lead to error report "m Note out of range". L1096: DEFW $0FBF ; Octave 1, Note 21 - A (27.50 Hz, Ideal=27.50 Hz, Error=-0.01%) C0 DEFW $0EDC ; Octave 1, Note 22 - A# (29.14 Hz, Ideal=29.16 Hz, Error=-0.08%) DEFW $0E07 ; Octave 1, Note 23 - B (30.87 Hz, Ideal=30.87 Hz, Error=-0.00%) DEFW $0D3D ; Octave 2, Note 24 - C (32.71 Hz, Ideal=32.70 Hz, Error=+0.01%) C1 DEFW $0C7F ; Octave 2, Note 25 - C# (34.65 Hz, Ideal=34.65 Hz, Error=-0.00%) DEFW $0BCC ; Octave 2, Note 26 - D (36.70 Hz, Ideal=36.71 Hz, Error=-0.01%) DEFW $0B22 ; Octave 2, Note 27 - D# (38.89 Hz, Ideal=38.89 Hz, Error=+0.01%) DEFW $0A82 ; Octave 2, Note 28 - E (41.20 Hz, Ideal=41.20 Hz, Error=+0.00%) DEFW $09EB ; Octave 2, Note 29 - F (43.66 Hz, Ideal=43.65 Hz, Error=+0.00%) DEFW $095D ; Octave 2, Note 30 - F# (46.24 Hz, Ideal=46.25 Hz, Error=-0.02%) DEFW $08D6 ; Octave 2, Note 31 - G (49.00 Hz, Ideal=49.00 Hz, Error=+0.00%) DEFW $0857 ; Octave 2, Note 32 - G# (51.92 Hz, Ideal=51.91 Hz, Error=+0.01%) DEFW $07DF ; Octave 2, Note 33 - A (55.01 Hz, Ideal=55.00 Hz, Error=+0.01%) DEFW $076E ; Octave 2, Note 34 - A# (58.28 Hz, Ideal=58.33 Hz, Error=-0.08%) DEFW $0703 ; Octave 2, Note 35 - B (61.75 Hz, Ideal=61.74 Hz, Error=+0.02%) DEFW $069F ; Octave 3, Note 36 - C ( 65.39 Hz, Ideal= 65.41 Hz, Error=-0.02%) C2 DEFW $0640 ; Octave 3, Note 37 - C# ( 69.28 Hz, Ideal= 69.30 Hz, Error=-0.04%) DEFW $05E6 ; Octave 3, Note 38 - D ( 73.40 Hz, Ideal= 73.42 Hz, Error=-0.01%) DEFW $0591 ; Octave 3, Note 39 - D# ( 77.78 Hz, Ideal= 77.78 Hz, Error=+0.01%) DEFW $0541 ; Octave 3, Note 40 - E ( 82.41 Hz, Ideal= 82.41 Hz, Error=+0.00%) DEFW $04F6 ; Octave 3, Note 41 - F ( 87.28 Hz, Ideal= 87.31 Hz, Error=-0.04%) DEFW $04AE ; Octave 3, Note 42 - F# ( 92.52 Hz, Ideal= 92.50 Hz, Error=+0.02%) DEFW $046B ; Octave 3, Note 43 - G ( 98.00 Hz, Ideal= 98.00 Hz, Error=+0.00%) DEFW $042C ; Octave 3, Note 44 - G# (103.78 Hz, Ideal=103.83 Hz, Error=-0.04%) DEFW $03F0 ; Octave 3, Note 45 - A (109.96 Hz, Ideal=110.00 Hz, Error=-0.04%) DEFW $03B7 ; Octave 3, Note 46 - A# (116.55 Hz, Ideal=116.65 Hz, Error=-0.08%) DEFW $0382 ; Octave 3, Note 47 - B (123.43 Hz, Ideal=123.47 Hz, Error=-0.03%) DEFW $034F ; Octave 4, Note 48 - C (130.86 Hz, Ideal=130.82 Hz, Error=+0.04%) C3 DEFW $0320 ; Octave 4, Note 49 - C# (138.55 Hz, Ideal=138.60 Hz, Error=-0.04%) DEFW $02F3 ; Octave 4, Note 50 - D (146.81 Hz, Ideal=146.83 Hz, Error=-0.01%) DEFW $02C8 ; Octave 4, Note 51 - D# (155.68 Hz, Ideal=155.55 Hz, Error=+0.08%) DEFW $02A1 ; Octave 4, Note 52 - E (164.70 Hz, Ideal=164.82 Hz, Error=-0.07%) DEFW $027B ; Octave 4, Note 53 - F (174.55 Hz, Ideal=174.62 Hz, Error=-0.04%) DEFW $0257 ; Octave 4, Note 54 - F# (185.04 Hz, Ideal=185.00 Hz, Error=+0.02%) DEFW $0236 ; Octave 4, Note 55 - G (195.83 Hz, Ideal=196.00 Hz, Error=-0.09%) DEFW $0216 ; Octave 4, Note 56 - G# (207.57 Hz, Ideal=207.65 Hz, Error=-0.04%) DEFW $01F8 ; Octave 4, Note 57 - A (219.92 Hz, Ideal=220.00 Hz, Error=-0.04%) DEFW $01DC ; Octave 4, Note 58 - A# (232.86 Hz, Ideal=233.30 Hz, Error=-0.19%) DEFW $01C1 ; Octave 4, Note 59 - B (246.86 Hz, Ideal=246.94 Hz, Error=-0.03%) DEFW $01A8 ; Octave 5, Note 60 - C (261.42 Hz, Ideal=261.63 Hz, Error=-0.08%) C4 Middle C DEFW $0190 ; Octave 5, Note 61 - C# (277.10 Hz, Ideal=277.20 Hz, Error=-0.04%) DEFW $0179 ; Octave 5, Note 62 - D (294.01 Hz, Ideal=293.66 Hz, Error=+0.12%) DEFW $0164 ; Octave 5, Note 63 - D# (311.35 Hz, Ideal=311.10 Hz, Error=+0.08%) DEFW $0150 ; Octave 5, Note 64 - E (329.88 Hz, Ideal=329.63 Hz, Error=+0.08%) DEFW $013D ; Octave 5, Note 65 - F (349.65 Hz, Ideal=349.23 Hz, Error=+0.12%) DEFW $012C ; Octave 5, Note 66 - F# (369.47 Hz, Ideal=370.00 Hz, Error=-0.14%) DEFW $011B ; Octave 5, Note 67 - G (391.66 Hz, Ideal=392.00 Hz, Error=-0.09%) DEFW $010B ; Octave 5, Note 68 - G# (415.13 Hz, Ideal=415.30 Hz, Error=-0.04%) DEFW $00FC ; Octave 5, Note 69 - A (439.84 Hz, Ideal=440.00 Hz, Error=-0.04%) DEFW $00EE ; Octave 5, Note 70 - A# (465.72 Hz, Ideal=466.60 Hz, Error=-0.19%) DEFW $00E0 ; Octave 5, Note 71 - B (494.82 Hz, Ideal=493.88 Hz, Error=+0.19%) DEFW $00D4 ; Octave 6, Note 72 - C (522.83 Hz, Ideal=523.26 Hz, Error=-0.08%) C5 DEFW $00C8 ; Octave 6, Note 73 - C# (554.20 Hz, Ideal=554.40 Hz, Error=-0.04%) DEFW $00BD ; Octave 6, Note 74 - D (586.46 Hz, Ideal=587.32 Hz, Error=-0.15%) DEFW $00B2 ; Octave 6, Note 75 - D# (622.70 Hz, Ideal=622.20 Hz, Error=+0.08%) DEFW $00A8 ; Octave 6, Note 76 - E (659.77 Hz, Ideal=659.26 Hz, Error=+0.08%) DEFW $009F ; Octave 6, Note 77 - F (697.11 Hz, Ideal=698.46 Hz, Error=-0.19%) DEFW $0096 ; Octave 6, Note 78 - F# (738.94 Hz, Ideal=740.00 Hz, Error=-0.14%) DEFW $008D ; Octave 6, Note 79 - G (786.10 Hz, Ideal=784.00 Hz, Error=+0.27%) DEFW $0085 ; Octave 6, Note 80 - G# (833.39 Hz, Ideal=830.60 Hz, Error=+0.34%) DEFW $007E ; Octave 6, Note 81 - A (879.69 Hz, Ideal=880.00 Hz, Error=-0.04%) DEFW $0077 ; Octave 6, Note 82 - A# (931.43 Hz, Ideal=933.20 Hz, Error=-0.19%) DEFW $0070 ; Octave 6, Note 83 - B (989.65 Hz, Ideal=987.76 Hz, Error=+0.19%) DEFW $006A ; Octave 7, Note 84 - C (1045.67 Hz, Ideal=1046.52 Hz, Error=-0.08%) C6 DEFW $0064 ; Octave 7, Note 85 - C# (1108.41 Hz, Ideal=1108.80 Hz, Error=-0.04%) DEFW $005E ; Octave 7, Note 86 - D (1179.16 Hz, Ideal=1174.64 Hz, Error=+0.38%) DEFW $0059 ; Octave 7, Note 87 - D# (1245.40 Hz, Ideal=1244.40 Hz, Error=+0.08%) DEFW $0054 ; Octave 7, Note 88 - E (1319.53 Hz, Ideal=1318.52 Hz, Error=+0.08%) DEFW $004F ; Octave 7, Note 89 - F (1403.05 Hz, Ideal=1396.92 Hz, Error=+0.44%) DEFW $004B ; Octave 7, Note 90 - F# (1477.88 Hz, Ideal=1480.00 Hz, Error=-0.14%) DEFW $0047 ; Octave 7, Note 91 - G (1561.14 Hz, Ideal=1568.00 Hz, Error=-0.44%) DEFW $0043 ; Octave 7, Note 92 - G# (1654.34 Hz, Ideal=1661.20 Hz, Error=-0.41%) DEFW $003F ; Octave 7, Note 93 - A (1759.38 Hz, Ideal=1760.00 Hz, Error=-0.04%) DEFW $003B ; Octave 7, Note 94 - A# (1878.65 Hz, Ideal=1866.40 Hz, Error=+0.66%) DEFW $0038 ; Octave 7, Note 95 - B (1979.30 Hz, Ideal=1975.52 Hz, Error=+0.19%) DEFW $0035 ; Octave 8, Note 96 - C (2091.33 Hz, Ideal=2093.04 Hz, Error=-0.08%) C7 DEFW $0032 ; Octave 8, Note 97 - C# (2216.81 Hz, Ideal=2217.60 Hz, Error=-0.04%) DEFW $002F ; Octave 8, Note 98 - D (2358.31 Hz, Ideal=2349.28 Hz, Error=+0.38%) DEFW $002D ; Octave 8, Note 99 - D# (2463.13 Hz, Ideal=2488.80 Hz, Error=-1.03%) DEFW $002A ; Octave 8, Note 100 - E (2639.06 Hz, Ideal=2637.04 Hz, Error=+0.08%) DEFW $0028 ; Octave 8, Note 101 - F (2771.02 Hz, Ideal=2793.84 Hz, Error=-0.82%) DEFW $0025 ; Octave 8, Note 102 - F# (2995.69 Hz, Ideal=2960.00 Hz, Error=+1.21%) DEFW $0023 ; Octave 8, Note 103 - G (3166.88 Hz, Ideal=3136.00 Hz, Error=+0.98%) DEFW $0021 ; Octave 8, Note 104 - G# (3358.81 Hz, Ideal=3322.40 Hz, Error=+1.10%) DEFW $001F ; Octave 8, Note 105 - A (3575.50 Hz, Ideal=3520.00 Hz, Error=+1.58%) DEFW $001E ; Octave 8, Note 106 - A# (3694.69 Hz, Ideal=3732.80 Hz, Error=-1.02%) DEFW $001C ; Octave 8, Note 107 - B (3958.59 Hz, Ideal=3951.04 Hz, Error=+0.19%) DEFW $001A ; Octave 9, Note 108 - C (4263.10 Hz, Ideal=4186.08 Hz, Error=+1.84%) C8 DEFW $0019 ; Octave 9, Note 109 - C# (4433.63 Hz, Ideal=4435.20 Hz, Error=-0.04%) DEFW $0018 ; Octave 9, Note 110 - D (4618.36 Hz, Ideal=4698.56 Hz, Error=-1.71%) DEFW $0016 ; Octave 9, Note 111 - D# (5038.21 Hz, Ideal=4977.60 Hz, Error=+1.22%) DEFW $0015 ; Octave 9, Note 112 - E (5278.13 Hz, Ideal=5274.08 Hz, Error=+0.08%) DEFW $0014 ; Octave 9, Note 113 - F (5542.03 Hz, Ideal=5587.68 Hz, Error=-0.82%) DEFW $0013 ; Octave 9, Note 114 - F# (5833.72 Hz, Ideal=5920.00 Hz, Error=-1.46%) DEFW $0012 ; Octave 9, Note 115 - G (6157.81 Hz, Ideal=6272.00 Hz, Error=-1.82%) DEFW $0011 ; Octave 9, Note 116 - G# (6520.04 Hz, Ideal=6644.80 Hz, Error=-1.88%) DEFW $0010 ; Octave 9, Note 117 - A (6927.54 Hz, Ideal=7040.00 Hz, Error=-1.60%) DEFW $000F ; Octave 9, Note 118 - A# (7389.38 Hz, Ideal=7465.60 Hz, Error=-1.02%) DEFW $000E ; Octave 9, Note 119 - B (7917.19 Hz, Ideal=7902.08 Hz, Error=+0.19%) DEFW $000D ; Octave 10, Note 120 - C ( 8526.20 Hz, Ideal= 8372.16 Hz, Error=+1.84%) C9 DEFW $000C ; Octave 10, Note 121 - C# ( 9236.72 Hz, Ideal= 8870.40 Hz, Error=+4.13%) DEFW $000C ; Octave 10, Note 122 - D ( 9236.72 Hz, Ideal= 9397.12 Hz, Error=-1.71%) DEFW $000B ; Octave 10, Note 123 - D# (10076.42 Hz, Ideal= 9955.20 Hz, Error=+1.22%) DEFW $000B ; Octave 10, Note 124 - E (10076.42 Hz, Ideal=10548.16 Hz, Error=-4.47%) DEFW $000A ; Octave 10, Note 125 - F (11084.06 Hz, Ideal=11175.36 Hz, Error=-0.82%) DEFW $0009 ; Octave 10, Note 126 - F# (12315.63 Hz, Ideal=11840.00 Hz, Error=+4.02%) DEFW $0009 ; Octave 10, Note 127 - G (12315.63 Hz, Ideal=12544.00 Hz, Error=-1.82%) DEFW $0008 ; Octave 10, Note 128 - G# (13855.08 Hz, Ideal=13289.60 Hz, Error=+4.26%) ; ------------------------- ; Play Note on MIDI Channel ; ------------------------- ; This routine turns on a note on the MIDI channel and sets its volume, if MIDI channel is assigned to the current string. ; Three bytes are sent, and have the following meaning: ; Byte 1: Channel number $00..$0F, with bits 4 and 7 set. ; Byte 2: Note number $00..$7F. ; Byte 3: Note velocity $00..$78. ; Entry: IX=Address of the channel data block. L116E: LD A,(IX+$01) ; Is a MIDI channel assigned to this string? OR A ; RET M ; Return if not. ;A holds the assigned channel number ($00..$0F) OR $90 ; Set bits 4 and 7 of the channel number. A=$90..$9F. CALL L11A3 ; Write byte to MIDI device. LD A,(IX+$00) ; The note number. CALL L11A3 ; Write byte to MIDI device. LD A,(IX+$04) ; Fetch the channel's volume. RES 4,A ; Ensure the 'using envelope' bit is reset so SLA A ; that A holds a value between $00 and $0F. SLA A ; Multiply by 8 to increase the range to $00..$78. SLA A ; A=Note velocity. CALL L11A3 ; Write byte to MIDI device. RET ; [Could have saved 1 byte by using JP L11A3 (ROM 0)] ; --------------------- ; Turn MIDI Channel Off ; --------------------- ; This routine turns off a note on the MIDI channel, if a MIDI channel is assigned to the current string. ; Three bytes are sent, and have the following meaning: ; Byte 1: Channel number $00..$0F, with bit 7 set. ; Byte 2: Note number $00..$7F. ; Byte 3: Note velocity $40. ; Entry: IX=Address of the channel data block. L118D: LD A,(IX+$01) ; Is a MIDI channel assigned to this string? OR A ; RET M ; Return if not. ;A holds the assigned channel number ($00..$0F) OR $80 ; Set bit 7 of the channel number. A=$80..$8F. CALL L11A3 ; Write byte to MIDI device. LD A,(IX+$00) ; The note number. CALL L11A3 ; Write byte to MIDI device. LD A,$40 ; The note velocity. CALL L11A3 ; Write byte to MIDI device. RET ; [Could have saved 1 byte by using JP L11A3 (ROM 0)] ; ------------------------ ; Send Byte to MIDI Device ; ------------------------ ; This routine sends a byte to the MIDI port. MIDI devices communicate at 31250 baud, ; although this routine actually generates a baud rate of 31388, which is within the 1% ; tolerance supported by MIDI devices. ; Entry: A=Byte to send. L11A3: LD L,A ; Store the byte to send. LD BC,$FFFD ; LD A,$0E ; OUT (C),A ; Select register 14 - I/O port. LD BC,$BFFD ; LD A,$FA ; Set RS232 'RXD' transmit line to 0. (Keep KEYPAD 'CTS' output line low to prevent the keypad resetting) OUT (C),A ; Send out the START bit. LD E,$03 ; (7) Introduce delays such that the next bit is output 113 T-states from now. L11B4: DEC E ; (4) JR NZ,L11B4 ; (12/7) NOP ; (4) NOP ; (4) NOP ; (4) NOP ; (4) LD A,L ; (4) Retrieve the byte to send. LD D,$08 ; (7) There are 8 bits to send. L11BE: RRA ; (4) Rotate the next bit to send into the carry. LD L,A ; (4) Store the remaining bits. JP NC,L11C9 ; (10) Jump if it is a 0 bit. LD A,$FE ; (7) Set RS232 'RXD' transmit line to 1. (Keep KEYPAD 'CTS' output line low to prevent the keypad resetting) OUT (C),A ; (11) JR L11CF ; (12) Jump forward to process the next bit. L11C9: LD A,$FA ; (7) Set RS232 'RXD' transmit line to 0. (Keep KEYPAD 'CTS' output line low to prevent the keypad resetting) OUT (C),A ; (11) JR L11CF ; (12) Jump forward to process the next bit. L11CF: LD E,$02 ; (7) Introduce delays such that the next data bit is output 113 T-states from now. L11D1: DEC E ; (4) JR NZ,L11D1 ; (12/7) NOP ; (4) ADD A,$00 ; (7) LD A,L ; (4) Retrieve the remaining bits to send. DEC D ; (4) Decrement the bit counter. JR NZ,L11BE ; (12/7) Jump back if there are further bits to send. NOP ; (4) Introduce delays such that the stop bit is output 113 T-states from now. NOP ; (4) ADD A,$00 ; (7) NOP ; (4) NOP ; (4) LD A,$FE ; (7) Set RS232 'RXD' transmit line to 0. (Keep KEYPAD 'CTS' output line low to prevent the keypad resetting) OUT (C),A ; (11) Send out the STOP bit. LD E,$06 ; (7) Delay for 101 T-states (28.5us). L11E7: DEC E ; (4) JR NZ,L11E7 ; (12/7) RET ; (10)
nimdasys_inbox_ru(29.05.2021)
Идем дальше, попытка распарсить параметры оператора PLAY
Имеем пример - PLAY "T160","","","Y1Z192Z0V15O5cdefgabC"
T160 - установка темпа 160 (по умолчанию 120)
Y1 - установка канала 1 миди (собственно Y дает понять что будет играть миди, а не AY), диапазон 1...16. Внутри оператора уменьшается на 1, чтобы передавалось в канал миди значение 0...F.
Z192, Z0 - непосредственная отправка команд в канал миди, в данном случае 0С0h и 00h - выбираем инструмент (акустический рояль) для канала 1
V15 - установка громкости звучания (15)
O5 - установка октавы (5)
cdefgabC - ноты
Djoni(25.05.2021), nimdasys_inbox_ru(29.05.2021), Oleg N. Cher(27.05.2021)
Продолжаем исследования. Теперь наберем строчку в эмуляторе Unreal
PLAY "T160","","","Y1Z192Z0V15O5cdefgabC"
И поставим в дебаггере точку останова на процедуру вывода байта в порт миди (11A3h - Send Byte to MIDI Device)
Вот что пересылается в порт миди в соответствии с параметрами сторки:
Z192Z0 - C0h, 00h - выбираем инструмент, в данном случае акустический рояль
c - 90h, 3Ch, 78h, 80h, 3Ch, 40h - включаем и выключаем ноту До
d - 90h, 3Eh, 78h, 80h, 3Eh, 40h - включаем и выключаем ноту Ре
e - 90h, 40h, 78h, 80h, 40h, 40h - включаем и выключаем ноту Ми
f - 90h, 41h, 78h, 80h, 41h, 40h - включаем и выключаем ноту Фа
g - 90h, 43h, 78h, 80h, 43h, 40h - включаем и выключаем ноту Соль
a - 90h, 45h, 78h, 80h, 45h, 40h - включаем и выключаем ноту Ля
b - 90h, 47h, 78h, 80h, 47h, 40h - включаем и выключаем ноту Си
C - 90h, 48h, 78h, 80h, 48h, 40h - включаем и выключаем ноту До следующей октавы
Также следует помнить, когда в строке задавали параметр O5 - выбор октавы 5,
то в соответствии с таблицей 5 октава соответствует Первой октаве в реальности
Также стоит отметить что между включением и выключением нот существует некоторая пауза, по всей видимости соответствующая выбранному темпу.Код:; ----------------- ; Note Lookup Table ; ----------------- ; Each word gives the value of the sound generator tone registers for a given note. ; There are 9 octaves, containing a total of 108 notes. These represent notes 21 to ; 128. Notes 0 to 20 cannot be reproduced on the sound chip and so note 21 will be ; used for all of these (they will however be sent to a MIDI device if one is assigned ; to a channel). [Note that both the sound chip and the MIDI port can not play note 128 ; and so its inclusion in the table is a waste of 2 bytes]. The PLAY command does not allow ; octaves higher than 8 to be selected directly. Using PLAY "O8G" will select note 115. To ; select higher notes, sharps must be included, e.g. PLAY "O8#G" for note 116, PLAY "O8##G" ; for note 117, etc, up to PLAY "O8############G" for note 127. Attempting to access note ; 128 using PLAY "O8#############G" will lead to error report "m Note out of range". L1096: DEFW $0FBF ; Octave 1, Note 21 - A (27.50 Hz, Ideal=27.50 Hz, Error=-0.01%) C0 DEFW $0EDC ; Octave 1, Note 22 - A# (29.14 Hz, Ideal=29.16 Hz, Error=-0.08%) DEFW $0E07 ; Octave 1, Note 23 - B (30.87 Hz, Ideal=30.87 Hz, Error=-0.00%) DEFW $0D3D ; Octave 2, Note 24 - C (32.71 Hz, Ideal=32.70 Hz, Error=+0.01%) C1 DEFW $0C7F ; Octave 2, Note 25 - C# (34.65 Hz, Ideal=34.65 Hz, Error=-0.00%) DEFW $0BCC ; Octave 2, Note 26 - D (36.70 Hz, Ideal=36.71 Hz, Error=-0.01%) DEFW $0B22 ; Octave 2, Note 27 - D# (38.89 Hz, Ideal=38.89 Hz, Error=+0.01%) DEFW $0A82 ; Octave 2, Note 28 - E (41.20 Hz, Ideal=41.20 Hz, Error=+0.00%) DEFW $09EB ; Octave 2, Note 29 - F (43.66 Hz, Ideal=43.65 Hz, Error=+0.00%) DEFW $095D ; Octave 2, Note 30 - F# (46.24 Hz, Ideal=46.25 Hz, Error=-0.02%) DEFW $08D6 ; Octave 2, Note 31 - G (49.00 Hz, Ideal=49.00 Hz, Error=+0.00%) DEFW $0857 ; Octave 2, Note 32 - G# (51.92 Hz, Ideal=51.91 Hz, Error=+0.01%) DEFW $07DF ; Octave 2, Note 33 - A (55.01 Hz, Ideal=55.00 Hz, Error=+0.01%) DEFW $076E ; Octave 2, Note 34 - A# (58.28 Hz, Ideal=58.33 Hz, Error=-0.08%) DEFW $0703 ; Octave 2, Note 35 - B (61.75 Hz, Ideal=61.74 Hz, Error=+0.02%) DEFW $069F ; Octave 3, Note 36 - C ( 65.39 Hz, Ideal= 65.41 Hz, Error=-0.02%) C2 DEFW $0640 ; Octave 3, Note 37 - C# ( 69.28 Hz, Ideal= 69.30 Hz, Error=-0.04%) DEFW $05E6 ; Octave 3, Note 38 - D ( 73.40 Hz, Ideal= 73.42 Hz, Error=-0.01%) DEFW $0591 ; Octave 3, Note 39 - D# ( 77.78 Hz, Ideal= 77.78 Hz, Error=+0.01%) DEFW $0541 ; Octave 3, Note 40 - E ( 82.41 Hz, Ideal= 82.41 Hz, Error=+0.00%) DEFW $04F6 ; Octave 3, Note 41 - F ( 87.28 Hz, Ideal= 87.31 Hz, Error=-0.04%) DEFW $04AE ; Octave 3, Note 42 - F# ( 92.52 Hz, Ideal= 92.50 Hz, Error=+0.02%) DEFW $046B ; Octave 3, Note 43 - G ( 98.00 Hz, Ideal= 98.00 Hz, Error=+0.00%) DEFW $042C ; Octave 3, Note 44 - G# (103.78 Hz, Ideal=103.83 Hz, Error=-0.04%) DEFW $03F0 ; Octave 3, Note 45 - A (109.96 Hz, Ideal=110.00 Hz, Error=-0.04%) DEFW $03B7 ; Octave 3, Note 46 - A# (116.55 Hz, Ideal=116.65 Hz, Error=-0.08%) DEFW $0382 ; Octave 3, Note 47 - B (123.43 Hz, Ideal=123.47 Hz, Error=-0.03%) DEFW $034F ; Octave 4, Note 48 - C (130.86 Hz, Ideal=130.82 Hz, Error=+0.04%) C3 DEFW $0320 ; Octave 4, Note 49 - C# (138.55 Hz, Ideal=138.60 Hz, Error=-0.04%) DEFW $02F3 ; Octave 4, Note 50 - D (146.81 Hz, Ideal=146.83 Hz, Error=-0.01%) DEFW $02C8 ; Octave 4, Note 51 - D# (155.68 Hz, Ideal=155.55 Hz, Error=+0.08%) DEFW $02A1 ; Octave 4, Note 52 - E (164.70 Hz, Ideal=164.82 Hz, Error=-0.07%) DEFW $027B ; Octave 4, Note 53 - F (174.55 Hz, Ideal=174.62 Hz, Error=-0.04%) DEFW $0257 ; Octave 4, Note 54 - F# (185.04 Hz, Ideal=185.00 Hz, Error=+0.02%) DEFW $0236 ; Octave 4, Note 55 - G (195.83 Hz, Ideal=196.00 Hz, Error=-0.09%) DEFW $0216 ; Octave 4, Note 56 - G# (207.57 Hz, Ideal=207.65 Hz, Error=-0.04%) DEFW $01F8 ; Octave 4, Note 57 - A (219.92 Hz, Ideal=220.00 Hz, Error=-0.04%) DEFW $01DC ; Octave 4, Note 58 - A# (232.86 Hz, Ideal=233.30 Hz, Error=-0.19%) DEFW $01C1 ; Octave 4, Note 59 - B (246.86 Hz, Ideal=246.94 Hz, Error=-0.03%) DEFW $01A8 ; Octave 5, Note 60 - C (261.42 Hz, Ideal=261.63 Hz, Error=-0.08%) C4 Middle C DEFW $0190 ; Octave 5, Note 61 - C# (277.10 Hz, Ideal=277.20 Hz, Error=-0.04%) DEFW $0179 ; Octave 5, Note 62 - D (294.01 Hz, Ideal=293.66 Hz, Error=+0.12%) DEFW $0164 ; Octave 5, Note 63 - D# (311.35 Hz, Ideal=311.10 Hz, Error=+0.08%) DEFW $0150 ; Octave 5, Note 64 - E (329.88 Hz, Ideal=329.63 Hz, Error=+0.08%) DEFW $013D ; Octave 5, Note 65 - F (349.65 Hz, Ideal=349.23 Hz, Error=+0.12%) DEFW $012C ; Octave 5, Note 66 - F# (369.47 Hz, Ideal=370.00 Hz, Error=-0.14%) DEFW $011B ; Octave 5, Note 67 - G (391.66 Hz, Ideal=392.00 Hz, Error=-0.09%) DEFW $010B ; Octave 5, Note 68 - G# (415.13 Hz, Ideal=415.30 Hz, Error=-0.04%) DEFW $00FC ; Octave 5, Note 69 - A (439.84 Hz, Ideal=440.00 Hz, Error=-0.04%) DEFW $00EE ; Octave 5, Note 70 - A# (465.72 Hz, Ideal=466.60 Hz, Error=-0.19%) DEFW $00E0 ; Octave 5, Note 71 - B (494.82 Hz, Ideal=493.88 Hz, Error=+0.19%) DEFW $00D4 ; Octave 6, Note 72 - C (522.83 Hz, Ideal=523.26 Hz, Error=-0.08%) C5 DEFW $00C8 ; Octave 6, Note 73 - C# (554.20 Hz, Ideal=554.40 Hz, Error=-0.04%) DEFW $00BD ; Octave 6, Note 74 - D (586.46 Hz, Ideal=587.32 Hz, Error=-0.15%) DEFW $00B2 ; Octave 6, Note 75 - D# (622.70 Hz, Ideal=622.20 Hz, Error=+0.08%) DEFW $00A8 ; Octave 6, Note 76 - E (659.77 Hz, Ideal=659.26 Hz, Error=+0.08%) DEFW $009F ; Octave 6, Note 77 - F (697.11 Hz, Ideal=698.46 Hz, Error=-0.19%) DEFW $0096 ; Octave 6, Note 78 - F# (738.94 Hz, Ideal=740.00 Hz, Error=-0.14%) DEFW $008D ; Octave 6, Note 79 - G (786.10 Hz, Ideal=784.00 Hz, Error=+0.27%) DEFW $0085 ; Octave 6, Note 80 - G# (833.39 Hz, Ideal=830.60 Hz, Error=+0.34%) DEFW $007E ; Octave 6, Note 81 - A (879.69 Hz, Ideal=880.00 Hz, Error=-0.04%) DEFW $0077 ; Octave 6, Note 82 - A# (931.43 Hz, Ideal=933.20 Hz, Error=-0.19%) DEFW $0070 ; Octave 6, Note 83 - B (989.65 Hz, Ideal=987.76 Hz, Error=+0.19%) DEFW $006A ; Octave 7, Note 84 - C (1045.67 Hz, Ideal=1046.52 Hz, Error=-0.08%) C6 DEFW $0064 ; Octave 7, Note 85 - C# (1108.41 Hz, Ideal=1108.80 Hz, Error=-0.04%) DEFW $005E ; Octave 7, Note 86 - D (1179.16 Hz, Ideal=1174.64 Hz, Error=+0.38%) DEFW $0059 ; Octave 7, Note 87 - D# (1245.40 Hz, Ideal=1244.40 Hz, Error=+0.08%) DEFW $0054 ; Octave 7, Note 88 - E (1319.53 Hz, Ideal=1318.52 Hz, Error=+0.08%) DEFW $004F ; Octave 7, Note 89 - F (1403.05 Hz, Ideal=1396.92 Hz, Error=+0.44%) DEFW $004B ; Octave 7, Note 90 - F# (1477.88 Hz, Ideal=1480.00 Hz, Error=-0.14%) DEFW $0047 ; Octave 7, Note 91 - G (1561.14 Hz, Ideal=1568.00 Hz, Error=-0.44%) DEFW $0043 ; Octave 7, Note 92 - G# (1654.34 Hz, Ideal=1661.20 Hz, Error=-0.41%) DEFW $003F ; Octave 7, Note 93 - A (1759.38 Hz, Ideal=1760.00 Hz, Error=-0.04%) DEFW $003B ; Octave 7, Note 94 - A# (1878.65 Hz, Ideal=1866.40 Hz, Error=+0.66%) DEFW $0038 ; Octave 7, Note 95 - B (1979.30 Hz, Ideal=1975.52 Hz, Error=+0.19%) DEFW $0035 ; Octave 8, Note 96 - C (2091.33 Hz, Ideal=2093.04 Hz, Error=-0.08%) C7 DEFW $0032 ; Octave 8, Note 97 - C# (2216.81 Hz, Ideal=2217.60 Hz, Error=-0.04%) DEFW $002F ; Octave 8, Note 98 - D (2358.31 Hz, Ideal=2349.28 Hz, Error=+0.38%) DEFW $002D ; Octave 8, Note 99 - D# (2463.13 Hz, Ideal=2488.80 Hz, Error=-1.03%) DEFW $002A ; Octave 8, Note 100 - E (2639.06 Hz, Ideal=2637.04 Hz, Error=+0.08%) DEFW $0028 ; Octave 8, Note 101 - F (2771.02 Hz, Ideal=2793.84 Hz, Error=-0.82%) DEFW $0025 ; Octave 8, Note 102 - F# (2995.69 Hz, Ideal=2960.00 Hz, Error=+1.21%) DEFW $0023 ; Octave 8, Note 103 - G (3166.88 Hz, Ideal=3136.00 Hz, Error=+0.98%) DEFW $0021 ; Octave 8, Note 104 - G# (3358.81 Hz, Ideal=3322.40 Hz, Error=+1.10%) DEFW $001F ; Octave 8, Note 105 - A (3575.50 Hz, Ideal=3520.00 Hz, Error=+1.58%) DEFW $001E ; Octave 8, Note 106 - A# (3694.69 Hz, Ideal=3732.80 Hz, Error=-1.02%) DEFW $001C ; Octave 8, Note 107 - B (3958.59 Hz, Ideal=3951.04 Hz, Error=+0.19%) DEFW $001A ; Octave 9, Note 108 - C (4263.10 Hz, Ideal=4186.08 Hz, Error=+1.84%) C8 DEFW $0019 ; Octave 9, Note 109 - C# (4433.63 Hz, Ideal=4435.20 Hz, Error=-0.04%) DEFW $0018 ; Octave 9, Note 110 - D (4618.36 Hz, Ideal=4698.56 Hz, Error=-1.71%) DEFW $0016 ; Octave 9, Note 111 - D# (5038.21 Hz, Ideal=4977.60 Hz, Error=+1.22%) DEFW $0015 ; Octave 9, Note 112 - E (5278.13 Hz, Ideal=5274.08 Hz, Error=+0.08%) DEFW $0014 ; Octave 9, Note 113 - F (5542.03 Hz, Ideal=5587.68 Hz, Error=-0.82%) DEFW $0013 ; Octave 9, Note 114 - F# (5833.72 Hz, Ideal=5920.00 Hz, Error=-1.46%) DEFW $0012 ; Octave 9, Note 115 - G (6157.81 Hz, Ideal=6272.00 Hz, Error=-1.82%) DEFW $0011 ; Octave 9, Note 116 - G# (6520.04 Hz, Ideal=6644.80 Hz, Error=-1.88%) DEFW $0010 ; Octave 9, Note 117 - A (6927.54 Hz, Ideal=7040.00 Hz, Error=-1.60%) DEFW $000F ; Octave 9, Note 118 - A# (7389.38 Hz, Ideal=7465.60 Hz, Error=-1.02%) DEFW $000E ; Octave 9, Note 119 - B (7917.19 Hz, Ideal=7902.08 Hz, Error=+0.19%) DEFW $000D ; Octave 10, Note 120 - C ( 8526.20 Hz, Ideal= 8372.16 Hz, Error=+1.84%) C9 DEFW $000C ; Octave 10, Note 121 - C# ( 9236.72 Hz, Ideal= 8870.40 Hz, Error=+4.13%) DEFW $000C ; Octave 10, Note 122 - D ( 9236.72 Hz, Ideal= 9397.12 Hz, Error=-1.71%) DEFW $000B ; Octave 10, Note 123 - D# (10076.42 Hz, Ideal= 9955.20 Hz, Error=+1.22%) DEFW $000B ; Octave 10, Note 124 - E (10076.42 Hz, Ideal=10548.16 Hz, Error=-4.47%) DEFW $000A ; Octave 10, Note 125 - F (11084.06 Hz, Ideal=11175.36 Hz, Error=-0.82%) DEFW $0009 ; Octave 10, Note 126 - F# (12315.63 Hz, Ideal=11840.00 Hz, Error=+4.02%) DEFW $0009 ; Octave 10, Note 127 - G (12315.63 Hz, Ideal=12544.00 Hz, Error=-1.82%) DEFW $0008 ; Octave 10, Note 128 - G# (13855.08 Hz, Ideal=13289.60 Hz, Error=+4.26%)
Громкость, которая указывалась как параметр V15, включается в параметр динамики воспроизведении ноты, в данном случаем значение байта равно 78h.
Диапазон громкости 0...15, но бейсик ее преобразует в значения 00...78h
Последний раз редактировалось Mick; 26.05.2021 в 10:20.
CLR(19.05.2024), nimdasys_inbox_ru(29.05.2021), Oleg N. Cher(27.05.2021)
Эту тему просматривают: 1 (пользователей: 0 , гостей: 1)