M CHANGES.md +2 -1
@@ 4,11 4,12 @@ VGMPlay MSX change log
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
M README.md +2 -1
@@ 26,7 26,8 @@ be manually decompressed to VGM with
[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:
M src/drivers/OPL3.asm +6 -2
@@ 25,10 25,12 @@ OPL3: MACRO ?base = OPL3_BASE_PORT, ?nam
; 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 @@ OPL3: MACRO ?base = OPL3_BASE_PORT, ?nam
; 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
M src/drivers/OPL4.asm +6 -4
@@ 31,9 31,10 @@ OPL4: MACRO ?fmbase, ?wavebase, ?name
; 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 @@ OPL4: MACRO ?fmbase, ?wavebase, ?name
; 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
A => src/timers/OPLTimer.asm +162 -0
@@ 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
M src/timers/Timer.asm +1 -0
@@ 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
M src/timers/TimerFactory.asm +46 -0
@@ 6,6 6,8 @@
TimerFactory: MACRO
lineTimer:
dw 0
+ oplTimer:
+ dw 0
vBlankTimer:
VBlankTimer
vBlankTimerFactory:
@@ 22,6 24,7 @@ TimerFactory: MACRO
; 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 @@ TimerFactory_Destroy:
call TimerFactory_DestroyLineTimer
pop ix
push ix
+ call TimerFactory_DestroyOPLTimer
+ pop ix
+ push ix
call TimerFactory_DestroyTurboRTimer
pop ix
ret
@@ 98,6 104,46 @@ TimerFactory_DestroyLineTimer:
; 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