# HG changeset patch # User Laurens Holst # Date 1595598912 -7200 # Fri Jul 24 15:55:12 2020 +0200 # Node ID e8f83e03346ae656e7c852815a9583edf3f9e369 # Parent c0c596dce313d398bc72205610b3321b20b20096 OPLTimer: Introduce new timer for OPL3 and OPL4. It provides a timing resolution of 1130 Hz, which can also be configured higher. In theory it would also work on MSX-AUDIO, however that requires more waits to be added since there must be a 84 / 3.58 µs wait after a data write, and the interrupt handling up to the int reset write takes less, esp. on faster CPUs. diff --git a/CHANGES.md b/CHANGES.md --- a/CHANGES.md +++ b/CHANGES.md @@ -4,11 +4,12 @@ For the complete list of changes please refer to the [revision history](https://hg.sr.ht/~grauw/vgmplay-msx/log). -[1.3] — 2019-??-?? +[1.3] — 2020-??-?? ------------------ * New chips: YM2203 OPN, YM2608 OPNA, YM2610 OPNB, YM2610B OPNB-B, YM2612 OPN2 * New sound expansions: Makoto, Neotron, Darky, DalSoRi R2, OPL3 (C0H) + * New timer: 1130 Hz if a MoonSound or OPL3 is present * Emulation of SN76489 DCSG on PSG * Emulation of YM2203 OPN, YM2608 OPNA and YM2612 OPN2 on Yamaha SFG * Translation of YM2612 OPN2 frequencies to correct tuning diff --git a/README.md b/README.md --- a/README.md +++ b/README.md @@ -26,7 +26,8 @@ [gunzip for MSX](http://www.grauw.nl/projects/gunzip/) or PC. The timing resolution is 50 or 60 Hz on MSX1 machines with a TMS9918 VDP, 300 Hz -on machines with a V9938 or V9958 VDP, and 4000 Hz on MSX turboR. +on machines with a V9938 or V9958 VDP, 1130 Hz if a MoonSound or OPL3 is +present, and 4000 Hz on MSX turboR. For collections of VGM music see: diff --git a/src/drivers/OPL3.asm b/src/drivers/OPL3.asm --- a/src/drivers/OPL3.asm +++ b/src/drivers/OPL3.asm @@ -25,10 +25,12 @@ ; a = register ; d = value WriteRegister: + di out (?base + OPL3_FM_ADDRESS),a basePort: equ $ - 1 - jp $ + 3 ; wait 32 / 14.32 µs + cp (hl) ; wait 32 / 14.32 µs ld a,d + ei out (?base + OPL3_FM_DATA),a ret MaskControl: @@ -43,9 +45,11 @@ ; a = register ; d = value WriteRegister2: + di out (?base + OPL3_FM2_ADDRESS),a - jp $ + 3 ; wait 32 / 14.32 µs + cp (hl) ; wait 32 / 14.32 µs ld a,d + ei out (?base + OPL3_FM2_DATA),a ret diff --git a/src/drivers/OPL4.asm b/src/drivers/OPL4.asm --- a/src/drivers/OPL4.asm +++ b/src/drivers/OPL4.asm @@ -31,9 +31,10 @@ ; a = register ; d = value WriteRegister: + di out (?fmbase + OPL4_FM_ADDRESS),a - nop ; wait 56 / 33.87 µs - ld a,d + ld a,d ; wait 56 / 33.87 µs + ei out (?fmbase + OPL4_FM_DATA),a ret MaskControl: @@ -48,9 +49,10 @@ ; a = register ; d = value WriteRegister2: + di out (?fmbase + OPL4_FM2_ADDRESS),a - nop ; wait 56 / 33.87 µs - ld a,d + ld a,d ; wait 56 / 33.87 µs + ei out (?fmbase + OPL4_FM2_DATA),a ret diff --git a/src/timers/OPLTimer.asm b/src/timers/OPLTimer.asm new file mode 100644 --- /dev/null +++ b/src/timers/OPLTimer.asm @@ -0,0 +1,162 @@ +; +; OPL3/4 interrupt timer +; +; 1130 Hz resolution +; +; Also works on MSX-AUDIO, but it requires more waits. +; +OPLTimer_RATE: equ 11 ; 2: 6214 Hz, 4: 3107 Hz, 11: 1130 Hz +OPLTimer_SAMPLES: equ 44100 * 288 * OPLTimer_RATE / 3579545 + +OPLTimer: MACRO + super: Timer OPLTimer_Start, OPLTimer_Stop, OPLTimer_Reset, Update + tick: equ InterruptHandler.tick + basePort: equ InterruptHandler.addressPort + lastTick: + db 0 + + ; ix = this + ; de <- time passed + Update: PROC + ld b,(ix + OPLTimer.lastTick) + Wait: + ld a,(ix + OPLTimer.tick) + cp b + jr z,Wait + ld (ix + OPLTimer.lastTick),a + sub b + ld b,a + ld hl,0 + ld de,OPLTimer_SAMPLES + Loop: + add hl,de + djnz Loop + ex de,hl + jr super.Callback + ENDP + + InterruptHandler: PROC + push af + ld a,MSXAudio_FLAG_CONTROL + out (0),a + addressPort: equ $ - 1 + in a,(0) + statusPort: equ $ - 1 + and 01000000B + jr z,OldHook + cpl + out (1),a + dataPort: equ $ - 1 + ld a,0 + tick: equ $ - 1 + inc a + ld (tick),a + tickReference: equ $ - 2 + pop af + ei + ret + OldHook: + pop af + oldHook: + ds Interrupt_HOOK_SIZE,0C9H + ENDP + _size: + ENDM + +OPLTimer_class: Class OPLTimer, OPLTimer_template, Heap_main +OPLTimer_template: OPLTimer + +; c = base I/O port +; hl = callback +; ix = this +OPLTimer_Construct: + call Timer_Construct + ld e,ixl + ld d,ixh + ld hl,OPLTimer.InterruptHandler.tick + add hl,de + ld (ix + OPLTimer.InterruptHandler.addressPort),c + ld (ix + OPLTimer.InterruptHandler.statusPort),c + inc c + ld (ix + OPLTimer.InterruptHandler.dataPort),c + ld (ix + OPLTimer.InterruptHandler.tickReference),l + ld (ix + OPLTimer.InterruptHandler.tickReference + 1),h + ret + +; ix = this +OPLTimer_Destruct: equ Timer_Destruct +; jp Timer_Destruct + +; e = register +; d = value +; ix = this +OPLTimer_WriteRegister: + ld c,(ix + OPLTimer.basePort) + di + out (c),e + cp (hl) ; wait 32 / 14.32 µs + inc c + ei + out (c),d + ret + +; ix = this +OPLTimer_Start: PROC + call OPLTimer_Reset + call OPLTimer_InstallInterruptHandler + ld de,-OPLTimer_RATE << 8 | MSXAudio_TIMER_1 + call OPLTimer_WriteRegister + ld de,10000000B << 8 | MSXAudio_FLAG_CONTROL + call OPLTimer_WriteRegister + ld de,00111001B << 8 | MSXAudio_FLAG_CONTROL + jp OPLTimer_WriteRegister + ENDP + +; ix = this +OPLTimer_Stop: + ld de,01111000B << 8 | MSXAudio_FLAG_CONTROL + call OPLTimer_WriteRegister + jr OPLTimer_UninstallInterruptHandler + +; ix = this +OPLTimer_InstallInterruptHandler: + push ix + ld ix,Interrupt_instance + call Interrupt_Construct + pop ix + ld c,ixl + ld b,ixh + ld hl,OPLTimer.InterruptHandler.oldHook + add hl,bc + ex de,hl + ld hl,OPLTimer.InterruptHandler + add hl,bc + push ix + ld ix,Interrupt_instance + call Interrupt_Hook + pop ix + ret + +; ix = this +OPLTimer_UninstallInterruptHandler: + push ix + ld ix,Interrupt_instance + call Interrupt_Destruct + pop ix + ret + +; ix = this +OPLTimer_Reset: + ld a,(ix + OPLTimer.tick) + ld (ix + OPLTimer.lastTick),a + ret + +; f <- c: found +; c <- base I/O port +OPLTimer_Detect: + ld c,MoonSound_FM_BASE_PORT + call OPL3_DetectPort + ret c + ld c,OPL3_BASE_PORT + call OPL3_DetectPort + ret diff --git a/src/timers/Timer.asm b/src/timers/Timer.asm --- a/src/timers/Timer.asm +++ b/src/timers/Timer.asm @@ -10,6 +10,7 @@ INCLUDE "TimerFactory.asm" INCLUDE "VBlankTimer.asm" INCLUDE "LineTimer.asm" + INCLUDE "OPLTimer.asm" INCLUDE "TurboRTimer.asm" Timer: MACRO ?start, ?stop, ?reset, ?update diff --git a/src/timers/TimerFactory.asm b/src/timers/TimerFactory.asm --- a/src/timers/TimerFactory.asm +++ b/src/timers/TimerFactory.asm @@ -6,6 +6,8 @@ TimerFactory: MACRO lineTimer: dw 0 + oplTimer: + dw 0 vBlankTimer: VBlankTimer vBlankTimerFactory: @@ -22,6 +24,7 @@ ; f <- c: succeeded TimerFactory_Create: call TimerFactory_CreateTurboRTimer + call nc,TimerFactory_CreateOPLTimer call nc,TimerFactory_CreateLineTimer call nc,TimerFactory_CreateVBlankTimer ret @@ -35,6 +38,9 @@ call TimerFactory_DestroyLineTimer pop ix push ix + call TimerFactory_DestroyOPLTimer + pop ix + push ix call TimerFactory_DestroyTurboRTimer pop ix ret @@ -98,6 +104,46 @@ ; ix = this ; ix <- timer ; f <- c: succeeded +TimerFactory_CreateOPLTimer: + push hl + call OPLTimer_Detect + pop hl + ret nc + ld a,(ix + TimerFactory.oplTimer) + or (ix + TimerFactory.oplTimer + 1) + ret nz + push ix + call OPLTimer_class.New + call OPLTimer_Construct + ld e,ixl + ld d,ixh + ex (sp),ix + ld (ix + TimerFactory.oplTimer),e + ld (ix + TimerFactory.oplTimer + 1),d + pop ix + scf + ret + +; ix = this +TimerFactory_DestroyOPLTimer: + ld e,(ix + TimerFactory.oplTimer) + ld d,(ix + TimerFactory.oplTimer + 1) + ld a,e + or d + ret z + ld (ix + TimerFactory.oplTimer),0 + ld (ix + TimerFactory.oplTimer + 1),0 + ld ixl,e + ld ixh,d + call OPLTimer_Destruct + call OPLTimer_class.Delete + and a + ret + +; hl = callback +; ix = this +; ix <- timer +; f <- c: succeeded TimerFactory_CreateTurboRTimer: push hl call TurboRTimer_Detect