# VOGONS

## Drums on truly real OPL2

### First post, by Electronic Genets

Rank Newbie
Rank
Newbie

I found differences in many emulators of OPL2/OPL3 drums.

DosBox wrote:
[…]
`1` `23`	//Snare Drum`4`	Bit32u sdVol = Op(3)->ForwardVolume(); // it is operator 16`5`	if ( !ENV_SILENT( sdVol ) ) {`6`		Bit32u sdIndex = ( 0x100 + (c2 & 0x100) ) ^ ( noiseBit << 8 );`7`		sample += Op(3)->GetWave( sdIndex, sdVol ); // it is also operator 16`8`	}``
opl3emu wrote:
[…]
`1`	/* Snare Drum (verified on real YM3812) */`2`	env = volume_calc(SLOT7_2); // it is operator 16`3`	if( env < ENV_QUIET )`4`	{`5`		/* base frequency derived from operator 1 in channel 7 */`6`		unsigned char bit8 = ((SLOT7_1->Cnt>>FREQ_SH)>>8)&1; // it is operator 13`78`		/* when bit8 = 0 phase = 0x100; */`9`		/* when bit8 = 1 phase = 0x200; */`10`		UINT32 phase = bit8 ? 0x200 : 0x100;`1112`		/* Noise bit XOR'es phase by 0x100 */`13`		/* when noisebit = 0 pass the phase from calculation above */`14`		/* when noisebit = 1 phase ^= 0x100; */`15`		/* in other words: phase ^= (noisebit<<8); */`16`		if (noise)`17`			phase ^= 0x100;`1819`		output[0] += op_calc(phase<<FREQ_SH, env, 0, SLOT7_2->wavetable) * 2;`20`	}``

Where is the truth? Does the Dosbox emulator wrongly emulates the Snare Drum or the code marked as "verified on real YM3812"? I know that the multiplier of 13 and 16 operator will be set both to 1 these two codes will generate the same effect... Can anyone please check Snare Drum with different multipliers for operator 13 and 16 on real OPL2/OPL3? There is used operator 13 or 16 for Phase Generator of Snare Drum?

I have no sound card with OPL to check it.

Author of the DINO-2e OPL2-like emulator.
DINO-2e is not OPL2 emulator.

### Reply 1 of 52, by leileilol

Rank l33t++
Rank
l33t++

Multipliers? DOSBox is doing bitwise operations here. It's meant to be quick. (it's also predated opl3emu by a bit) DOSBox FM isn't accurate to begin with given that it doesn't work in 49716hz by default, so that'd break the percussions anyway and is a dead giveaway to those who know and are familiar the real chip.

There's NukeYKT's emulator these days as the OPL emulator of the highest caliber, but that covers work from reverse engineering an OPL3 rather than a 2. It's also much slower.

### Reply 2 of 52, by Electronic Genets

Rank Newbie
Rank
Newbie
leileilol wrote:

Multipliers? DOSBox is doing bitwise operations here.

Yes, multipliers. Multiplier for Phase Generator for operator 13 and 16 is stored in bits 0-3 of registers 0x31 and 0x34.

Correct way is doing bitwise operation from operator 13 [slot 7 operator 0/modulater] or operator 16 [slot 7 operator 1/carrier] ?

leileilol, do you mean that emulator that it has perfectly implemented Snare Drum?

Sampling rate influences the feedback and noise generator. But does not affect the source operator selection (13 or 16 in this case) and the main sound of the melodic instrument (if incrementation of Phase Generator is proportionally changed). Edit: it will influence TC and HH due to non-linear use of bits 2 and 3 of Phase Generators.

I use on ARM 60MHz sampling rate 24000 with changed procedure for feedback and there are no audible differences in melody channels (in spectrum 30-10000 Hz). I cannot use Dosbox emulator and opl3emu emulator in my project due to many reasons:
1) Both are poorly optimized for RISC STM32F4. They will not be timed at the sampling rate for part of tracks.
2) Both emulate attack curve and decay curve with big quantization. I prefer for music smooth rising/falling curves. Thus I do not emulate real OPL2 in my project.
3) My OPL2-like emulator doesn't produce clicks during changes of volume while the sound is in progress . The goal is not the exact OPL2 emulation but the music quality.

