OPL3 C++ research implementation

An investigation into the behaviour of the Yamaha OPL3 chipset.
With a view to a more accurate emulator

Re: OPL3 C++ research implementation

Postby opl3 » Thu Aug 15, 2013 9:50 am

Hi sto, I started an IMF player yesterday for my own experiments, it is very easy to do. I already have players for my own reg-dump format, dosbox DRO dump and .LAA midi based files so IMF is not a problem.

I actually dug out some exact timing values for playing out .IMF files from Wolf3D. The sound init uses a base rate of 70 Hz, but for Adlib music it multiplies this base rate by 10, so 700 Hz timer interrupt is requested.
But it turns out the timer rate setting code has a slight miscalculation and uses 1192030 as the timer tick frequency, so the requested timer tick period is 1192030/700 which truncates to 1702 ticks. With the real timer frequency of approximately 1193182, the actual IMF timer frequency is something like 701.047 Hz. Because both the timer and OPL2 chip are run at least in theory from the same crystal, it means that for each IMF timer tick there is really 1702*12 crystal ticks and for each OPL sample there is 288 crystal ticks, so this is the time base that is correct. As it is not an integer, I will first just begin with the fact that in each IMF timer tick there are 1704*12 crystal ticks that results into exactly 71 OPL samples. I think nobody can hear the difference. This timing is Wolf3D specific so other 700Hz files may use different timer period. Again, close enough.

First test: Can you be more detailed? What other values like decay, sustain and release should be used? What frequency square wave? I don't know how much delay there is, but with attack rate 15 the output jumps directly to maximum when it does. AR 14 is already double speed (or double fast per sample), and AR 13 is the "reference" with one step per sample, and all values below that have 2^N integer samples per step. But it is not 2^N steps from setting the KEYON bit, there is some free-running timer that ticks away and divides the sample clock, and there is up to 2^N - 1 samples before you see the KEYON have an effect on the amplitude. And since attack starts from previous amplitude that is not necessarily 0, I think what happens with AR=0 is that the sound amplitude does not attack, but it stays where it is left off previously during decay, sustain or release. This can also be tested with RR=0 so the sound does not release at all, and thus SL sets the level it stays at KEYOFF event. Mind you, that after KON even the phase generator will have zero phase I hope, if there is no delay or the phase generator is accidentally loaded with the increment instead of zero. Fortunately there is a trick to run a waveform backwards, just set waveform parameters so that instead of increasing 1 waveform table entry per sample (512), you increase 1023 waveform table entries per sample which equals to -1 entry per sample. I think this means FNUM=1023, BLOCK=7 and MULTI=8x so phase increment is 0x7FE00 or 1023<<9.

Second test: Yes this is still on my todo list. I should try feedback with some other more complex waveform than square, maybe with waveforms #0, #1 and #3 (sine, sine with positive peaks only and negative peak is flat 0, or the rising quarter with flat during falling quarter). I think it would be safest with waveforms #1 and #3 as they do not have negative output, so the waveform is never negative. With sine, even if amplitude is minimum but the sound is running, it still alternates between 0 and -1 because of the sign flag. Oh, but I can of course start a sine wave with frequency of 0, so it will reset phase to 0 and stay there until I change FNUM. And hope the amplitude is small enough not to cause feedback at zero phase, it's only 12 in PCM value.

KSL: "...no other ROM's larger than 16 samples". The sin and exp tables are located in the "operator" block, as that is what converts phase and envelope info to waveform and to output values. There should be no output from the tables anywhere else like to the envelope generator. So this KSL algorithm could be a separate table with up to 16 entries that gets modified by amount indicated by FNUM and BLOCK. 128 entry table in C code should do it for all purposes, although bits need to be correctly shifted or dropped out when selecting between the 3 different KSL levels (4th being no KSL). Verifying this has been on my todo list as well. The odd interface might be a leftover from an older chip that had only no KSL or only 3dB/octave KSL, but I did not currently find any reference to this. I have a vague memory that some older chip had only 1 bit, but I may be wrong. I just need to calculate if these values can be safely determined with a full volume square wave sweep which should be easy. If many KSL attennuation values map to identical PCM output values then another waveform must be used and at different frequencies which is more difficult.
opl3
 
Posts: 55
Joined: Sun Sep 26, 2010 8:11 pm

Re: OPL3 C++ research implementation

Postby opl3 » Thu Aug 15, 2013 2:23 pm

For the KSL table, there is a pattern.

When you go in left-right direction (octave +/- 1), there is always +/- 3dB difference or +/- 128 in attennuation value, unless the attennuation value must be capped at 0, because there is no gain.

Therefore, you only need 16-entry table for 4 bits of FNUM. Shift and add octave to select how many 3dB steps required. Not many bits to store.
opl3
 
Posts: 55
Joined: Sun Sep 26, 2010 8:11 pm

Re: OPL3 C++ research implementation

