Jump to content

Another SID 2 VERA demo


Recommended Posts

This has been done before (and with better results IMO) but my approach is different...

This is a demo of my SID ripper. Instead of patching a SID to run on X16, this utility generates a byte stream of the converted reg values, and the X16 program simply loads the file and streams it to the VERA PSG.

The goal is to make a player routine with assembly and C API calls, as well as a standard data format. This will facilitate the creation of SFX/music tools that generate audio native to the X16 instead of having to convert output from other tools.

I was kind of in a chicken/egg scenario where any player code I wrote wouldn’t have any data to use, and any tool I wrote wouldn’t have anything to play it back. This utility has given me a source for PSG music so I can work on the audio library.

It’s already revealed one fact - music on PSG may very well be best suited to using a tracker style format because the raw, pre-computed data gets large very quickly. 

My next step is going to be to condense the byte stream in a way that doesn’t add load to the playback routines.

  • Like 4
Link to comment
Share on other sites

3 hours ago, Yazwho said:

Sounds great. Any tricks on debugging audio!?

On the X16 side:
If I get no audio, I'd look at the data to make sure it's actually there, and it actually contains stuff that should make sound. I may try manually poking a few of the values into the PSG from BASIC to see if I get sound that way. (this actually happened - I had a facepalm moment when I realized that I was using the wrong VRAM address for the PSG registers in my X16 routine - D'OH!!).

If I were to get nothing but screeching, garbled noise, I'd verify that the data from the file was being properly written into PSG. The emulator's debugger is great for that.
You can type commands to view memory addresses:
m1a000  --> shows the BANK1 ram where I'm storing the audio data in memory. Does this contain the same data as my ZSM file?
v1f9c0 --> shows the VRAM contents starting at the PSG register location. Do the values here match what was supposed to be written at this point?
If no, then my playback routine has a bug to track down.
If yes in both cases, then I have to assume my data itself is bad, so it's time to debug the converter.

For debugging the conversion program, I have it log everything it does.

The general loop in the converter is:

  1. trigger an IRQ so the SID player generates one frame's worth of SID writes
  2. when the IRQ finishes, snapshot the "SID" and convert the values to PSG equivalents
  3. write out any changes to the ZSM data stream
  4. repeat

I'll walk you though how I use the logged data....

For step 1:
the log shows all writes to the SID memory addresses:


d418: 0f -> 0f : G VOL
d402: 32 -> 64 : 0 PWlo
d403: 08 -> 08 : 0 PWhi

Having the register names really helps to easily comprehend what's going on. In this case, the SID writes are setting master volume (d418) from 0f -> 0f (no change) followed by a new pulse width value for voice 0. (PW is a 12-bit value so there are 2 writes). This is also an opportunity to verify the sanity of the emulation / SID player operation itself. Suppose a PW-Hi value of $c0 was written - this is invalid as the PW-Hi register doesn't use the top 4 bits. Maybe the original player wrote that for some reason, but if I see a lot of nonsense-looking values here, I'd try other SID files and if I get similar results, I know the VM/SID execution itself is broken somehow. (debugging THAT is outside the scope of this)

For steps 2 & 3:


  v00   v01   v02
   c0    00    00 : pan
   3f   +3f   +3f : volume
 0184 +0000 +0000 : freq
  +10   +00   +00 : pulsewidth
   00   +ff   +ff : waveform
delay = 1
Queueing updates: voice 0  WF|PW
Skipping silenced voice 1
Skipping silenced voice 2
Writing delay frame for delay=1
Writing 3 bytes of PSG updates to zsm file.
 -- 02 03 10

This starts with a dump of the PSG states. The + next to values indicates a "dirty" register - one which has not been written to the byte stream. If it gets updated again before being written out, only the latest value is eventually written. I was surprised to see how many redundant writes actually happen! Obviously, there's no need to put those in the output. These would waste RAM and CPU cycles on the X16 side. Instead, the program writes out only the most recent value @ snapshot time, and only if the voice isn't muted.

Above I can see that voice 0 pulsewidth is flagged +, and the value is $10 (16). Then the program shows that it wrote out the dirty PW value for v01 and skipped any output for v02 & v03 because those are muted (pan 0).

The last line shows the actual bytes written to the ZSM file (02 03 10).  (02 = VERA PSG, 03 = voice 0 register 3, 10 = value to write)

This behavior is consistent with what the program should do, so I see no problems. Had there been any inconsistency, it would lead me to where in the program I should go looking for bugs.

I can also use the PSG state dump to verify that the converted values make sense.

The original SID writes set voice 0 PW = $0864.
$0864 / $0FFF = ~0.52.
Vera PSG PW should be 0.52 * 31 (31 = max pulse width on Vera)
31 * 0.52 = 16.2  (~ $10 hex).
This is correct, so long as I haven't made any logic mistakes, this translation is working correctly.
I also check the other values for sanity if something sounds wrong.

Garbage In / Garbage Out:

If the converter's output is correct for the inputs I see, and they're being correctly written to the PSG, then that means the input itself is somehow faulty, my actual method of writing updates to the PSG generates errors, or else the output couldn't possibly match the real sound of a SID anyway..... (e.g.: SID has analog filters, VERA PSG has no such thing). For instance, the Linux box I ran this demo on is known to have occassional clicky glitches in audio coming from the X16 emulator where the same programs running on the Windows-based emu never have such glitches.

This is where I am now - obviously the results don't match a real SID's sound very well. At this point, it's time to use my ear and put on my thinking cap. For starters, I haven't implemented the ADSR envelopes at all yet, which is why notes are doing the wrong sustain and stuff. For instance, in the second Rambo tune, there's this annoying beeeeeeeeeeep over the top of the music. In the real tune, that's a string of morse code beeps. ADSR should fix that. But there's some clicky/garbly bits in there as well that I don't attribute to ADSR being missing. Now I just have to start playing around and seeing what I can improve or not. Another thing I haven't put any work into is accurate timing. This is definitely non-trivial because taking 60hz snapshots of a data stream that's (very often) being generated at 50hz, and generating audio in real time can lead to lots of errors.

Another thing I could do would be to make the X16 side play back at 50Hz updates instead of 60Hz for tunes that were generated by a SID at 50Hz. That would require using some alternative timing source - ostensibly the VIA chips, but the emulator doesn't support VIA timers yet, and even if it did, my goal is a program that plays sound at 1 update per frame - 60Hz. Making as perfect of a SID rendition as possible is not the ultimate goal, so if there's any glitchiness coming from that discrepancy, then it's just going to stay. 🙂

Edited by ZeroByte
  • Like 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Create New...

Important Information

Please review our Terms of Use