I want to implement percussion for the purposes of compliance with OPL2 and to enable the device to play songs that use it. After compatibility at 49716 (on PC), I will rewrite the algorithm to 24000 to make it sound as similar as possible.

BTW. I know this ARM processor can operate at 180 MHz but I use 60 MHz due to power consumption.

Edit.

Nuked-OPL3 wrote:
[…]
`1`    if (slot->slot_num == 13) /* hh */ // it is operator 13`2`    {`3`        chip->rm_hh_bit2 = (phase >> 2) & 1;`4`        chip->rm_hh_bit3 = (phase >> 3) & 1;`5`        chip->rm_hh_bit7 = (phase >> 7) & 1;`6`        chip->rm_hh_bit8 = (phase >> 8) & 1;`7`    }`8`(...)`9`      case 16: /* sd */ `10`            slot->pg_phase_out = (chip->rm_hh_bit8 << 9)`11`                               | ((chip->rm_hh_bit8 ^ (noise & 1)) << 8);`12`            break;``

It seems, the dosbox OPL3 emulator works completely wrong (minimum for a snare drum). Nuked-OPL3 and opl3emu use operator 13 as Phase Generator and Operator 16 as Envelope Generator for Snare Drum.

It seems the phase generator of operator 16 is used nowhere if the percussion mode is enabled. Yamaha wasted at least one operator in this case.

Last edited by Electronic Genets on 2022-05-08, 11:23. Edited 1 time in total.

Author of the DINO-2e OPL2-like emulator.
DINO-2e is not OPL2 emulator.

### Reply 3 of 52, by OPLx

Rank Member
Rank
Member

NukeYKT OPL3 is a fairly accurate OPL3 emulator, so that should provide you with some hints and you wouldn't necessarily need actual hardware due to the said accuracy. Having said that, there are some subtle differences between the OPL2 and OPL3 that has been documented. In some of my tests there also seems to be a difference in the behavior of the the percussive mode between the chips, but since I was hunting down something else, I didn't take any detailed notes.

The documented OPL3 Percussion Mode Map documented is fairly accurate for the OPL3. So maybe that information will help you as well.

Depending on your needs, you may want to look into getting an OPL2 Audio Board or an OPL2LPT as having actual hardware on hand will save you a lot of headache in the long run.

### Reply 4 of 52, by Electronic Genets

Rank Newbie
Rank
Newbie

OPLx, Thanks for your reply. The midibox diagram will be very helpful for me. The code of dosbox OPL and the 2 other emulators does not indicate that the feedback is used. Very strange thing. Tom Tom does't use feedback (IMO next wasted potential, Yamaha could implement it as single additive channel with standard feedback) but the High Hat and Snare Drum use non-standard feedback (pre-AMP and pre-EG should be easier to implement to handle at 24000 without lost of sound and lost of performance).