Postby opl3 » Tue Aug 27, 2013 7:03 pm

I verified the KSL values. I wrote a program that controls ksl/fnum/block values of real chip playing square wave while writing the expected output to stdout and then it was a matter of looking at the PC logic analyzer serial decoder output if the values match or not. They did, always. I had no reason to doubt the values, as both 3dB and 1.5dB tables have such small attenuation values that they map only to single PCM output value. The 6dB table values are so large that many attenuation values could match the actual PCM output, but it is very unlikely that some mystery LSBs would come from some table. The 6dB table can only truly be verified with non-square waveform, but I think that is unneccesary and it is quite hard to use sine wave at multiple frequencies as the phase must be tracked very carefully.

So I got the same KSL addition table as sto as by chance I ended up using the same algorithm he already implemented based on carbon14 code. And the values do match the table figured out by carbon14 when the table is used as KSL subtract table (56-X).

The values in the table match exactly the 3dB/octave table in OPL4 datasheets, so the conversion from dB to attenuation value is just dB*256/6. And indeed the value is x<<1 when used as 6dB/oct and x>>1 when used as 1.5dB/oct table. No bits are lost in the shift. NTS bit has no effect to KSL values at all. It is just that simple, high 4 bits of FNUM, all 3 octave bits and two KSL selection bits. And sto seems to have the selection bits correctly (0, 3dB, 1.5dB, 6dB), while for example my understanding is that MAME ymf262.c v0.2 accidentally swaps the 3dB/oct and 1.5dB/oct values.

Funny thing is these are "binary decibels", meaning doubling of amplitude is exactly 6dB, not approximately 6.022 as in real world.
opl3
 
Posts: 55
Joined: Sun Sep 26, 2010 8:11 pm

Re: OPL3 C++ research implementation

Postby sto » Thu Aug 29, 2013 4:12 am

Sorry for not answering for some time, but I have to prepare a seminar about server cluster management. But here's a quick one about the KSL: There's a slight error in the table I used (an AFAIR also others use), namely [24, 32, 37, 40, 43, 45, 47, 48, 50, 51, 52, 53, 54, 55, 56] vs. [24, 32, 37, 40, 43, 45, 47, 49, 50, 51, 52, 53, 54, 55, 56] (48 vs. 49), have a look at it (blue=48, green=49):
ksl.jpg
ksl.jpg (24.91 KiB) Viewed 24471 times


About the envelope delay: I guess a sine wave would be better to test it, though that would depend on a phase reset on a raising KON edge (0->1 transition), which I honestly don't think occurs as a phase reset would normally lead to clicks in the output when it happens with a non-silent envelope value.

And I'm also working on feedback test cases in my spare time, namely I'm working on some GNU Octave code to figure out the best cases where the different feedback types give the most different output.

Many thanks for testing the KSL values ;)
sto
 
Posts: 60
Joined: Thu Nov 08, 2012 4:33 am

Re: OPL3 C++ research implementation

Postby opl3 » Thu Sep 12, 2013 1:07 pm

No, thank YOU! Thanks to your recent discussion about the on-chip ROMs, I started to think if the bit patterns of the third ROM that has 16 entries x 7 bits match the KSL table in some way. You know what, they did! In some way. The difference between entries in ROM match the differece between entries in your KSL table. Just take a look at the close-up decapsulation picture of OPL2 third ROM.
opl3
 
Posts: 55
Joined: Sun Sep 26, 2010 8:11 pm

Re: OPL3 C++ research implementation

Postby sto » Thu Sep 12, 2013 3:30 pm

Hmmm... 48 seems to be the correct value, not 49, so I guess the ROM has a miscalculated glitch ;)

If I understood it, the values in the ROM are our KSL values plus 8, and the MSB is on the right instead of on the left, so the order of the bits is b5...b0,b6 from left to right, with raising adresses from top to bottom. I can only guess that the effective KSL envelope offset is not calculated as "rom[fnum >> 6] - 8*(7-block)", but as "rom[fnum >> 6] + 8*(block-8)", which would explain the off-by-8 values.

// EDIT: "rom[fnum >> 6] + 8*(block-8)" is completely right, as "-8" is the one's complement of 7; the offset of 8 in the ROM compensates this.
sto
 
Posts: 60
Joined: Thu Nov 08, 2012 4:33 am

Re: OPL3 C++ research implementation

Postby opl3 » Sat Sep 14, 2013 4:21 pm

Very good work finding out how to use the original ROM bits in the algorithm!
I sort of let it be when I figured out what the third ROM was, as we already knew we have the correct KSL operation.

Now, back to the phase generator reset on key-on you just removed, I have some bad news for you ;)

I made a non-decaying sound so it does not die out. Using VGA vertical retrace as approximately 70Hz timebase, I set key-on high and low a few times. Basically the key-on is toggled every 710 samples. As you see, when the sound starts, it resets phase approximately 1420 samples later, which is the point the key-on toggles to high again.
Screenshot-pg_rst.png
Digital capture of phase generator reset at key-on
Screenshot-pg_rst.png (58.44 KiB) Viewed 24391 times


