VOGONS


(Dual) OPL2 vs OPL3?

Topic actions

Reply 20 of 34, by superfury

User metadata
Rank l33t++
Rank
l33t++

I took a look at the OPL3 waveforms used by nuked-OPL (opl3.c).

It looks like it uses a weird waveform?
It includes the addition of 1024 to the waveform, which is fine.

But it multiplies the exponential table output by 2 and divides by the upper bits instead of multiplying it (as is documented for the OPLx reverse engineering and works in UniPCemu)?
When I try to replace the formula:

	return sign * (DOUBLE)(OPL2_ExpTable[v & 0xFF] + 1024) * pow(2.0, (DOUBLE)(v >> 8)); //Lookup normally with the specified sign, mantissa(8 bits translated to 10 bits) and exponent(3 bits taken from the high part of the input)!

with the formula from OPL3_EnvelopeCalcExp from nuked-OPL (https://github.com/nukeykt/Nuked-OPL3/blob/master/opl3.c), the results are all weird for the first quarter of the sine wave dump?
The waveforms get all screwed up?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 21 of 34, by superfury

User metadata
Rank l33t++
Rank
l33t++

When I disable the invertion of the exponential table input (v in the above formulas), I seem to instead go from high to low, instead getting a weird format that goes from high to low over and over again, producing corrupt waveforms instead.

Any idea what the correct scaling of the Exponential table output is? How is it supposed to be read?

Right now, the current commits got:

	//Exponential lookup also reverses the input, since it's a -logSin table!
//Exponent = x/256
//Significant = ExpTable[v%256]+1024
//Output = Significant * (2^Exponent)
DOUBLE sign;
word significand;
word exponent;
#ifdef IS_LONGDOUBLE
sign = (v & SIGNBIT) ? -1.0L : 1.0L; //Get the sign first before removing it! Reverse the sign to create proper output!
#else
sign = (v & SIGNBIT) ? -1.0 : 1.0; //Get the sign first before removing it! Reverse the sign to create proper output!
#endif
v &= SIGNMASK; //Sign off!
if (v>=MaximumExponential) v = MaximumExponential;
//v = MaximumExponential - v; //For some reason, this seems to be inverted?
//Reverse the range given! Input 0=Maximum volume, Input max=No output.
significand = ((OPL2_ExpTable[v & 0xFF] + 1024)<<(OPL3?1:0)); //Significand, converted to 10 bits significand.
exponent = (v >> 8); //Exponent!
return sign * (DOUBLE)ldexp(significand,exponent); //Convert floating point to value!

Entry #0 is supposed to have the highest value, with the last entry having the lowest value (and maximum attenuation) from what I know of the chip?
But using normal integer numbers as input to this causes a really weird output, with part of sinuses at maximum volume getting distorted for part of the input (probably due to the weird exponent behaviour).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 22 of 34, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've been thinking a bit...

Is it true that (thinking as 4-OP channels as a dual-OPL2 setup, with the first 3 channels being fully on chip #0 (the dual-OPL2 left chip) and the second 3 channels being fully on chip #1 (the dual-OPL2 right chip), the 4-OP channel operators, when looked at a 'single' dual-OPL2 chip being:
- channel #0 operator 1 is channel #0 operator 1
- channel #0 operator 2 is channel #0 operator 2
- channel #0 operator 3 is channel #3 operator 1
- channel #0 operator 4 is channel #3 operator 2
- channel #1 operator 1 is channel #1 operator 1
- channel #1 operator 2 is channel #1 operator 2
- channel #1 operator 3 is channel #4 operator 1
- channel #1 operator 4 is channel #4 operator 2
- channel #2 operator 1 is channel #2 operator 1
- channel #2 operator 2 is channel #2 operator 2
- channel #2 operator 3 is channel #5 operator 1
- channel #2 operator 4 is channel #5 operator 2

Also, for operators 3&4, their register A0/B0h sources are redirected to the ones that operators 1/2 use, thus controlling frequency number, block number and note on for those channels too.

The bottom 3 bits of the 4-OP channel register control channel 0-2 for the first 'chip'(or bank in the official documentation), with the higher 3 bits control the second 'chip'/bank.

Finally, the AM mode selection bit that usually toggles between FM and AM mode of a 2-OP channel effectively gets combined for both lower and upper 4-OP channels to form a 2-bit 4-OP mode that determines the 4-OP mode (FM-FM, FM-AM, AM-FM or AM-AM) the enabled 4-OP channel uses to link the channels in FM modulation mode or AM(effectively summing to output for the later channels, with 4th operator output always being in 'AM' mode as it were, with the others being configured depending on the 2-bit combination of FM/AM bits in the two channels that are linked together on the bank?

Effectively, this results in channels x and x+3 being linked, controlled by channel x's Ax/Bxh registers.

With regard to the wiki, to effectively get the 4-OP mode order of the 4-OP modes explained (in it's 'mess' section), you'd have to lookup the 4-OP synthesis entry (which determines the 4-OP mode in the documented 'mess' order) by the lower channel's AM setting in bit 0 and the higher channel's AM setting in bit 1. Thus you obtain the 4 entry order described there (FM-FM, FM-AM, AM-FM, AM-AM). You can simply fill the lookup table's 4 entries with the link (set to perform 'AM' (summing to output) or clear to perform 'FM' (modulate next operator). The fourth operator bit isn't used, as it's always in 'AM' mode. This can be done using the lower bits of the entry's value, simply shifting out one bit every operator (except the last) to determine how to handle it's output.

Is that correct?

Side note:
Interestingly enough, I found on a vogons post (YMF262 no sound from Op3/Op4 in 4-OP mode) that on a real OPL2, the OPL3 bit being cleared (register 05h on the second bank) might disable writes to register 04h on the second bank but keep it's effect active, locking the 4-OP channels to their ON-state while OPL3 mode is disabled, unless register 04h is cleared before register 05h? Basically, it just write-protects register o4h, not affecting it's operation?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 23 of 34, by superfury

User metadata
Rank l33t++
Rank
l33t++

I think I managed to get the OPL3 4-operator mode fixed.

It looked like the channels weren't parsed correctly in 4-operator mode for some reason, so I did some looking into what makes 4-operator mode settings different.
Eventually found the cause: the 4-operator mode got some parameters for the upper channel incorrect. It was loading the upper channel's channel number, which it set to x+3 instead of x (loading it's settings from channels 3-5 instead of properly 0-2). This would affect feedback (disabled for these operators, so this wasn't used) and more importantly the vibrato LFO (based on Dosbox's formula), which affected it's fnum and block sources to load from the high channel, thus loading the wrong channel in 4-operator mode.

Having fixed that, the weird sounds that appeared on some OPL3 songs seem to have disappeared. 😁
Most notably the first song of The Lost Vikings' title theme, which seems to render correctly now, when reaching the second phase of the song.

Although maybe a bit low on generic volume, but that might be due to OPL3's adjusted volume formula for each channel.
My emulation simply gives each channel an equal part of the sample space (1/9th on OPL2, 1/18th on OPL3), so that might affect volume a bit.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 24 of 34, by superfury

User metadata
Rank l33t++
Rank
l33t++

Are there any other differences known between the OPL2 and OPL3 chips? As in, if for example the waveforms used differ, how do they differ while keeping compatibility with OPL2?
Or is the exponential table simply different? If so, how does the formula for it differ from OPL2 without breaking compatibility?

I know there maybe a difference in the attack envelope (mainly the difference in the 1st setting (value #0)), but are there any other differences in the waveforms or exponential tables?
I'd assume that those two still exist, maybe even unmodified from OPL2 to remain compatible?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 25 of 34, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've been thinking a bit..

On dual OPL2, writes to port 388h must be routed to both chips to perform mono (otherwise, it just would sound at the left channel for OPL2 songs).
But on OPL3, this might not need to be the case? If the OPL2 mode (mode bit in register 5 cleared), does the OPL3 respond to port 388h writes by writing just to the 'left' chip (the chip at 320h)? Or is it also routed to the chip at 322h?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 26 of 34, by OPLx

User metadata
Rank Member
Rank
Member
superfury wrote on 2025-07-02, 16:26:

I've been thinking a bit..

On dual OPL2, writes to port 388h must be routed to both chips to perform mono (otherwise, it just would sound at the left channel for OPL2 songs).
But on OPL3, this might not need to be the case? If the OPL2 mode (mode bit in register 5 cleared), does the OPL3 respond to port 388h writes by writing just to the 'left' chip (the chip at 320h)? Or is it also routed to the chip at 322h?

Regardless of the mode setting, port 388h/389h writes will only send data to the 'left' chip on the OPL3.
Depending on the sound card, writes to the 'right' chip usually be on port 38Ah/38Bh (usually non-sound blaster cards). Using the mentioned base address, if the 'left' chip is at 320h, then the 'right' chip would generally be at 322h.

Reply 27 of 34, by OPLx

User metadata
Rank Member
Rank
Member
superfury wrote on 2025-07-02, 13:05:
Are there any other differences known between the OPL2 and OPL3 chips? As in, if for example the waveforms used differ, how do t […]
Show full quote

Are there any other differences known between the OPL2 and OPL3 chips? As in, if for example the waveforms used differ, how do they differ while keeping compatibility with OPL2?
Or is the exponential table simply different? If so, how does the formula for it differ from OPL2 without breaking compatibility?

I know there maybe a difference in the attack envelope (mainly the difference in the 1st setting (value #0)), but are there any other differences in the waveforms or exponential tables?
I'd assume that those two still exist, maybe even unmodified from OPL2 to remain compatible?

OPL2/3 Frequency - The 1Hz-ish Difference talks about some of the differences.

The other differences I know of are:

  • The OPL2 has CSM support, the OPL3 doesn't.
  • The OPL2 has no pseudo-stereo, the OPL3 does.
  • The OPL2 only has 4 waveforms, the OPL3 adds 4 new ones (in addition to using the OPL2's)
  • There seems to be a subtle difference on how the OPL3 handles it's percussive mode timings compared to the OPL2. I'd have to check again when I have time though, but it doesn't really affect general use-cases.

For all intents and purposes, unless the 'NEW' bit at address 5h on the 'right' chip is set, the OPL3 will generally behave like the OPL2.

Reply 28 of 34, by superfury

User metadata
Rank l33t++
Rank
l33t++

Oh, my bad, the volume envelope's 0-setting is the same. Apparently the 0xF setting (the fastest rate) is different on OPL2? I think I had the OPL3 definition implemented all along (it being nothing in that case, or 0 cycles. It's based on Dosbox's handling of it from what I remember). What actually happens on OPL2? Simply 1 cycle (of 49.5xxHz)?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 29 of 34, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've been thinking a bit.

With OPL2 and OPL3 sound synthesis, OPL3 has a volume problem.
In my case, I've modified the OPL3 volume range to be half of the OPL2 chip's output (due to the Exponential table output being twice in range).

But that would cause the OPL2 sounds, which only use the 'left' chip, to be only half their expected volume.
Otherwise, volume would overflow 16-bit calculations. Or i'd need to make it clip the samples to 16-bit.

Edit: For now, went the clipping route. Each channel gets 1/9th of the output range. So running 10 or more channels on a single channel (that being left or right channel), it will start top clip when at full volume range (so +MAX or -MAX for more than 10 channels).
It will match the volume curve for the OPL2 synthesis now.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 30 of 34, by superfury

User metadata
Rank l33t++
Rank
l33t++

Looking on some emulation projects, I eventually found a java implementation of an OPL3 emulator.
https://github.com/gtaylormb/opl3_fpga/blob/m … /docs/OPL3.java (line 500 or 588 and beyond)

It seems to normalize the additive synthesis (and 4-OP modes too in the same way where additive synthesis is performed)? Like taking the average of 2-OP or 3-OP outputs (2 or 3 being the resulting channels effectively, which is 1, 2 or 3)?

Is this true on real OPL2/OPL3 chips too?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 31 of 34, by OPLx

User metadata
Rank Member
Rank
Member
superfury wrote on 2025-07-03, 16:34:
Looking on some emulation projects, I eventually found a java implementation of an OPL3 emulator. https://github.com/gtaylormb/o […]
Show full quote

Looking on some emulation projects, I eventually found a java implementation of an OPL3 emulator.
https://github.com/gtaylormb/opl3_fpga/blob/m … /docs/OPL3.java (line 500 or 588 and beyond)

It seems to normalize the additive synthesis (and 4-OP modes too in the same way where additive synthesis is performed)? Like taking the average of 2-OP or 3-OP outputs (2 or 3 being the resulting channels effectively, which is 1, 2 or 3)?

Is this true on real OPL2/OPL3 chips too?

Unfortunately, I'm not familiar with the hardware implementation details of both chips to be able to give a sound answer. The datasheets may have such information, but I can't recall ever seeing it.

Reply 32 of 34, by superfury

User metadata
Rank l33t++
Rank
l33t++
OPLx wrote on Yesterday, 02:31:
superfury wrote on 2025-07-03, 16:34:
Looking on some emulation projects, I eventually found a java implementation of an OPL3 emulator. https://github.com/gtaylormb/o […]
Show full quote

Looking on some emulation projects, I eventually found a java implementation of an OPL3 emulator.
https://github.com/gtaylormb/opl3_fpga/blob/m … /docs/OPL3.java (line 500 or 588 and beyond)

It seems to normalize the additive synthesis (and 4-OP modes too in the same way where additive synthesis is performed)? Like taking the average of 2-OP or 3-OP outputs (2 or 3 being the resulting channels effectively, which is 1, 2 or 3)?

Is this true on real OPL2/OPL3 chips too?

Unfortunately, I'm not familiar with the hardware implementation details of both chips to be able to give a sound answer. The datasheets may have such information, but I can't recall ever seeing it.

Right now, I've applied the 'division' by 2 or 3 (if needed) to the stage just before mixing the floating point 16-bit mixing step (which is after the Exponential table's output has been converted to rendering range.
So basically, the steps performed at this point is:
- Exponential table lookup (int_64 range). Doubly precalculated LUT (the second mixed step is done at the below step in one go at precalculation time).
- Normalized to -1.0 to 1.0 and then multiplied by 1024 (modulation range of a full sine wave(2*PI)). This is what is used for modulation. This and above step is merged into one LUT lookup (from one LUT with a multiplication onto it, signed 16-bit range).
- Converted to export by dividing by 1024 to create a -1.0-1.0 range again (floating point).
After these steps, the halfing, or 1/3rd-ing is performed (before summing to output samples).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 33 of 34, by OPLx

User metadata
Rank Member
Rank
Member

Do you have actual hardware to compare/test with? It would probably be useful, especially if it is possible to alter the input clock frequency making it possible to slow down the chip's operations.

Reply 34 of 34, by superfury

User metadata
Rank l33t++
Rank
l33t++
OPLx wrote on Yesterday, 22:18:

Do you have actual hardware to compare/test with? It would probably be useful, especially if it is possible to alter the input clock frequency making it possible to slow down the chip's operations.

Nope. Built all my emulation on documentation (and reverse engineering reports for the YM3812 chip). The only part I didn't build in the CPU/hardware source code rn is the volume envelopes, which I think were adapted from Dosbox from what I remember (as nothing is documented anywhere on those, not even ksl handling). I'd love to properly implement it myself, but there simply isn't ANY documentation on how the chip actually handles the curves.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io