I just wrote an algorithm to handle drums at 24000 (#define-able) and 49716 Hz sampling but I need to fix due the new documentation.

Regarding OPL3LPT/OPL2LPT... Nowadays, the USB version would be more helpful. I know I can buy another USB to LPT adapter. BTW. My STM32F4 kit is cheaper than OPL3LPT. After unfavorable strict political changes (which seem to be endless 🙁), I am unemployed, so now I cannot buy this device now. But thanks for the tips on how to check each parameter of OPL2/OPL3 in the future.

Edit.
I tried to create account on the midibox to query more information about feedback on operator 17 (in melodic mode only operators 0,1,2,6,7,8,12,13,14 has feedback, 15,16,17 has not) but I get message "You are not permitted to register a user account with this site."...

I do not know what means "feedback fixed at all", where is stored operator "34(17)" feedback value, does the algorithm is the same (half of sum of last two samples)...
"Wave setting can mute feedback or OSC input". When?

Probably midibox percussion documentation is also buggy (operator 17 has no official feedback register).

Author of the DINO-2e OPL2-like emulator.
DINO-2e is not OPL2 emulator.

### Reply 5 of 52, by superfury

Rank l33t++
Rank
l33t++

I've tried implementing the OPL2 drums in the past as well, based on known documentation.
But although the melodic voices were rendering correctly, the drums never comes out correct?

`1`OPTINLINE float adlibsample(uint8_t curchan, word phase7_1, word phase8_2) {`2`	byte op6_1, op6_2, op7_1, op7_2, op8_1, op8_2; //The four slots used during Drum samples!`3`	word tempop_phase; //Current phase of an operator!`4`	float result;`5`	float immresult; //The operator result and the final result!`6`	byte op1,op2; //The two operators to use!`7`	float op1frequency, op2frequency;`8`	curchan &= 0xF;`9`	if (curchan >= NUMITEMS(adlibch)) return 0; //No sample with invalid channel!`1011`	//Determine the modulator and carrier to use!`12`	op1 = adliboperators[0][curchan]; //First operator number!`13`	op2 = adliboperators[1][curchan]; //Second operator number!`14`	op1frequency = adlibfreq(op1); //Load the modulator frequency!`15`	op2frequency = adlibfreq(op2); //Load the carrier frequency!`1617`	if (adlibpercussion && (curchan >= 6) && (curchan <= 8)) //We're percussion?`18`	{`19`		#ifndef ADLIB_RHYTHM`20`		return 0.0f; //Disable percussion!`21`		#else`22`		INLINEREGISTER word tempphase;`23`		result = 0; //Initialise the result!`24`		//Calculations based on http://bisqwit.iki.fi/source/opl3emu.html fmopl.c`25`		//Load our four operators for processing!`26`		op6_1 = adliboperators[0][6];`27`		op6_2 = adliboperators[1][6];`28`		op7_1 = adliboperators[0][7];`29`		op7_2 = adliboperators[1][7];`30`		op8_1 = adliboperators[0][8];`31`		op8_2 = adliboperators[1][8];`32`		switch (curchan) //What channel?`33`		{`34`			case 6: //Bass drum?`35`				//Generate Bass drum samples!`36`				//Special on Bass Drum: Additive synthesis(Operator 1) is ignored.`3738`				//Calculate the frequency to use!`39`				result = 0.0f;`40`				if (adlibop[op6_2].volenvstatus) //Running?`41`				{`42`					result = calcOperator(6, op6_1, op6_1, op6_1, adlibfreq(op6_1), 0.0f, 0x00); //Calculate the modulator for feedback!`4344`					if (adlibch[6].synthmode) //Additive synthesis?`45`					{`46`						result = calcOperator(6, op6_2, op6_2, op6_2, adlibfreq(op6_2), 0.0f, 0x08); //Calculate the carrier without applied modulator additive!`47`					}`48`					else //FM synthesis?`49`					{`50`						result = calcOperator(6, op6_2, op6_2, op6_2, adlibfreq(op6_2), result, 0x08); //Calculate the carrier with applied modulator!`51`					}`52`				}`5354`				return result; //Apply the exponential! The volume is always doubled!`55`				break;`5657`				//Comments with information from fmopl.c:`58`				/* Phase generation is based on: */`59`				/* HH  (13) channel 7->slot 1 combined with channel 8->slot 2 (same combination as TOP CYMBAL but different output phases) */`60`				/* SD  (16) channel 7->slot 1 */``
`61`				/* TOM (14) channel 8->slot 1 */`62`				/* TOP (17) channel 7->slot 1 combined with channel 8->slot 2 (same combination as HIGH HAT but different output phases) */`6364`			`65`				/* Envelope generation based on: */`66`				/* HH  channel 7->slot1 */`67`				/* SD  channel 7->slot2 */`68`				/* TOM channel 8->slot1 */`69`				/* TOP channel 8->slot2 */`70`				//So phase modulation is based on the Modulator signal. The volume envelope is in the Carrier signal (Hi-hat/Tom-tom) or Carrier signal().`71`			case 7: //Hi-hat(Carrier)/Snare drum(Modulator)? High-hat uses modulator, Snare drum uses Carrier signals.`72`				immresult = 0.0f; //Initialize immediate result!`73`				if (adlibop[op7_1].volenvstatus) //Hi-hat on Modulator?`74`				{`75`					//Derive frequency from channel 7(modulator) and 8(carrier).`76`					tempop_phase = phase7_1; //Save the phase!`77`					tempphase = (tempop_phase>>2);`78`					tempphase ^= (tempop_phase>>7);`79`					tempphase |= (tempop_phase>>3);`80`					tempphase &= 1; //Only 1 bit is used!`81`					tempphase = tempphase?(0x200|(0xD0>>2)):0xD0;`82`					tempop_phase = phase8_2; //Calculate the phase of channel 8 carrier signal!`83`					if (((tempop_phase>>3)^(tempop_phase>>5))&1) tempphase = 0x200|(0xD0>>2);`84`					if (tempphase&0x200)`85`					{`86`						if (OPL2_RNG) tempphase = 0x2D0;`87`					}`88`					else if (OPL2_RNG) tempphase = (0xD0>>2);`89`					result = calcOperator(8, op8_2,op8_2,op7_1,adlibfreq(op8_2), convertphase(tempphase), 0x4B); //Calculate the modulator, but only use the current time(position in the sine wave)!`90`					immresult += result; //Apply the tremolo!`91`				}`92`				if (adlibop[op7_2].volenvstatus) //Snare drum on Carrier volume?`93`				{`94`					//Derive frequency from channel 0.`95`					tempphase = 0x100 << ((phase7_1 >> 8) & 1); //Bit8=0(Positive) then 0x100, else 0x200! Based on the phase to generate!`96`					tempphase ^= (OPL2_RNG << 8); //Noise bits XOR'es phase by 0x100 when set!`97`					result = calcOperator(7, op7_2,op7_2,op7_2,adlibfreq(op7_2), convertphase(tempphase), 0x40); //Calculate the carrier with applied modulator!`98`					immresult += result; //Apply the tremolo!`99`				}`100`				result = immresult; //Load the resulting channel!`101`				//result *= 0.5f; //We only have half(two channels combined)!`102`				return result; //Give the result, converted to short!`103`				break;`104`			case 8: //Tom-tom(Carrier)/Cymbal(Modulator)? Tom-tom uses Modulator, Cymbal uses Carrier signals.`105`				immresult = 0.0f; //Initialize immediate result!`106`				if (adlibop[op8_1].volenvstatus) //Tom-tom(Modulator)?`107`				{`108`					result = calcOperator(8, op8_1, op8_1, op8_1, adlibfreq(op8_1), 0.0f, 0xA); //Calculate the carrier without applied modulator additive! Ignore volume!`109`					immresult += result; //Apply the exponential!`110`				}`111`				if (adlibop[op8_2].volenvstatus) //Cymbal(Carrier)?`112`				{`113`					//Derive frequency from channel 7(modulator) and 8(carrier).`114`					tempop_phase = phase7_1; //Save the phase!`115`					tempphase = (tempop_phase>>2);`116`					tempphase ^= (tempop_phase>>7);`117`					tempphase |= (tempop_phase>>3);`118`					tempphase &= 1; //Only 1 bit is used!`119`					tempphase <<= 9; //0x200 when 1 makes it become 0x300`120`					tempphase |= 0x100; //0x100 is always!`121`					tempop_phase = phase8_2; //Calculate the phase of channel 8 carrier signal!`122`					if (((tempop_phase>>3)^(tempop_phase>>5))&1) tempphase = 0x300;`123`					`124`					result = calcOperator(8, op8_2,op8_2,op8_2, adlibfreq(op8_2), convertphase(tempphase), 0x41); //Calculate the carrier with applied modulator! Use volume!`125`					immresult += result; //Apply the exponential!`126`				}`127128`				//Advance the shared percussion channel by 7-1 and 8-2!`129`				result = calcOperator(7, op7_1, op7_1, op7_1, adlibfreq(op7_1), 0.0f, 0); //Calculate the modulator, but only use the current time(position in the sine wave)!`130`				result = calcOperator(8, op8_2, op8_2, op8_2, adlibfreq(op8_2), 0.0f, 0); //Calculate the carrier with applied modulator! Use volume!`131132`				result = immresult; //Load the resulting channel!`133`				//result *= 0.5f; //We only have half(two channels combined)!`134`				return result; //Give the result, converted to short!`135`				break;`136`			default:`137`				break;`138`		}`139`		#endif`140`		//Not a percussion channel? Pass through!`141`	}`142143`	//Operator 1!`144`	//Calculate the frequency to use!`145`	result = calcOperator(curchan, op1,op1,op1, op1frequency, 0.0f,0x80); //Calculate the modulator for feedback!`146147`	if (adlibch[curchan].synthmode) //Additive synthesis?`148`	{`149`		result += calcOperator(curchan, op2,op2,op2, op2frequency, 0.0f,0x00); //Calculate the carrier without applied modulator additive!`150`	}`151`	else //FM synthesis?`152`	{`153`		result = calcOperator(curchan, op2,op2,op2, op2frequency, result, 0x00); //Calculate the carrier with applied modulator!`154`	}`155156`	return result; //Give the result!`157`}``

Anyone here knows anything about this? It's the only part of my OPL2 emulation that doesn't work correctly somehow?

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

### Reply 6 of 52, by Electronic Genets

Rank Newbie
Rank
Newbie
superfury wrote:
tempphase <<= 9; //0x200 when 1 makes it become 0x300 tempphase |= 0x100; //0x100 is always! tempop_phase = phase8_2; […]

tempphase <<= 9; //0x200 when 1 makes it become 0x300
tempphase |= 0x100; //0x100 is always!
tempop_phase = phase8_2; //Calculate the phase of channel 8 carrier signal!
if (((tempop_phase>>3)^(tempop_phase>>5))&1) tempphase = 0x300;

result = calcOperator(8, op8_2,op8_2,op8_2, adlibfreq(op8_2), convertphase(tempphase), 0x41); //Calculate the carrier with applied modulator! Use volume!
immresult += result; //Apply the exponential!
}

NukedOPL3 has 0x80 here.
https://github.com/nukeykt/Nuked-OPL3/blob/ma … er/opl3.c#LC627

BTW. Too many #defines (code is illegible), too many function calls and instructions in time critical sections (for example before calcOperator, I predict bad performance). Also too many exclamations and queries in the code. 😉 Use camelCase or under_lines in names.

I have the impression that everyone is copying from their predecessors, almost no one has done a chip test and that is why almost all emulators sound bad for the drums. Superfury Do you copied Top Cymbal from opl3emu?

I would really appreciate files with drums samples (only HH, TC and SD) in WAV 49716 Hz (without other sounds) from original OPL2 or OPL3 and RAW, DRO or HSC* files for tests. My application recognizes these formats.

*) If you use other format, please tell me what DOS application (with version number) do you use to play it on DosBox (to dump it to DRO format).