I also attached the raw sound waveform you can import into Audacity yourself. It is 16-bit signed PCM with two channels. Sampling rate 49716 close enough.

When I have some time I can also prove how the feedback works.

Edit: I did find it disturbing, that the first sample after key-on is not from sine table index 0 as the value would be 0x000c, but from sine table index 1 which makes the output to be 0x0025. Basically it is reset but it really looks like that the phase increment is then added to it, before converting the phase to output value. In my case the sound phase increment was 512, or to put it in other words, the waveform will advance to next waveform entry every sample (512>>9 == 1). What do you think?
Attachments
pg_rst.pcm.zip
2CH signed 16-bit PCM file
(2.84 KiB) Downloaded 4057 times
opl3
 
Posts: 55
Joined: Sun Sep 26, 2010 8:11 pm

Re: OPL3 C++ research implementation

Postby sto » Sat Sep 14, 2013 8:21 pm

Coincidentally, I figured the phase reset out on my own today after listening to one of my favorite songs again which had some very odd sounding (non-RYT-mode) base drum. Additionally, I'm already increasing the phase before using it, but I guess that was only luck. Thanks for investigating.

The phase reset makes it easier for the envelope-delay-test: Consider some sine wave with your FNUM and an envelope with AR=15. If the current envelope value (after decreasing it: env=calc; output(env)) is used, the first output sample after a KON would be very high; if the previous envelope value (output(env);env=calc) is used, it would be much lower if the initial envelope value was 511. More precisely, I'm talking about envelope values of 511,69,12 for a delayed and 69,12,4 for a non-delayed envelope; the key point is to do a KON=0 after 1 or 2 samples to limit the samples to the important ones.

PS: Hopefully, you understand my idea about the envelope; unfortunately I'm pretty bad at expressing my ideas/thoughts, even if I know exactly what I'm talking about. I guess that's the drawback of programming since the age of 11 :D
sto
 
Posts: 60
Joined: Thu Nov 08, 2012 4:33 am

Re: OPL3 C++ research implementation

Postby opl3 » Sun Sep 15, 2013 9:45 pm

sto wrote:Coincidentally, I figured the phase reset out on my own today after listening to one of my favorite songs again which had some very odd sounding (non-RYT-mode) base drum. Additionally, I'm already increasing the phase before using it, but I guess that was only luck. Thanks for investigating.

The phase reset makes it easier for the envelope-delay-test: Consider some sine wave with your FNUM and an envelope with AR=15. If the current envelope value (after decreasing it: env=calc; output(env)) is used, the first output sample after a KON would be very high; if the previous envelope value (output(env);env=calc) is used, it would be much lower if the initial envelope value was 511. More precisely, I'm talking about envelope values of 511,69,12 for a delayed and 69,12,4 for a non-delayed envelope; the key point is to do a KON=0 after 1 or 2 samples to limit the samples to the important ones.

PS: Hopefully, you understand my idea about the envelope; unfortunately I'm pretty bad at expressing my ideas/thoughts, even if I know exactly what I'm talking about. I guess that's the drawback of programming since the age of 11 :D


Yes I think I understand. You mean if there is some sample offset between phasegen reset and envelopegen start when key-on happens.

I did a test, but frankly the case with AR=15 is very clear. I made a sinewave sound that attacks immediately and it decays somewhat. Then I again toggle the keyon bit on and off. FNUM=1, BLOCK=7, MULTI=8x, which makes the phase increment 512.

Every time I turn on the key-on, no matter if the sound was running or not before, the first sample is 0x0025. It means, the envelope generator jumps to 0 immediately, and the phase generator was reset but it has already the phase increment 512 added, so the sinewave entry is 512>>9 or 1. So there is no delay.

This is of course more complex when AR is not 15 so it does not jump immediately.
Attachments
pg_eg.pcm.zip
PG vs EG delay, seems to be 0.
(4.37 KiB) Downloaded 3912 times
opl3
 
Posts: 55
Joined: Sun Sep 26, 2010 8:11 pm

Re: OPL3 C++ research implementation

Postby sto » Mon Sep 23, 2013 4:16 am

I started to write a document about the mathematical side of the OPL, let me know what you think. I'm trying to make it fun to read, though I'm not sure if I accomplish this. The goal is to have a document which is indepentent from any implementation-specific things, e.g., I will not include the register map of the OPL and only describe a single channel.

Please have a special look at the math conventions I'm using, as I'm not quite sure that it's the right way.
Attachments
opl3math.pdf.zip
(145.3 KiB) Downloaded 3913 times
sto
 
Posts: 60
Joined: Thu Nov 08, 2012 4:33 am

PreviousNext

Return to Yamaha OPL-3 research

Who is online

Users browsing this forum: No registered users and 1 guest

cron