Topic: [MZ-700] IN-GAME 1-bit PWM
I'm the owner of an MZ-700 and coding some games on it.
First, let me introduce to its sound hardware:
- Loudpseaker 8Ohm/1W
- Notes are usually played through a square signal from 16Hz to 1.108404688MHz using i8253 counter 0 in mode 3.
PWM can be done by alternating mode 0 and mode 4 without setting counter 0.
In fact, we can also use mode 0 with setting count 0 to create a one-shot square signal with a variable duty: intersective PWM.
To be able to output sound in-game, I use an interrupt. Stock MZ-700 mostly uses /INT to toggle AM.PM flag and to readjust time through counters 1 and 2. Since my games are standalone, I usually shut down this interrupt to allow "fast" screen drawing using stack trick (POP/PUSH) because you can access VRAM only when horizontal blanking is on.
So I need another trick to be able use interrupts without disturbing the drawing code. It happens the clock of counter 1 is the same signal which freezes CPU when attempting to access VRAM when BLNK is 0. Interrupt is raised at falling edge of BLNK. So I use HALT instruction to wait for an ISR to toggle the sound output or not then transfer N VRAM blocks through stack then restore a valid stack and do that for 25 lines (in total, we have 2 x 28 x 25 characters/attributes to draw in 100 horizontal blanks - 312 for a frame). I won't dig in the details so let say what is possible:
- for a full 50 FPS, I need an ISR every 4 horizontal blanks to avoid screen corruption. PWM rate is 3902.8314Hz.
- for a full 25 FPS, I need an ISR every 3 horizontal blanks to avoid screen corruption. PWM rate is 5203.7752Hz.
I used IM2 a la Spectrum to set my ISR and here is the ISR:
sfx_isr: PUSH HL PUSH AF if DEBUG LD HL,isr_count INC (HL) endif LD HL,$E006 ; rearm counter to get the next ISR LD (HL),PIT_CNT2 LD A,(sfx_buffer) ; get $20 or $28 to toggle the output sound (PWM 0 or 100%) sfx_isr_buffer_index equ $-2 INC L LD (HL),A LD HL,sfx_isr_buffer_index ; advance in buffer (256-byte ring page) sfx_isr_on_off: NOP ; opcodes $34 (INC [HL]) for ON or $00 (NOP) for OFF POP AF POP HL EI RET
$20 is mode 0 (final sound output is 0) and $28 is mode 4 (final sound output is 1).
I then have a code to run in the game loop to fill the ring page on the fly from the data generated by pcm2pwm v1.
For 50 FPS, I need 78 bytes in the ring page to play per frame.
For 25 FPS, I need 104 bytes in the ring page to play per frame.