superfury wrote:

//Load our four operators for processing!

Four? Really? I see six.

Author of the DINO-2e OPL2-like emulator.
DINO-2e is not OPL2 emulator.

### Reply 7 of 52, by superfury

Rank l33t++
Rank
l33t++
Electronic Genets wrote on 2022-05-09, 19:52:
NukedOPL3 has 0x80 here. https://github.com/nukeykt/Nuked-OPL3/blob/ma … er/opl3.c#LC627 […]
superfury wrote:
tempphase <<= 9; //0x200 when 1 makes it become 0x300 tempphase |= 0x100; //0x100 is always! tempop_phase = phase8_2; […]

tempphase <<= 9; //0x200 when 1 makes it become 0x300
tempphase |= 0x100; //0x100 is always!
tempop_phase = phase8_2; //Calculate the phase of channel 8 carrier signal!
if (((tempop_phase>>3)^(tempop_phase>>5))&1) tempphase = 0x300;

result = calcOperator(8, op8_2,op8_2,op8_2, adlibfreq(op8_2), convertphase(tempphase), 0x41); //Calculate the carrier with applied modulator! Use volume!
immresult += result; //Apply the exponential!
}

NukedOPL3 has 0x80 here.
https://github.com/nukeykt/Nuked-OPL3/blob/ma … er/opl3.c#LC627

