I’ve updated SAM SID Player to version 1.1, addressing some issues with the original version:
Updated 6502 core
The recent core enhancements mean it’s now possible to trap SID writes from all instructions, without the need for hard-coded checks. Control register re-triggering now works correctly in all tunes rather than just the few previously supported cases.
Unfortunately, limited program space prevents the full 65C02 core being used,so the extra instructions have been replaced by NOPs of the appropriate size. This is still better than the previous behaviour of failing if an undocumented instruction was encountered. The updated core also includes a bug fix to the indirect indexed addressing using X, which wasn’t performing the indirect lookup correctly.
Additional playback rates
The previous version supported only 50Hz playback using SAM’s frame interrupt. This worked well with most tunes (taken from PAL C64 titles), but it made 60Hz NTSC tunes (such as Fairlight) sound sluggish, and anything requiring 100Hz or above sounded awful.
Generating 60Hz on a 50Hz machine is a bit of a challenge, requiring synchronisation to 6 different points across the frame, advancing to the previous point in the next frame to achieve the correct playback rate. In our case it also needs to work without stealing too much CPU time from the 6502 emulation running in the background. The 6 sync points divide the 312 raw display lines into 52 lines segments. The first point is simply the frame interrupt, which is nice and easy. With a 1-line adjustment, the final 4 points fall on the main screen area, and can be synchronised to using line interrupts at screen lines: 35, 87, 139 and 191.
That just leaves the second point at display line 52, which is 68-52=16 lines above the main screen area. Busy-looping from the frame interrupt would waste 1/6 of the total frame time, and the point is too early for a line interrupt, but not for another technique. MIDI writes are output at a fixed 31.25Kbaud, and generate an interrupt to signal when the transfer has completed, even if there’s no device present to receive the write. Using a cascading sequence of MIDI writes starting from the frame interrupt, we can regain control at the required point without having to wait for it. There is some interrupt processing overhead, but any remaining time is free for the main 6502 emulation.
A number of SID tunes also use the C64 programmable timer to generate custom speeds, which can be used to make the playback speed independent of PAL/NTSC model. 50/60Hz timers are supported the same way as PAL/NTSC tunes, as described above. 100Hz is used by a few tunes, and can be supported by adding a single line interrupt in the middle of the frame (312/2-68 = line 88).
The tune playback speed is detected automatically, using the speed bit array in the SID tune header and the active C64 timer frequency, with 50Hz used for other cases. If the playback rate is close enough to one of the supported speeds then it will be used instead. You can also override the playback speed with the following keys: 1=100Hz, 5=50Hz and 6=60Hz.
Large tune support
To simplify relocating the SID tune, the previous version required the tune be loaded at 49152 with a maximum size of 16K. This could be expanded to 28K by allowing the tune to be loaded directly after the 4K player code at 36864. That still doesn’t give enough space to load the 49K Ghouls n Goblins SID, which fills most of the available C64 RAM.
The new version now works with tunes up to the full 64K, including those that span the I/O area from &d000-dfff (which is where the SID player code runs). On the first playback the tune is relocated to the correct address, with subsequent plays using the existing player to save time. As with the previous version, a fresh copy of the SID player code is copied for each playback, to minimise the risk of tune players overwriting parts of it.
Keyboard control tweaks
The new version adds a mask for keys to ignore during playback, allowing the caller to limit the key selection causing the player to terminate. This allows the Next key to be ignored when there is no next tune to play, etc.