BTW. Too many #defines (code is illegible), too many function calls and instructions in time critical sections (for example before calcOperator, I predict bad performance). Also too many exclamations and queries in the code. 😉 Use camelCase or under_lines in names.

I have the impression that everyone is copying from their predecessors, almost no one has done a chip test and that is why almost all emulators sound bad for the drums. Superfury Do you copied Top Cymbal from opl3emu?

I would really appreciate files with drums samples (only HH, TC and SD) in WAV 49716 Hz (without other sounds) from original OPL2 or OPL3 and RAW, DRO or HSC* files for tests. My application recognizes these formats.

*) If you use other format, please tell me what DOS application (with version number) do you use to play it on DosBox (to dump it to DRO format).

superfury wrote:

//Load our four operators for processing!

Four? Really? I see six.

Yeah, it's indeed 6 operators. Perhaps they were less originally (before I started to work on it). It's been a long while back already.

Looking at my sources (not that there's many sources on the workings of the OPL2 percussion to begin with), i've based the percussion on opl3emu-1.1.1 (from what I can find in my documentation folder).
Although even with all that it still doesn't work properly yet.

Melodic channels do work properly (they sound correct for the tested games). The audio samples are actually generated at the actual speed of the adlib (14318180.0 / 288.0 Hz). These are sampled at that rate, then lowpass filtered at 15392Hz.

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

### Reply 8 of 52, by Electronic Genets

Rank Newbie
Rank
Newbie
superfury wrote:

These are sampled at that rate, then lowpass filtered at 15392Hz.

I see you also cannot hear this feedback squeak that makes all music uncomfortable. I cut it with special algorithm for feedback (only if OPL_FREQ is set to 49716 the filter is disabled for full compatibility).

Don't compare game sounds on DosBox. DosBox OPL3 emulator is full of bugs. One is described in this thread. Second... After second running HSC Player strange things are happening with music and with WAV / OPL command recording (track: WVOLDTITLE, OS: Ubuntu). Third: bad sampling frequency (44100) without any algorithm to avoid differences.

Did you try to connect real OPL2LPT as sound card during game tests and then check your percussion implementation?

For TC phase you can try:

Electronic Genets wrote:
uint16_t hh=(p13>>22); // normalize phase to 10-bit. uint16_t tc=(p17>>22); uint16_t phase=0x80; phase|=(((hh>>2)^(hh […]

uint16_t hh=(p13>>22); // normalize phase to 10-bit.
uint16_t tc=(p17>>22);
uint16_t phase=0x80;
phase|=(((hh>>2)^(hh>>7)) | ((hh>>3)^(tc>>5)) | ((tc>>3)^(tc>>5)))<<9; // optionally &0x3ff

For HH similar. Which instrument wrongly sounds in your implementation?

Author of the DINO-2e OPL2-like emulator.
DINO-2e is not OPL2 emulator.

### Reply 9 of 52, by superfury

Rank l33t++
Rank
l33t++
Electronic Genets wrote on 2022-05-09, 23:44:
I see you also cannot hear this feedback squeak that makes all music uncomfortable. I cut it with special algorithm for feedback […]
superfury wrote:

These are sampled at that rate, then lowpass filtered at 15392Hz.

I see you also cannot hear this feedback squeak that makes all music uncomfortable. I cut it with special algorithm for feedback (only if OPL_FREQ is set to 49716 the filter is disabled for full compatibility).

Don't compare game sounds on DosBox. DosBox OPL3 emulator is full of bugs. One is described in this thread. Second... After second running HSC Player strange things are happening with music and with WAV / OPL command recording (track: WVOLDTITLE, OS: Ubuntu). Third: bad sampling frequency (44100) without any algorithm to avoid differences.

Did you try to connect real OPL2LPT as sound card during game tests and then check your percussion implementation?

For TC phase you can try:

Electronic Genets wrote:
uint16_t hh=(p13>>22); // normalize phase to 10-bit. uint16_t tc=(p17>>22); uint16_t phase=0x80; phase|=(((hh>>2)^(hh […]

uint16_t hh=(p13>>22); // normalize phase to 10-bit.
uint16_t tc=(p17>>22);
uint16_t phase=0x80;
phase|=(((hh>>2)^(hh>>7)) | ((hh>>3)^(tc>>5)) | ((tc>>3)^(tc>>5)))<<9; // optionally &0x3ff

For HH similar. Which instrument wrongly sounds in your implementation?

Well, in my implementation, all of the RNG based ones (only the base drum seems to function properly as far as I remember).
This was the thread where I mentioned my findings on it back then: Can anyone help me fix my Adlib(OPL2) emulation?

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

### Reply 10 of 52, by superfury

Rank l33t++
Rank
l33t++

Also, the 15392Hz low-pass filter is correct, according to reverse engineering:
http://yehar.com/blog/?p=665#comment-29339

Edit: Also, is the phase conversion used correct? I'm using the phase functions to convert the modulator/carrier phase back and forth between the floating point format of the modulator/carrier output, but I'm not sure it's the correct range (or what range should be used in that case).

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

### Reply 11 of 52, by Electronic Genets

Rank Newbie
Rank
Newbie
superfurry wrote:

and forth between the floating point format of the modulator/carrier output

Due to loss of accuracy of floating point numbers at each operation you should not use them to calculate phase of drum (HH, TC, SD) instruments. If your value 16384 will be rounded to 16383.997 your output of HH and TC will be incorrect. SD will be sligtly affected. I also dont know what is result of 16383.997>>5.
I use 10.22 fixed point format for phase and -32767 to 32767 integer range for modulator/carrier output and there is no risk of this inaccuracy.

Author of the DINO-2e OPL2-like emulator.
DINO-2e is not OPL2 emulator.

### Reply 12 of 52, by superfury

Rank l33t++
Rank
l33t++
Electronic Genets wrote on 2022-05-11, 08:33:
superfurry wrote:

and forth between the floating point format of the modulator/carrier output

Due to loss of accuracy of floating point numbers at each operation you should not use them to calculate phase of drum (HH, TC, SD) instruments. If your value 16384 will be rounded to 16383.997 your output of HH and TC will be incorrect. SD will be sligtly affected. I also dont know what is result of 16383.997>>5.
I use 10.22 fixed point format for phase and -32767 to 32767 integer range for modulator/carrier output and there is no risk of this inaccuracy.

Although I don't know how to calculate the frequency shifts in fixed point format.

UniPCemu simply uses two variables, using the formula 2*PI*frequency*time (where frequency*time is stored) as an input to the OPL2 lookup tables (which are in native OPL2 format, so fixed point outputs there, but not inputs).
When multiplying freq0 with time in the operator, you'll get a number between 0 and 1 specifying the effective phase.

Frequency*time results in a number that goes from 0.0-0.999999999 (reaching modulo 1.0 becoming 0 again) at the speed of 1Hz for the specified frequency (which input is a floating point number).

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

### Reply 13 of 52, by Electronic Genets

Rank Newbie
Rank
Newbie
superfury wrote:

Although I don't know how to calculate the frequency shifts in fixed point format.

Do you mean bit2, bit3, bit5 and bit7? If yes, it is very easy.

`1`// phaseCounter is in format 10.22 fixed point uint32_t phaseCounter`2`uint16_t tmp=phaseCounter>>22; // normalize to 10-bit  integer`3`uint8_t bit2=(tmp>>2)&1;`4`uint8_t bit3=(tmp>>3)&1;`5`uint8_t bit5=(tmp>>5)&1;`6`uint8_t bit7=(tmp>>7)&1;``

It will not work properly with variable point format...
Your phase 0..2π should be converted to range 0..1023 (1024 is unused, value for 1024 is equal value of 0). Tables of sinus in original OPL2 also have length 1024 (indexes 0..1023 or 0x000 to 0x3ff).

superfury wrote:

When multiplying freq0 with time in the operator, you'll get a number between 0 and 1 specifying the effective phase.

Frequency*time results in a number that goes from 0.0-0.999999999 (reaching modulo 1.0 becoming 0 again) at the speed of 1Hz for the specified frequency (which input is a floating point number).

It is strange. The value of phase is <0, 2π) not <0, 1)... 0 is represented internally in OPL2 as 0. 2π is represented in OPL2 internally as 1024. Range 0..1023. From this value you should extract bits needed for HH, CY and SD.
I have implemented it in this manner in my DINO-2e code. But I still have no feedback that output file sounds properly... Does really noone have YM3812 to check it?

BTW phase is calculated in math as ω⋅t=⋅f⋅t not as f⋅t.

Author of the DINO-2e OPL2-like emulator.
DINO-2e is not OPL2 emulator.

### Reply 14 of 52, by superfury

Rank l33t++
Rank
l33t++
Electronic Genets wrote on 2022-05-11, 10:53:
Do you mean bit2, bit3, bit5 and bit7? If yes, it is very easy. […]
superfury wrote:

Although I don't know how to calculate the frequency shifts in fixed point format.

Do you mean bit2, bit3, bit5 and bit7? If yes, it is very easy.

`1`// phaseCounter is in format 10.22 fixed point uint32_t phaseCounter`2`uint16_t tmp=phaseCounter>>22; // normalize to 10-bit  integer`3`uint8_t bit2=(tmp>>2)&1;`4`uint8_t bit3=(tmp>>3)&1;`5`uint8_t bit5=(tmp>>5)&1;`6`uint8_t bit7=(tmp>>7)&1;``

It will not work properly with variable point format...
Your phase 0..2π should be converted to range 0..1023 (1024 is unused, value for 1024 is equal value of 0). Tables of sinus in original OPL2 also have length 1024 (indexes 0..1023 or 0x000 to 0x3ff).

superfury wrote:

When multiplying freq0 with time in the operator, you'll get a number between 0 and 1 specifying the effective phase.

Frequency*time results in a number that goes from 0.0-0.999999999 (reaching modulo 1.0 becoming 0 again) at the speed of 1Hz for the specified frequency (which input is a floating point number).

It is strange. The value of phase is <0, 2π) not <0, 1)... 0 is represented internally in OPL2 as 0. 2π is represented in OPL2 internally as 1024. Range 0..1023. From this value you should extract bits needed for HH, CY and SD.
I have implemented it in this manner in my DINO-2e code. But I still have no feedback that output file sounds properly... Does really noone have YM3812 to check it?

BTW phase is calculated in math as ω⋅t=⋅f⋅t not as f⋅t.

The original table is already used and it's output parsed properly (according to the reverse engineering of the chip).

The 2*PI*f*t is already used. In UniPCemu, the last 'f' is stored in adlibop.freq0(and input to the calcOperator function as well) and t is increasing with each sample by 1/samplerate and stored at adlibop.time.
samplerate is the adlib chip frequency in this case (~47kHz, the 14318180/288 to be exact)).
But still, what is the value of phasecounter in this case? How to calculate it from the f*t value(2*pi is a full sinus period after all, so it can be ignored?)?

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

### Reply 15 of 52, by Electronic Genets

Rank Newbie
Rank
Newbie

Superfury Probably your result in range <0, 1) should be multiplied by 1024 and then extract these bits. But variable-point formats are inaccurate.

Author of the DINO-2e OPL2-like emulator.
DINO-2e is not OPL2 emulator.

### Reply 16 of 52, by superfury

Rank l33t++
Rank
l33t++
Electronic Genets wrote on 2022-05-11, 16:36:

Superfury Probably your result in range <0, 1) should be multiplied by 1024 and then extract these bits. But variable-point formats are inaccurate.

You keep saying that, but how would I create an 'accurate' version then? Both f and t in the formula are floating point after all.
Also, that's exactly what 'getphase()' already does.

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

### Reply 17 of 52, by Electronic Genets

Rank Newbie
Rank
Newbie

Superfury, look at opl3emu and nukedOPL3. I use format 10.22 in the DINO-2e OPL-2 like emulator. In this case the floating point operations are not needed. Yamaha also doesn't need them and Yamaha doesn't need multiplying (all done by fixed point addition, xor, and, or, shifts and arrays).

f - calculated as 10.22 "increment" (Δphase) added after each calculation step.
t - subsequent steps. In my code there are no time and multiplying f by t. 😉

BTW. Original Yamaha chips used 10.10 (20 bits) fixed point format for phase generator.

Author of the DINO-2e OPL2-like emulator.
DINO-2e is not OPL2 emulator.

### Reply 18 of 52, by superfury

Rank l33t++
Rank
l33t++
Electronic Genets wrote on 2022-05-11, 16:52:
Superfury, look at opl3emu and nukedOPL3. I use format 10.22 in the DINO-2e OPL-2 like emulator. In this case the floating point […]

Superfury, look at opl3emu and nukedOPL3. I use format 10.22 in the DINO-2e OPL-2 like emulator. In this case the floating point operations are not needed. Yamaha also doesn't need them and Yamaha doesn't need multiplying (all done by fixed point addition, xor, and, or, shifts and arrays).

f - calculated as 10.22 "increment" (Δphase) added after each calculation step.
t - subsequent steps. In my code there are no time and multiplying f by t. 😉

BTW. Original Yamaha chips used 10.10 (20 bits) fixed point format for phase generator.

So the increase is (frq<<mul)*ksl, where ksl=0,0.5,0.25,1.0?

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

### Reply 19 of 52, by Electronic Genets

Rank Newbie
Rank
Newbie
superfury wrote:

So the increase is (frq<<mul)*ksl, where ksl=0,0.5,0.25,1.0?

Do you notice that 0.0, 0.25, 0.5 and 1.0 is multiply of 0.25? The "increment" in OPL2 was limited to 10.10 accurancy.

Author of the DINO-2e OPL2-like emulator.
DINO-2e is not OPL2 emulator.