VOGONS


Drums on truly real OPL2

Topic actions

Reply 20 of 52, by superfury

User metadata
Rank l33t++
Rank
l33t++
Electronic Genets wrote on 2022-05-11, 17:59:
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.

I was thinking it might not be that? More like shift right by 0(=1.0), 1(=0.5), 2(=0.25), MAX(the amount of bits in the variable to be exact, so for the OPL2 that would be 20?) (=0.0)?

When I look at the Incr being calculated by the tables in fmopl.c of opl3emu, I see:
FREQ_SH=16
Although FREQ_SH=10 would be more correct (*.10 instead of 16.16 floating point)?
freqbase=the adlib operating frequency? So ~49715(with numbers behind the decimal point as well, see my earlier formula on that ((14318180/288) Hz)).
fn_tab=[i*64*freqbase*(1<<(FREQ_SH-10))][ i=0-1024 ]
The -10 in that formula is the 1024 steps in the amount behind the full range, so 1/1024th(1 index out of 1024 to be exact) of 64 times the base frequency. 64 is 6 times shifted left, but I don't know why yet)
fc=fn_tab[fnum]>>(7-block)
mul=([0.5,1-12,12,15,15])+2
incr=fn*mul

The frequency of the chip mentioned here: https://moddingwiki.shikadi.net/wiki/OPL_chip … the_Synthesizer

Edit: Thinking about it, FREQ_SH should be 10, since that would match the 1024 entries in the OPL2 sinus wave table (4 times the quarter sinus, with second half vertically flipped and first/third half inverted in time/index)?
Edit: Hmmm... According to the wiki, the maximum frequency is ~6208Hz. That's ~1/8th of the chip's frequency. Counting the x17 maximum multiplier from the mul into it, that would get up to 105536Hz, which the chip shouldn't be able to handle, or would it?

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

Reply 21 of 52, by Electronic Genets

User metadata
Rank Newbie
Rank
Newbie

FREQ_SH - part of emulators uses 10.10 fixed point accurancy (FREQ_SH=10). Part uses 16.16 (opl3Emu: FREQ_SH=16). I use 10.22 because on 32-bit register (STM32) because I can remove the "and" after shifting right by 22 positions (time critical section) - FREQ_SH=22.
If you use 10.10 format, you will emulate the YM3812 as similar as possible. If you use 10.16, 16.16 or 10.22 fixed point format also it will be good emulation (but not identical). I don't know if you want to emulate YM3812 faithfully or have good music quality.

BTW. I don't know the "Incr" added to "Cnt" should be rounded up (ceil), normal (round) or down (floor) after calculation "Incr". I know chip uses only 20 bits for phase generator. I also don't know how rounded is log_sin array in original OPL2/OPL3 chip. Original OPL2/OPL3 doesn't use SIN array but log_sin.

I believe you see now that floating point calculation is useless.

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

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

Reply 22 of 52, by superfury

User metadata
Rank l33t++
Rank
l33t++
Electronic Genets wrote on 2022-05-11, 20:40:
FREQ_SH - part of emulators use 10.10 fixed point accurancy (FREQ_SH=10). Part use 16.16 (opl3Emu: FREQ_SH=16). I use 10.22 beca […]
Show full quote

FREQ_SH - part of emulators use 10.10 fixed point accurancy (FREQ_SH=10). Part use 16.16 (opl3Emu: FREQ_SH=16). I use 10.22 because on 32-bit register (STM32) because I can remove the "and" after shifting right by 22 positions (time critical section) - FREQ_SH=22.
If you use 10.10 format, you will emulate the YM3812 as similar as possible. If you use 10.16, 16.16 or 10.22 fixed point format also it will be good emulation (but not identical). I don't know if you want to emulate YM3812 faithfully or have good music quality.

BTW. I don't know the "Incr" added to "Cnt" should be rounded up (ceil), normal (round) or down (floor) after calculation "Incr". I know chip uses only 20 bits for phase generator. I also don't know how rounded is log_sin array in original OPL2/OPL3 chip. Original OPL2/OPL3 doesn't use SIN array but log_sin.

I believe you see now that floating point calculation is useless.

It will take some more work to implement it, modifying the core timing algorithms (although the SIN function is easily adjusted, as it just converts back to those 1024 entries).
The only thing I still have questions about is what that 64 in the calculation is.

Based on the wiki:
1023*49716=50859468
50859468/(2^20-7)=50859468/(2^13=8192)=6208
6208*1024=6356992=610000h. That's a difference of A800 with the 6400000, but doesn't make sense?

So what would that times 64 need to become?
There's also the added case of every increase might to be needing to tune to 1024 entries (I'd assume that's literally the lowest 10 bits of the 10.10 floating point number?), so multiplication or division by 1024, but I don't know which in this case?

Edit: According to the wiki, block 0, fnum=21 should be ~1Hz(1024 1-increases spread over ~48 samples per tick). So calculating it from that...
The minimum for mul is 2.5...

Hmmm...

FREQ_SH=10
freqbase=the adlib operating frequency? So ~49715(with numbers behind the decimal point as well, see my earlier formula on that ((14318180/288) Hz)).
For this, assume freqbase=49716 for simplicity (and synchonizing it with the wiki)
fn_tab=[21*whatineedtoknow*49716*(1<<(FREQ_SH-10)=1)][ i=0-1024 ]. 6144/49716/21=~0.0005? This entry becomes 6177 rounded down(using 1/169 as a base). whatineedtoknow=About 1/169?
The -10 in that formula is the 1024 steps in the amount behind the full range, so 1/1024th(1 index out of 1024 to be exact) of 64 times the base frequency. 64 is 6 times shifted left, but I don't know why yet)
fc=fn_tab[fnum]>>(7-block). Block=0. So 48<<7=6144 for fnum=21. After looking it up using the (1/169 base), it would become 48.
mul=([0.5,1-12,12,15,15])(+2 would be incorrect here?). Anyway, mul=1.
incr=fn*mul=48=48*1. fn=48

So 64 would need to become ~1/169? (0,0058848545452456) That would result in fnum=21 becoming ~1Hz.

Last edited by superfury on 2022-05-11, 21:15. Edited 2 times in total.

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

Reply 23 of 52, by Electronic Genets

User metadata
Rank Newbie
Rank
Newbie

Read also about Floating point Patriot problem.
Such are the long-term effects of using floating point numbers. There is no error for a few seconds and then it can grow. FIXED-point should be used if it is possible.

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

Reply 24 of 52, by superfury

User metadata
Rank l33t++
Rank
l33t++

Would the changing of 64 to (1/169) in the fnum lookup table be correct in this case (see my reverse calculations of 1Hz using octave 0)?

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

Reply 25 of 52, by Electronic Genets

User metadata
Rank Newbie
Rank
Newbie
superfury wrote on 2022-05-11, 21:17:

Would the changing of 64 to (1/169) in the fnum lookup table be correct in this case (see my reverse calculations of 1Hz using octave 0)?

Can you please tell me what 64? Where does 64 come from?

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

Reply 26 of 52, by superfury

User metadata
Rank l33t++
Rank
l33t++
Electronic Genets wrote on 2022-05-11, 21:19:
superfury wrote on 2022-05-11, 21:17:

Would the changing of 64 to (1/169) in the fnum lookup table be correct in this case (see my reverse calculations of 1Hz using octave 0)?

Can you please tell me what 64? Where does 64 come from?

fmopl.c of opl3emu1.1.1:

	/* make fnumber -> increment counter table */
for( i=0 ; i < 1024 ; i++ )
{
/* opn phase increment counter = 20bit */
OPL->fn_tab[i] = (UINT32)( (double)i * 64 * OPL->freqbase * (1<<(FREQ_SH-10)) ); /* -10 because chip works with 10.10 fixed point, while we use 16.16 */
#if 0
logerror("FMOPL.C: fn_tab[%4i] = %08x (dec=%8i)\n",
i, OPL->fn_tab[i]>>6, OPL->fn_tab[i]>>6 );
#endif
}

Edit :Just found this one:
https://github.com/Falcosoft/OPLl3emu4v/blob/ … er/src/opl3.cpp
Or even better: https://github.com/Falcosoft/OPLl3emu4v/blob/ … ter/src/opl.cpp
Would that be what's required in this case?

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

Reply 28 of 52, by superfury

User metadata
Rank l33t++
Rank
l33t++
Electronic Genets wrote on 2022-05-11, 21:42:

I don't know and I don't find the equivalent in my code.

Looking at the latter file, it seems to use 10.10 floating point with the divided 14MHz/288 ratio I mentioned.
Perhaps that one is based somewhat on the reverse engineering as well (I see some similarities in it).

I see it's using most of the specific stuff inside the frqmul table precalcs:

recipsamp = 1.0 / (fltype)int_samplerate;
for (i=15;i>=0;i--) {
frqmul[i] = (fltype)(frqmul_tab[i]*INTFREQU/(fltype)WAVEPREC*(fltype)FIXEDPT*recipsamp);
}

INTFREQU is the chip's clock. That's the number mentioned before (the 14MHz/288 one).
WAVEPREC is the waveform precision (10 bits). This matches UniPCemu's wave (although it's changed to 2PI by multiplication during generation/division during lookup)
intsamplerate is obvious (the same as INTFREQU in UniPCemu's case). So recipsamp seems to match UniPCemu?
FIXEDPT is 0x10000 for 16.16. This should probably be changed to 0x400 for 10-bit (10.10)?

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

Reply 29 of 52, by Electronic Genets

User metadata
Rank Newbie
Rank
Newbie

Probably if the format is converted from 10.10 to 16.16 the integer must be multiplied by 1<<6 = 64.
But I don't see the point in shifting it 6. FREQ_SH might be 10 instead 16 without it.

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

Reply 30 of 52, by superfury

User metadata
Rank l33t++
Rank
l33t++
Electronic Genets wrote on 2022-05-11, 23:15:

Probably if the format is converted from 10.10 to 16.16 the integer must be multiplied by 1<<6 = 64.
But I don't see the point in shifting it 6. FREQ_SH might be 10 instead 16 without it.

So essentially, just change FREQ_SH to 10 and mask the counter and incr with 0xFFFFF and don't use the 64 multiplication?

But still, if the formula is used without the 64 multiplication or FREQ_SH(which result shifted always is 0 when properly using FREQ_SH=10), will the resulting frequency be correct? Since if the 1/169 multiplier isn't used, the wave for ~1Hz will be much too slow and all other frequencies as well (assuming an increase of 1024 is 1 full sinus wave (or derivative))?
It would become 8156 ticks per 1024 sample, so 1 wave in 8351744 ticks per wave table entry(1Hz being rendered), so that's 1/167000th Hz.
Edit: Incorrect: (21*47916)>>7=7861. That's 376667676 increases each second.
376667676/1024=367839Hz sinuses/sec generated using the fractional part?
That would need to be shifted right 18 or 19 times to get the phase properly?
Edit: Perhaps 26 shifts for <1024 samples/sec?
Edit2: Another divide by 1024 for the non-fractional part(10 bits) leaves 359Hz?

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

Reply 31 of 52, by Electronic Genets

User metadata
Rank Newbie
Rank
Newbie
Adlib @ UniPCEmu wrote:
Copyright (C) 2019 - 2021 Superfury […]
Show full quote

Copyright (C) 2019 - 2021 Superfury

This file is part of UniPCemu.

UniPCemu is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Nuked-OPL3 wrote:
/* Nuked OPL3 * Copyright (C) 2013-2020 Nuke.YKT * * This file is part of Nuked OPL3. * * Nuked OPL3 is free software: you […]
Show full quote

/* Nuked OPL3
* Copyright (C) 2013-2020 Nuke.YKT
*
* This file is part of Nuked OPL3.
*
* Nuked OPL3 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 2.1
* of the License, or (at your option) any later version.

superfury, your goal is probably strict emulating of sound card and goal of nuked-opl3 is the same. You can use external Nuked-OPL3 LGPL library in your GPL code of UniPCEmu. Wouldn't it be easier to use something that works instead of reinventing the wheel?

You also do not need maximum optimized code, because your emulator is used on fast processors (for example: on Windows, Android, PSP). You probably don't plan run it on 60 MHz ARM. Thus you don't need to optimize it.
My goal is:
- non-GPL license (mainly prohibiting the use of the code for governmental and military purposes - it is not possible in GPL and LGPL),
- good quality of music but not strict (bit-to-bit) emulation,
- performance (must work on weak processors).

So I wrote the whole thing from scratch. The OPL2-like emulator is only part of my project.

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

Reply 32 of 52, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've just modified the basic synth to be using the new formulas, but somehow the frequency is WAY too high...

I'm probably forgetting something here...
Edit: Yup. Forgot the FREQ_SH part...

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

Reply 33 of 52, by superfury

User metadata
Rank l33t++
Rank
l33t++

OK. I've implemented the new increment method according to above documentation.
But somehow the sounds are way too high frequency (the phase walk is way too fast)?

/*

Copyright (C) 2019 - 2021 Superfury

This file is part of UniPCemu.

UniPCemu is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

UniPCemu is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with UniPCemu. If not, see <https://www.gnu.org/licenses/>.
*/

#include "headers/types.h" //Basic headers!
#include "headers/emu/sound.h" //Basic sound!
#include "headers/support/log.h" //Logging support!
#include "headers/hardware/ports.h" //Basic ports!
#include "headers/support/highrestimer.h" //High resoltion timer support!
#include "headers/emu/timers.h" //Timer support for attack/decay!
#include "headers/support/locks.h" //Locking support!
#include "headers/support/sounddoublebuffer.h" //Sound buffer support!
#include "headers/support/wave.h" //WAV file logging support!
#include "headers/support/filters.h" //Filter support!
#include "headers/support/signedness.h" //Sign conversion support!

#define uint8_t byte
#define uint16_t word

//Are we disabled?
#define __HW_DISABLED 0
//Use the adlib sound? If disabled, only run timers for the CPU. Sound will not actually be heard.
#define __SOUND_ADLIB 1
//What volume, in percent!
#define ADLIB_VOLUME 100.0f
//What volume is the minimum volume to be heard!
#define __MIN_VOL (1.0f / SHRT_MAX)
//Generate WAV file output?
//#define WAV_ADLIB
//Generate WAV file output of a single sine wave?
//#define WAVE_ADLIB
//Adlib low-pass filter if enabled!
#define ADLIB_LOWPASS 15392.0f
//Enable Tremolo&Vibrato if enabled!
#define ADLIB_TREMOLOVIBRATO
//Enable rhythm?
#define ADLIB_RHYTHM
//14MHz ticks per sample
#define MHZ14_TICK 288
//Sample divided to get 80us tick!
#define TIMER80_TICK 4

//How large is our sample buffer? 1=Real time, 0=Automatically determine by hardware
#define __ADLIB_SAMPLEBUFFERSIZE 4971
Show last 1390 lines

#define PI2 ((float)(2.0f * PI))

//Silence value?
#define Silence 0x1FF

//Sign bit, disable mask and extension to 16-bit value! 3-bits exponent and 8-bits mantissa(which becomes 10-bits during lookup of Exponential data)
//Sign bit itself!
#define SIGNBIT 0x8000
//Sign mask for preventing overflow
#define SIGNMASK 0x7FFF

//Convert cents to samples to increase (instead of 1 sample/sample). Floating point number (between 0.0+ usually?) Use this as a counter for the current samples (1.1+1.1..., so keep the rest value (1,1,1,...,0,1,1,1,...))
//The same applies to absolute and relative timecents (with absolute referring to 1 second intervals (framerate samples) and relative to the absolute value)
#define cents2samplesfactor(cents) pow(2, ((cents) / 1200))
//Convert to samples (not entire numbers, so keep them counted)!

//extern void set_port_write_redirector (uint16_t startport, uint16_t endport, void *callback);
//extern void set_port_read_redirector (uint16_t startport, uint16_t endport, void *callback);

uint16_t baseport = 0x388; //Adlib address(w)/status(r) port, +1=Data port (write only)

//Sample based information!
DOUBLE usesamplerate = 0.0; //The sample rate to use for output!
DOUBLE adlib_soundtick = 0.0; //The length of a sample in ns!
//The length of a sample step:
#ifdef IS_LONGDOUBLE
#define adlib_sampleLength (1.0L / (14318180.0L / 288.0L))
#else
#define adlib_sampleLength (1.0 / (14318180.0 / 288.0))
#endif

//Counter info
float counter80 = 0.0f, counter320 = 0.0f; //Counter ticks!
byte timer80=0, timer320=0; //Timer variables for current timer ticks!

//Registers itself
byte adlibregmem[0xFF], adlibaddr = 0;

word OPL2_ExpTable[0x100], OPL2_LogSinTable[0x100]; //The OPL2 Exponentional and Log-Sin tables!
DOUBLE OPL2_ExponentialLookup[0x10000]; //Full exponential lookup table!
float OPL2_ExponentialLookup2[0x10000]; //The full exponential lookup table, converted to -1 to +1 range!
float OPL2_TremoloVibratoLookup[0x10000]; //The full tremolo/vibrato lookup table!
word OPL2_TremoloVibratoLookupPhase[0x10000]; //The full tremolo/vibrato lookup table!

byte adliboperators[2][0x10] = { //Groupings of 22 registers! (20,40,60,80,E0)
{ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12,255,255,255,255,255,255 },
{ 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15,255,255,255,255,255,255 }
};

byte adliboperatorsreverse[0x20] = { 0, 1, 2, 0, 1, 2, 255, 255, 3, 4, 5, 3, 4, 5, 255, 255, 6, 7, 8, 6, 7, 8,255,255,255,255,255,255,255,255,255,255}; //Channel lookup of adlib operators!
byte adliboperatorsreversekeyon[0x20] = { 1, 1, 1, 2, 2, 2, 255, 255, 1, 1, 1, 2, 2, 2, 255, 255, 1, 1, 1, 2, 2, 2,0,0,0,0,0,0,0,0,0,0}; //Modulator/carrier lookup of adlib operators in the keyon bits!

static const float feedbacklookup[8] = { 0, (float)(37.5), (float)(75), (float)(150), (float)(300), (float)600, (float)(1200), (float)(1200*2.0) }; //The feedback to use from opl3emu! Seems to be half a sinus wave per number!
float feedbacklookup2[8]; //Actual feedback lookup value!

byte wavemask = 0; //Wave select mask!

byte NTS; //NTS bit!
byte CSMMode; //CSM mode enabled?

SOUNDDOUBLEBUFFER adlib_soundbuffer; //Our sound buffer for rendering!

#ifdef WAV_ADLIB
WAVEFILE *adlibout = NULL;
#endif

typedef struct
{
word m_fnum, m_block; //Our settings!
uint8_t keyon;
uint8_t synthmode; //What synthesizer mode (1=Additive synthesis, 0=Frequency modulation)
float feedback; //The feedback strength of the modulator signal.
} ADLIBCHANNEL; //A channel!

typedef struct {
//Effects
word outputlevel; //(RAW) output level!
word volenv; //(RAW) volume level!
byte m_ar, m_dr, m_sl, m_rr; //Four rates and levels!
uint_32 m_counter; //Counter for the volume envelope!
word m_env;
word m_ksl, m_kslAdd, m_ksr; //Various key setttings regarding pitch&envelope!
byte ReleaseImmediately; //Release even when the note is still turned on?
word m_kslAdd2; //Translated value of m_ksl!

//Volume envelope
uint8_t volenvstatus; //Envelope status and raw volume envelope value(0-64)
word gain; //The gain gotten from the volume envelopes!
word rawgain; //The gain without main volume control!

byte vibrato, tremolo; //Vibrato/tremolo setting for this channel. 1=Enabled, 0=Disabled.

//Signal generation
byte wavesel;
float ModulatorFrequencyMultiple; //What harmonic to sound?
float lastsignal[2]; //The last signal produced!
uint_32 phase; //Current phase calculated!
word increment; //Increment each sample!
ADLIBCHANNEL *channel;
} ADLIBOP; //An adlib operator to process!

ADLIBOP adlibop[0x20];

ADLIBCHANNEL adlibch[0x10];

word outputtable[0x40]; //Build using software formulas!

uint8_t adlibpercussion = 0, adlibstatus = 0;

uint_32 OPL2_RNGREG = 0;
uint_32 OPL2_RNG = 0; //The current random generated sample!

uint16_t adlibport = 0x388;


//Tremolo/vibrato support

typedef struct
{
float time; //Current time(loops every second)!
float current; //Current value in absolute data!
word phase; //Raw phase!
float depth; //The depth to apply!
float active; //Active value, depending on tremolo/vibrato what this is: tremolo: volume to apply. vibrato: speedup to apply.
} TREMOLOVIBRATOSIGNAL; //Tremolo&vibrato signals!

TREMOLOVIBRATOSIGNAL tremolovibrato[2]; //Tremolo&vibrato!

//RNG

OPTINLINE void OPL2_stepRNG() //Runs at the sampling rate!
{
OPL2_RNG = ( (OPL2_RNGREG) ^ (OPL2_RNGREG>>14) ^ (OPL2_RNGREG>>15) ^ (OPL2_RNGREG>>22) ) & 1; //Get the current RNG!
OPL2_RNGREG = (OPL2_RNG<<22) | (OPL2_RNGREG>>1);
}

OPTINLINE float calcModulatorFrequencyMultiple(byte data)
{
switch (data)
{
case 0: return 0.5f;
case 11: return 10.0f;
case 13: return 12.0f;
case 14: return 15.0f;
default: return (float)data; //The same number!
}
}

//Attenuation setting!
void EnvelopeGenerator_setAttennuation(ADLIBOP *operator); //Prototype!
void EnvelopeGenerator_setAttennuationCustom(ADLIBOP *op)
{
op->m_kslAdd2 = (op->m_kslAdd<<3); //Multiply with 8!
}


void writeadlibKeyON(byte channel, byte forcekeyon)
{
byte isflipkeyon = 0; //Are we processing a low or high flipped key-on for High-hat/Cymbal? 1=Start modulator, 2=Start carrier!
byte ispercussionflipping = 0;
byte keyon;
byte oldkeyon;
keyon = ((adlibregmem[0xB0 + (channel&0xF)] >> 5) & 1)?3:0; //New key on for melodic channels? Affect both operators! This disturbs percussion mode!
if (adlibpercussion && (channel&0x80)) //Percussion enabled and percussion channel changed?
{
keyon = adlibregmem[0xBD]; //Total key status for percussion channels?
switch (channel&0xF) //What channel?
{
//Adjusted according to http://www.4front-tech.com/dmguide/dmfm.html
case 6: //Bass drum? Uses the channel normally!
keyon = (keyon&0x10)?3:0; //Bass drum on? Key on/off on both operators!
channel = 6; //Use channel 6!
break;
case 7: //Snare drum(Modulator)/Hi-hat(Carrier)? fmopl.c: High-hat uses modulator, Snare drum uses Carrier signals.
keyon = ((keyon>>3)&1)|((keyon<<1)&2); //Shift the information to modulator and carrier positions!
channel = 7; //Use channel 7!
ispercussionflipping = 1; //Enable flip key-on for modulator(High-hat)!
break;
case 8: //Tom-tom(Carrier)/Cymbal(Modulator)? fmopl.c:Tom-tom uses Modulator, Cymbal uses Carrier signals.
keyon = ((keyon>>2)&1)|(keyon&2); //Shift the information to modulator and carrier positions!
channel = 8; //Use channel 8!
ispercussionflipping = 2; //Enable flip key-on for carrier(Cymbal)!
break;
default: //Unknown channel?
//New key on for melodic channels? Don't change anything!
break;
}
}

oldkeyon = adlibch[channel].keyon; //Current&old key on!

nextkeyon:
adlibch[channel].m_block = (adlibregmem[0xB0 + channel] >> 2) & 7;
adlibch[channel].m_fnum = (adlibregmem[0xA0 + channel] | ((adlibregmem[0xB0 + channel] & 3) << 8)); //Frequency number!
if (adliboperators[0][channel] != 0xFF)
{
adlibop[adliboperators[0][channel]].increment = ((((uint_32)(adlibch[channel].m_fnum*(1.0/169.0)*(14318180.0 / 288.0))) >> (7 - adlibch[channel].m_block)) * adlibop[adliboperators[0][channel]].ModulatorFrequencyMultiple); //Calculate the effective frequency!
}
if (adliboperators[1][channel] != 0xFF)
{
adlibop[adliboperators[1][channel]].increment = ((((uint_32)(adlibch[channel].m_fnum*(1.0/169.0) * (14318180.0 / 288.0))) >> (7 - adlibch[channel].m_block)) * adlibop[adliboperators[1][channel]].ModulatorFrequencyMultiple); //Calculate the effective frequency!
}

if ((adliboperators[0][channel]!=0xFF) && ((((keyon&1) && ((oldkeyon^keyon)&1)) || (forcekeyon&1)) || (isflipkeyon==1))) //Key ON on operator #1 or flip starting the modulator?
{
if (adlibop[adliboperators[0][channel]&0x1F].volenvstatus==0) //Not retriggering the volume envelope?
{
adlibop[adliboperators[0][channel]&0x1F].volenv = Silence; //No raw level: Start silence!
adlibop[adliboperators[0][channel]&0x1F].m_env = Silence; //No raw level: Start level!
}
adlibop[adliboperators[0][channel]&0x1F].volenvstatus = 1; //Start attacking!
adlibop[adliboperators[0][channel]&0x1F].gain = ((adlibop[adliboperators[0][channel]].volenv)<<3); //Apply the start gain!
adlibop[adliboperators[0][channel]&0x1F].m_counter = 0; //No raw level: Start counter!
adlibop[adliboperators[0][channel]&0x1F].phase = 0; //Initialise operator signal!
adlibop[adliboperators[0][channel]&0x1F].lastsignal[0] = adlibop[adliboperators[1][channel]&0x1F].lastsignal[1] = 0.0f; //Reset the last signals!
EnvelopeGenerator_setAttennuation(&adlibop[adliboperators[0][channel]&0x1F]);
EnvelopeGenerator_setAttennuationCustom(&adlibop[adliboperators[0][channel]&0x1F]);
if (ispercussionflipping==1) //Are we flipping on this key-on?
{
ispercussionflipping = 3; //Start the other percussion flip after we're done!
}
}

//Below block is a fix for stuck notes!
if ((adliboperators[0][channel] != 0xFF) && (((keyon & 1) == 0) && ((oldkeyon^keyon) & 1) && ((forcekeyon & 1) == 0))) //Key OFF on operator #1?
{
if (adlibop[adliboperators[0][channel] & 0x1F].volenvstatus == 0) //Not retriggering the volume envelope?
{
adlibop[adliboperators[0][channel] & 0x1F].volenv = Silence; //No raw level: Start silence!
adlibop[adliboperators[0][channel] & 0x1F].m_env = Silence; //No raw level: Start level!
}
adlibop[adliboperators[0][channel] & 0x1F].volenvstatus = 4; //Start attacking!
adlibop[adliboperators[0][channel] & 0x1F].gain = ((adlibop[adliboperators[0][channel]].volenv) << 3); //Apply the start gain!
EnvelopeGenerator_setAttennuation(&adlibop[adliboperators[0][channel] & 0x1F]);
EnvelopeGenerator_setAttennuationCustom(&adlibop[adliboperators[0][channel] & 0x1F]);
}

if ((adliboperators[1][channel]!=0xFF) && ((((keyon&2) && ((oldkeyon^keyon)&2)) || (forcekeyon&2)) || (isflipkeyon==2))) //Key ON on operator #2 or flip starting the carrier?
{
if (adlibop[adliboperators[1][channel]&0x1F].volenvstatus==0) //Not retriggering the volume envelope?
{
adlibop[adliboperators[1][channel]&0x1F].volenv = Silence; //No raw level: silence!
adlibop[adliboperators[1][channel]&0x1F].m_env = Silence; //No raw level: Start level!
}
adlibop[adliboperators[1][channel]&0x1F].volenvstatus = 1; //Start attacking!
adlibop[adliboperators[1][channel]&0x1F].gain = ((adlibop[adliboperators[1][channel]].volenv)<<3); //Apply the start gain!
adlibop[adliboperators[1][channel]&0x1F].m_counter = 0; //No raw level: Start counter!
adlibop[adliboperators[1][channel]&0x1F].phase = 0; //Initialise operator signal!
adlibop[adliboperators[1][channel]&0x1F].lastsignal[0] = adlibop[adliboperators[1][channel]&0x1F].lastsignal[1] = 0.0f; //Reset the last signals!
EnvelopeGenerator_setAttennuation(&adlibop[adliboperators[1][channel]&0x1F]);
EnvelopeGenerator_setAttennuationCustom(&adlibop[adliboperators[1][channel]&0x1F]);
if (ispercussionflipping == 2) //Are we flipping on this key-on?
{
ispercussionflipping = 3; //Start the other percussion flip after we're done!
}
}

//Below block is a fix for stuck notes!
if ((adliboperators[1][channel] != 0xFF) && (((keyon & 2) == 0) && ((oldkeyon^keyon) & 2) && ((forcekeyon & 2) == 0))) //Key OFF on operator #1?
{
if (adlibop[adliboperators[1][channel] & 0x1F].volenvstatus == 0) //Not retriggering the volume envelope?
{
adlibop[adliboperators[1][channel] & 0x1F].volenv = Silence; //No raw level: Start silence!
adlibop[adliboperators[1][channel] & 0x1F].m_env = Silence; //No raw level: Start level!
}
adlibop[adliboperators[1][channel] & 0x1F].volenvstatus = 4; //Start releasing!
adlibop[adliboperators[1][channel] & 0x1F].gain = ((adlibop[adliboperators[1][channel]].volenv) << 3); //Apply the start gain!
EnvelopeGenerator_setAttennuation(&adlibop[adliboperators[1][channel] & 0x1F]);
EnvelopeGenerator_setAttennuationCustom(&adlibop[adliboperators[1][channel] & 0x1F]);
}

//Update keyon information!
adlibch[channel].keyon = keyon | forcekeyon; //Key is turned on?
if (ispercussionflipping == 3) //Flipping percussion halves?
{
forcekeyon = 0; //Don't force said key on, if so!
channel = (channel == 8) ? 7 : 8; //Flip the channel!
keyon = adlibch[channel].keyon; //The channel's actual key-on(so it doesn't detect any changes, so no strange effects)!
isflipkeyon = (channel == 7) ? 1 : 2; //Are we to start the modulator(0) or carrier(1) on said channel?
ispercussionflipping = 4; //Finish flipping afterwards!
goto nextkeyon;
}
}

void writeadlibaddr(byte value)
{
adlibaddr = value; //Set the address!
}

void writeadlibdata(byte value)
{
word portnum;
byte oldval;
portnum = adlibaddr;
oldval = adlibregmem[portnum]; //Save the old value for reference!
if (portnum != 4) adlibregmem[portnum] = value; //Timer control applies it itself, depending on the value!
switch (portnum & 0xF0) //What block to handle?
{
case 0x00:
switch (portnum) //What primary port?
{
case 1: //Waveform select enable
wavemask = (adlibregmem[1] & 0x20) ? 3 : 0; //Apply waveform mask!
break;
case 4: //timer control
if (value & 0x80) { //Special case: don't apply the value!
adlibstatus &= 0x1F; //Reset status flags needed!
}
else //Apply value to register?
{
adlibregmem[portnum] = value; //Apply the value set!
if (value & 1) //Timer1 enabled?
{
timer80 = adlibregmem[2]; //Reload timer!
}
if (value & 2) //Timer2 enabled?
{
timer320 = adlibregmem[3]; //Reload timer!
}
}
break;
case 8: //CSW/Note-Sel?
CSMMode = (adlibregmem[8] & 0x80) ? 1 : 0; //Set CSM mode!
NTS = (adlibregmem[8] & 0x40) ? 1 : 0; //Set NTS mode!
break;
default: //Unknown?
break;
}
case 0x10: //Unused?
break;
case 0x20:
case 0x30:
if (portnum <= 0x35) //Various flags
{
portnum &= 0x1F;
adlibop[portnum].ModulatorFrequencyMultiple = calcModulatorFrequencyMultiple(value & 0xF); //Which harmonic to use?
adlibop[portnum].ReleaseImmediately = (value & 0x20) ? 0 : 1; //Release when not sustain until release!
adlibop[portnum].m_ksr = (value >> 4) & 1; //Keyboard scaling rate!
EnvelopeGenerator_setAttennuation(&adlibop[portnum]); //Apply attenuation settings!
EnvelopeGenerator_setAttennuationCustom(&adlibop[portnum]); //Apply attenuation settings!
}
break;
case 0x40:
case 0x50:
if (portnum <= 0x55) //KSL/Output level
{
portnum &= 0x1F;
adlibop[portnum].m_ksl = ((value >> 6) & 3); //Apply KSL!
adlibop[portnum].outputlevel = outputtable[value & 0x3F]; //Apply raw output level!
EnvelopeGenerator_setAttennuation(&adlibop[portnum]); //Apply attenuation settings!
EnvelopeGenerator_setAttennuationCustom(&adlibop[portnum]); //Apply attenuation settings!
}
break;
case 0x60:
case 0x70:
if (portnum <= 0x75) { //attack/decay
portnum &= 0x1F;
adlibop[portnum].m_ar = (value >> 4); //Attack rate
adlibop[portnum].m_dr = (value & 0xF); //Decay rate
EnvelopeGenerator_setAttennuation(&adlibop[portnum]); //Apply attenuation settings!
EnvelopeGenerator_setAttennuationCustom(&adlibop[portnum]); //Apply attenuation settings!
}
break;
case 0x80:
case 0x90:
if (portnum <= 0x95) //sustain/release
{
portnum &= 0x1F;
adlibop[portnum].m_sl = (value >> 4); //Sustain level
adlibop[portnum].m_rr = (value & 0xF); //Release rate
EnvelopeGenerator_setAttennuation(&adlibop[portnum]); //Apply attenuation settings!
EnvelopeGenerator_setAttennuationCustom(&adlibop[portnum]); //Apply attenuation settings!
}
break;
case 0xA0:
case 0xB0:
if (portnum <= 0xB8)
{ //octave, freq, key on
if ((portnum & 0xF) > 8) return; //Ignore A9-AF!
portnum &= 0xF; //Only take the lower nibble (the channel)!
writeadlibKeyON((byte)portnum, 0); //Write to this port! Don't force the key on!
}
else if (portnum == 0xBD) //Percussion settings etc.
{
adlibpercussion = (value & 0x20) ? 1 : 0; //Percussion enabled?
tremolovibrato[0].depth = (value & 0x80) ? 4.8f : 1.0f; //Default: 1dB AM depth, else 4.8dB!
tremolovibrato[1].depth = (value & 0x40) ? 14.0f : 7.0f; //Default: 7 cent vibrato depth, else 14 cents!
if (((oldval^value) & 0x1F) && adlibpercussion) //Percussion enabled and changed state?
{
writeadlibKeyON(0x86, 0); //Write to this port(Bass drum)! Don't force the key on!
writeadlibKeyON(0x87, 0); //Write to this port(Snare drum/Tom-tom)! Don't force the key on!
writeadlibKeyON(0x88, 0); //Write to this port(Cymbal/Hi-hat)! Don't force the key on!
}
}
break;
case 0xC0:
if (portnum <= 0xC8)
{
portnum &= 0xF;
adlibch[portnum].synthmode = (adlibregmem[0xC0 + portnum] & 1); //Save the synthesis mode!
byte feedback;
feedback = (adlibregmem[0xC0 + portnum] >> 1) & 7; //Get the feedback value used!
adlibch[portnum].feedback = (float)feedbacklookup2[feedback]; //Convert to a feedback of the modulator signal!
}
break;
case 0xE0:
case 0xF0:
if (portnum <= 0xF5) //waveform select
{
portnum &= 0x1F;
adlibop[portnum].wavesel = value & 3;
}
break;
default: //Unsupported port?
break;
}
}

byte readadlibstatus()
{
return adlibstatus; //Give the current status!
}

byte outadlib (uint16_t portnum, uint8_t value) {
if (portnum==adlibport) {
writeadlibaddr(value); //Write to the address port!
return 1;
}
if (portnum != (adlibport+1)) return 0; //Don't handle what's not ours!
writeadlibdata(value); //Write to the data port!
return 1; //We're finished and handled, even non-used registers!
}

uint8_t inadlib (uint16_t portnum, byte *result) {
if (portnum == adlibport) //Status port?
{
*result = readadlibstatus(); //Give the current status!
return 1; //We're handled!
}
return 0; //Not our port!
}

//Native OPL2 Sinus Wave!
OPTINLINE word OPL2SinWave(word r)
{
INLINEREGISTER word index;
word entry; //The entry to convert!
INLINEREGISTER byte location; //The location in the table to use!
byte PIpart;
PIpart = 0; //Default: part 0!
index = r; //Default: take the index as specified!
index &= 0x3FF; //Loop the sinus infinitely!
if (index&0x200) //Second half?
{
PIpart = 2; //Second half!
index -= 0x200; //Convert to first half!
}
if (index&0x100) //Past quarter?
{
PIpart |= 1; //Second half!
index -= 0x100; //Convert to first quarter!
}
location = (byte)index; //Set the location to use!
if (PIpart&1) //Reversed quarter(second and fourth quarter)?
{
location = ~location; //Reverse us!
}

entry = OPL2_LogSinTable[location]; //Take the full load!
if (PIpart&2) //Second half is negative?
{
entry |= SIGNBIT; //We're negative instead, so toggle the sign bit!
}
return entry; //Give the processed entry!
}

OPTINLINE word OPL2SinWavePI(const float r)
{
float index;
index = fmodf(r, PI2); //Loop the sinus infinitely!
return OPL2SinWave((word)(index*1024)); //Call the native OPL2 sinus wave function with the phase to generate!
}

word MaximumExponential = 0; //Maximum exponential input!

OPTINLINE DOUBLE OPL2_Exponential_real(word v)
{
//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;
#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!
//Reverse the range given! Input 0=Maximum volume, Input max=No output.
if (v>MaximumExponential) v = MaximumExponential; //Limit to the maximum value available!
v = MaximumExponential-v; //Reverse our range to get the correct value!
#ifdef IS_LONGDOUBLE
return sign*(DOUBLE)(OPL2_ExpTable[v & 0xFF] + 1024)*pow(2.0L, (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)!
#else
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)!
#endif
}

OPTINLINE float OPL2_Exponential(word v)
{
return OPL2_ExponentialLookup2[v]; //Give the precalculated lookup result!
}

OPTINLINE float getOPL2TriangleWave(word v)
{
return OPL2_TremoloVibratoLookup[v]; //Give the precalculated lookup result!
}

OPTINLINE word getOPL2TriangleWavePhase(word v)
{
return OPL2_TremoloVibratoLookupPhase[v]; //Give the precalculated lookup result!
}

OPTINLINE void stepTremoloVibrato(TREMOLOVIBRATOSIGNAL *signal, float frequency)
{
float temp, dummy;
word phasetemp;
signal->current = getOPL2TriangleWave(OPL2SinWave((float)1024*frequency*(float)signal->time)); //Apply the signal using the OPL2 Sine Wave, reverse the operation and convert to triangle wave!
signal->phase = getOPL2TriangleWavePhase(OPL2SinWave((float)1024 * frequency * (float)signal->time)); //Apply the signal using the OPL2 Sine Wave, reverse the operation and convert to triangle wave!

signal->time += (float)adlib_sampleLength; //Add 1 sample to the time!

temp = signal->time*frequency; //Calculate for overflow!
if (temp >= 1.0f) { //Overflow?
signal->time = modff(temp, &dummy) / frequency;
}
}

OPTINLINE void OPL2_stepTremoloVibrato()
{
//Step to the next value!
stepTremoloVibrato(&tremolovibrato[0], 3.7f); //Tremolo at 3.7Hz!
stepTremoloVibrato(&tremolovibrato[1], 6.4f); //Vibrato at 6.4Hz!

//Now the current value of the signal is stored! Apply the active tremolo/vibrato!
#ifdef ADLIB_TREMOLOVIBRATO
tremolovibrato[0].active = (float)dB2factor(93.0f - (tremolovibrato[0].depth*tremolovibrato[0].current), 93.0f); //Calculate the current tremolo!
tremolovibrato[1].active = (100.0f + (tremolovibrato[1].depth*tremolovibrato[1].current))*0.01f*1024.0f; //Calculate the current vibrato!
#else
tremolovibrato[0].active = tremolovibrato[1].active = 1.0f; //No tremolo/vibrato!
#endif
}

OPTINLINE float OPL2_Vibrato(word phase, byte operatornumber)
{
if (adlibop[operatornumber].vibrato) //Vibrato enabled?
{
return phase + (tremolovibrato[1].phase * tremolovibrato[1].active); //Apply vibrato!
}
return phase; //Unchanged frequency!
}

OPTINLINE float OPL2_Tremolo(byte operator, float f)
{
if (adlibop[operator].tremolo) //Tremolo enabled?
{
return f*tremolovibrato[0].active; //Apply the current tremolo/vibrato!
}
return f; //Unchanged!
}

OPTINLINE word OPL2_Sin(byte signal, word phase) {
#ifdef IS_FLOATDOUBLE
double dummy;
#else
DOUBLE dummy;
#endif
word t;
word result;
switch (signal) {
case 0: //SINE?
return OPL2SinWave(phase); //The sinus function!
default:
t = (phase&0x3FF); //Calculate rest for special signal information!
switch (signal) { //What special signal?
case 1: // Negative=0?
if (t >= 0x200) return OPL2_LogSinTable[0]; //Negative=0!
result = OPL2SinWave(phase); //The sinus function!
return result; //Positive!
case 3: // Absolute with second half=0?
if (phase&0x100) return OPL2_LogSinTable[0]; //Are we the second half of the half period? Clear the signal if so!
case 2: // Absolute?
result = OPL2SinWave(phase); //The sinus function!
result &= ~SIGNBIT; //Ignore negative values!
return result; //Simply absolute!
default: //Unknown signal?
return 0;
}
}
}

OPTINLINE word calcOPL2Signal(byte wave, float phase, float operatorphase) //Calculates a signal for input to the adlib synth!
{
word ftp;
ftp = (word)operatorphase; //Frequency!
ftp += (word)phase; //Apply raw phase, in raw units!
return OPL2_Sin(wave, ftp); //Give the generated sample!
}

OPTINLINE void incop(byte operator)
{
if (operator==0xFF) return; //Invalid operator or ignoring timing increase!
adlibop[operator].phase += adlibop[operator].increment; //Add 1 sample to the time!
adlibop[operator].phase = OPL2_Vibrato(adlibop[operator].phase, operator); //Apply vibrato!
adlibop[operator].phase &= 0xFFFFF; //20-bit number!
}

OPTINLINE word calcModulator(float modulator)
{
return (word)(modulator*1024*4); //Calculate current modulation! 4 full sinus periods range!
}

OPTINLINE word calcFeedback(byte channel, ADLIBOP *operator)
{
return (word)((operator->lastsignal[0]+operator->lastsignal[1])*0.5f*adlibch[channel].feedback); //Calculate current feedback
}

//Calculate an operator signal!
OPTINLINE float calcOperator(byte channel, byte operator, byte timingoperator, byte volenvoperator, float modulator, byte flags)
{
if (operator==0xFF) return 0.0f; //Invalid operator!
INLINEREGISTER word result, gain; //The result to give!
float result2; //The translated result!
float activemodulation=0.0f;
//Generate the signal!
if ((flags & 0xC0)==0x80) //Apply channel feedback?
{
activemodulation = calcFeedback(channel, &adlibop[timingoperator]); //Apply this feedback signal!
}
else if ((flags&0x40)==0) //Apply normal modulation from a previous output?
{
activemodulation = calcModulator(modulator); //Use the normal modulator!
}

//Generate the correct signal! Ignore time by setting frequency to 0.0f(effectively disables time, keeping it stuck at 0(frequencytime))!
if ((flags & 0x40) == 0) //Normal signal?
{
result = calcOPL2Signal(adlibop[operator].wavesel&wavemask, activemodulation, (adlibop[operator].phase>>10)); //Take the last frequency or current frequency!
}
else //Raw input/output(don't take a normal signal)!
{
result = calcOPL2Signal(adlibop[operator].wavesel&wavemask, activemodulation, (adlibop[operator].phase>>10)); //Take the last frequency or current frequency!
}

//Calculate the gain!
gain = 0; //Init gain!
if (flags&2) //Special: ignore main volume control!
{
gain += outputtable[0]; //Always maximum volume, ignore the volume control!
}
else //Normal output level!
{
gain += adlibop[volenvoperator].outputlevel; //Current gain!
}
gain += adlibop[volenvoperator].gain; //Apply volume envelope and related calculations!
gain += adlibop[volenvoperator].m_kslAdd2; //Add KSL preprocessed!

//Now apply the gain!
result += gain; //Simply add the gain!
if (flags&8) //Double the volume?
{
result = (result&SIGNBIT)|(MIN(((result&SIGNMASK)>>1),SIGNMASK)&SIGNMASK); //Double the volume!
}
result2 = OPL2_Exponential(result); //Translate to Exponential range!

if (adlibop[timingoperator].increment && ((flags & 1) == 0)) //Running operator and allowed to update our signal?
{
adlibop[timingoperator].lastsignal[0] = adlibop[timingoperator].lastsignal[1]; //Previous last signal!
adlibop[timingoperator].lastsignal[1] = result2; //Set last signal #0 to #1(shift into the older one)!
incop(timingoperator); //Increase time for the operator when allowed to increase (frequency=0 during PCM output)!
}
result2 = OPL2_Tremolo(operator,result2); //Apply tremolo as well, after applying the new feedback signal(don't include tremolo in it)!
return result2; //Give the translated result!
}

float adlib_scaleFactor = 0.0f; //We're running 9 channels in a 16-bit space, so 1/9 of SHRT_MAX

OPTINLINE float adlibsample(uint8_t curchan, word phase7_1, word phase8_2) {
byte op6_1, op6_2, op7_1, op7_2, op8_1, op8_2; //The four slots used during Drum samples!
word tempop_phase; //Current phase of an operator!
float result;
float immresult; //The operator result and the final result!
byte op1,op2; //The two operators to use!
curchan &= 0xF;
if (curchan >= NUMITEMS(adlibch)) return 0; //No sample with invalid channel!

//Determine the modulator and carrier to use!
op1 = adliboperators[0][curchan]; //First operator number!
op2 = adliboperators[1][curchan]; //Second operator number!
if (adlibpercussion && (curchan >= 6) && (curchan <= 8)) //We're percussion?
{
#ifndef ADLIB_RHYTHM
return 0.0f; //Disable percussion!
#else
INLINEREGISTER word tempphase;
result = 0; //Initialise the result!
//Calculations based on http://bisqwit.iki.fi/source/opl3emu.html fmopl.c
//Load our four operators for processing!
op6_1 = adliboperators[0][6];
op6_2 = adliboperators[1][6];
op7_1 = adliboperators[0][7];
op7_2 = adliboperators[1][7];
op8_1 = adliboperators[0][8];
op8_2 = adliboperators[1][8];
switch (curchan) //What channel?
{
case 6: //Bass drum?
//Generate Bass drum samples!
//Special on Bass Drum: Additive synthesis(Operator 1) is ignored.

//Calculate the frequency to use!
result = 0.0f;
if (adlibop[op6_2].volenvstatus) //Running?
{
result = calcOperator(6, op6_1, op6_1, op6_1, 0.0f, 0x00); //Calculate the modulator for feedback!

if (adlibch[6].synthmode) //Additive synthesis?
{
result = calcOperator(6, op6_2, op6_2, op6_2, 0.0f, 0x08); //Calculate the carrier without applied modulator additive!
}
else //FM synthesis?
{
result = calcOperator(6, op6_2, op6_2, op6_2, result, 0x08); //Calculate the carrier with applied modulator!
}
}

return result; //Apply the exponential! The volume is always doubled!
break;

//Comments with information from fmopl.c:
/* Phase generation is based on: */
/* HH (13) channel 7->slot 1 combined with channel 8->slot 2 (same combination as TOP CYMBAL but different output phases) */
/* SD (16) channel 7->slot 1 */
/* TOM (14) channel 8->slot 1 */
/* TOP (17) channel 7->slot 1 combined with channel 8->slot 2 (same combination as HIGH HAT but different output phases) */


/* Envelope generation based on: */
/* HH channel 7->slot1 */
/* SD channel 7->slot2 */
/* TOM channel 8->slot1 */
/* TOP channel 8->slot2 */
//So phase modulation is based on the Modulator signal. The volume envelope is in the Carrier signal (Hi-hat/Tom-tom) or Carrier signal().
case 7: //Hi-hat(Carrier)/Snare drum(Modulator)? High-hat uses modulator, Snare drum uses Carrier signals.
immresult = 0.0f; //Initialize immediate result!
if (adlibop[op7_1].volenvstatus) //Hi-hat on Modulator?
{
//Derive frequency from channel 7(modulator) and 8(carrier).
tempop_phase = phase7_1; //Save the phase!
tempphase = (tempop_phase>>2);
tempphase ^= (tempop_phase>>7);
tempphase |= (tempop_phase>>3);
tempphase &= 1; //Only 1 bit is used!
tempphase = tempphase?(0x200|(0xD0>>2)):0xD0;
tempop_phase = phase8_2; //Calculate the phase of channel 8 carrier signal!
if (((tempop_phase>>3)^(tempop_phase>>5))&1) tempphase = 0x200|(0xD0>>2);
if (tempphase&0x200)
{
if (OPL2_RNG) tempphase = 0x2D0;
}
else if (OPL2_RNG) tempphase = (0xD0>>2);
result = calcOperator(8, op8_2,op8_2,op7_1,(float)(tempphase), 0x4B); //Calculate the modulator, but only use the current time(position in the sine wave)!
immresult += result; //Apply the tremolo!
}
if (adlibop[op7_2].volenvstatus) //Snare drum on Carrier volume?
{
//Derive frequency from channel 0.
tempphase = 0x100 << ((phase7_1 >> 8) & 1); //Bit8=0(Positive) then 0x100, else 0x200! Based on the phase to generate!
tempphase ^= (OPL2_RNG << 8); //Noise bits XOR'es phase by 0x100 when set!
result = calcOperator(7, op7_2,op7_2,op7_2,(float)tempphase, 0x40); //Calculate the carrier with applied modulator!
immresult += result; //Apply the tremolo!
}
result = immresult; //Load the resulting channel!
//result *= 0.5f; //We only have half(two channels combined)!
return result; //Give the result, converted to short!
break;
case 8: //Tom-tom(Carrier)/Cymbal(Modulator)? Tom-tom uses Modulator, Cymbal uses Carrier signals.
immresult = 0.0f; //Initialize immediate result!
if (adlibop[op8_1].volenvstatus) //Tom-tom(Modulator)?
{
result = calcOperator(8, op8_1, op8_1, op8_1, 0.0f, 0xA); //Calculate the carrier without applied modulator additive! Ignore volume!
immresult += result; //Apply the exponential!
}
if (adlibop[op8_2].volenvstatus) //Cymbal(Carrier)?
{
//Derive frequency from channel 7(modulator) and 8(carrier).
tempop_phase = phase7_1; //Save the phase!
tempphase = (tempop_phase>>2);
tempphase ^= (tempop_phase>>7);
tempphase |= (tempop_phase>>3);
tempphase &= 1; //Only 1 bit is used!
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, (float)tempphase, 0x41); //Calculate the carrier with applied modulator! Use volume!
immresult += result; //Apply the exponential!
}

//Advance the shared percussion channel by 7-1 and 8-2!
result = calcOperator(7, op7_1, op7_1, op7_1, 0.0f, 0); //Calculate the modulator, but only use the current time(position in the sine wave)!
result = calcOperator(8, op8_2, op8_2, op8_2, 0.0f, 0); //Calculate the carrier with applied modulator! Use volume!

result = immresult; //Load the resulting channel!
//result *= 0.5f; //We only have half(two channels combined)!
return result; //Give the result, converted to short!
break;
default:
break;
}
#endif
//Not a percussion channel? Pass through!
}

//Operator 1!
//Calculate the frequency to use!
result = calcOperator(curchan, op1,op1,op1, 0.0f,0x80); //Calculate the modulator for feedback!

if (adlibch[curchan].synthmode) //Additive synthesis?
{
result += calcOperator(curchan, op2,op2,op2, 0.0f,0x00); //Calculate the carrier without applied modulator additive!
}
else //FM synthesis?
{
result = calcOperator(curchan, op2,op2,op2, result, 0x00); //Calculate the carrier with applied modulator!
}

return result; //Give the result!
}

//Timer ticks!

byte ticked80_320 = 0; //80/320 ticked?

OPTINLINE void tick_adlibtimer()
{
if (CSMMode) //CSM enabled?
{
//Process CSM tick!
byte channel=0;
for (;;)
{
writeadlibKeyON(channel,3); //Force the key to turn on!
if (++channel==9) break; //Finished!
}
}
}

OPTINLINE void adlib_timer320() //Second timer!
{
if (adlibregmem[4] & 2) //Timer2 enabled?
{
if (++timer320 == 0) //Overflown?
{
if ((~adlibregmem[4]) & 0x20) //Update status register?
{
adlibstatus |= 0xA0; //Update status register and set the bits!
}
timer320 = adlibregmem[3]; //Reload timer!
ticked80_320 = 1; //We're ticked!
}
}
}

byte ticks80 = 0; //How many timer 80 ticks have been done?

OPTINLINE void adlib_timer80() //First timer!
{
ticked80_320 = 0; //Default: not ticked!
if (adlibregmem[4] & 1) //Timer1 enabled?
{
if (++timer80 == 0) //Overflown?
{
timer80 = adlibregmem[2]; //Reload timer!
if ((~adlibregmem[4]) & 0x40) //Update status?
{
adlibstatus |= 0xC0; //Update status register and set the bits!
}
ticked80_320 = 1; //Ticked 320 clock!
}
}
if (++ticks80 == 4) //Every 4 timer 80 ticks gets 1 timer 320 tick!
{
ticks80 = 0; //Reset counter to count 320us ticks!
adlib_timer320(); //Execute a timer 320 tick!
}
if (ticked80_320) tick_adlibtimer(); //Tick by either timer!
}

float counter80step = 0.0f; //80us timer tick interval in samples!

OPTINLINE byte adlib_channelplaying(byte channel)
{
if (channel==7) //Drum channels?
{
if (adlibpercussion) //Percussion mode? Split channels!
{
return 1; //Percussion channel is always on!
}
//Melodic?
return adlibop[adliboperators[1][7]].volenvstatus; //Melodic, so carrier!
}
else if (channel==8) //Drum channel?
{
if (adlibpercussion) //Percussion mode? Split channels!
{
return 1; //Percussion is always on?
}
//Melodic?
return adlibop[adliboperators[1][8]].volenvstatus; //Melodic, so carrier!
}
else //0 - 5=Melodic, 6=Melodic, Also drum channel, but no difference here.
{
return adlibop[adliboperators[1][channel]].volenvstatus; //Melodic, so carrier!
}
return 0; //Unknown channel!
}


OPTINLINE float adlibgensample() {
float adlibaccum = 0.0f;
byte channel;
byte op7_1;
byte op8_2;
word phase7_1;
word phase8_2;
op7_1 = adliboperators[0][7];
op8_2 = adliboperators[1][8];
phase7_1 = (adlibop[op7_1].phase>>10); //Save the current 7_1 phase for usage in drum channels!
phase8_2 = (adlibop[op8_2].phase>>10); //Save the current 8_2 phase for usage in drum channels!

for (channel=0;channel<9;++channel) //Process all channels!
{
if (adlib_channelplaying(channel)) adlibaccum += adlibsample(channel,phase7_1,phase8_2); //Sample when playing!
}
adlibaccum *= adlib_scaleFactor; //Scale according to volume!
return adlibaccum;
}

void EnvelopeGenerator_setAttennuation(ADLIBOP *operator)
{
if( operator->m_ksl == 0 ) {
operator->m_kslAdd = 0;
return;
}

if (!operator->channel) return; //Invalid channel?
// 1.5 dB att. for base 2 of oct. 7
// created by: round(8*log2( 10^(dbMax[msb]/10) ))+8;
// verified from real chip ROM
static const int kslRom[16] = {
0, 32, 40, 45, 48, 51, 53, 55, 56, 58, 59, 60, 61, 62, 63, 64
};
// 7 negated is, by one's complement, effectively -8. To compensate this,
// the ROM's values have an offset of 8.
int tmp = kslRom[operator->channel->m_fnum >> 6] + 8 * ( operator->channel->m_block - 8 );
if( tmp <= 0 ) {
operator->m_kslAdd = 0;
return;
}
operator->m_kslAdd = tmp;
switch( operator->m_ksl ) {
case 1:
// 3 db
operator->m_kslAdd <<= 1;
break;
case 2:
// no change, 1.5 dB
break;
case 3:
// 6 dB
operator->m_kslAdd <<= 2;
break;
default:
break;
}
}

OPTINLINE byte EnvelopeGenerator_nts(ADLIBOP *operator)
{
return NTS; //Give the NTS bit!
}

OPTINLINE uint8_t EnvelopeGenerator_calculateRate(ADLIBOP *operator, uint8_t rateValue )
{
if (!operator->channel) return 0; //Invalid channel?
if( rateValue == 0 ) {
return 0;
}
// calculate key scale number (see NTS in the YMF262 manual)
uint8_t rof = ( operator->channel->m_fnum >> ( EnvelopeGenerator_nts(operator) ? 8 : 9 ) ) & 0x1;
// ...and KSR (see manual, again)
rof |= operator->channel->m_block << 1;
if( !operator->m_ksr ) {
rof >>= 2;
}
// here, rof<=15
// the limit of 60 results in rof=0 if rateValue=15 below
return MIN( 60, rof + (rateValue << 2) );
}

OPTINLINE uint8_t EnvelopeGenerator_advanceCounter(ADLIBOP *operator, uint8_t rate )
{
if (rate >= 16 ) return 0;
if( rate == 0 ) {
return 0;
}
const uint8_t effectiveRate = EnvelopeGenerator_calculateRate(operator, rate );
// rateValue <= 15
const uint8_t rateValue = effectiveRate >> 2;
// rof <= 3
const uint8_t rof = effectiveRate & 3;
// 4 <= Delta <= (7<<15)
operator->m_counter += ((uint_32)(4 | rof )) << rateValue;
// overflow <= 7
uint8_t overflow = operator->m_counter >> 15;
operator->m_counter &= ( 1 << 15 ) - 1;
return overflow;
}

OPTINLINE void EnvelopeGenerator_attenuate( ADLIBOP *operator,uint8_t rate )
{
if( rate >= 64 ) return;
operator->m_env += EnvelopeGenerator_advanceCounter(operator, rate );
if( operator->m_env >= Silence ) {
operator->m_env = Silence;
}
}

OPTINLINE void EnvelopeGenerator_release(ADLIBOP *operator)
{
EnvelopeGenerator_attenuate(operator,operator->m_rr);
if (operator->m_env>=Silence)
{
operator->m_env = Silence;
operator->volenvstatus = 0; //Finished the volume envelope!
}
}

OPTINLINE void EnvelopeGenerator_decay(ADLIBOP *operator)
{
if ((operator->m_env>>4)>=operator->m_sl)
{
operator->volenvstatus = 3; //Start sustaining!
return;
}
EnvelopeGenerator_attenuate(operator,operator->m_dr);
}

OPTINLINE void EnvelopeGenerator_attack(ADLIBOP *operator)
{
if (operator->m_env<=0) //Nothin to attack anymore?
{
operator->volenvstatus = 2; //Start decaying!
}
else if (operator->m_ar==15)
{
operator->m_env = 0;
}
else //Attack!
{
if (operator->m_env<=0) return; //Abort if too high!
byte overflow = EnvelopeGenerator_advanceCounter(operator,operator->m_ar); //Advance with attack rate!
if (!overflow) return;
operator->m_env -= ((operator->m_env*overflow)>>3)+1; //Affect envelope in a curve!
}
}

OPTINLINE void tickadlib()
{
const byte maxop = NUMITEMS(adlibop); //Maximum OP count!
uint8_t curop;
for (curop = 0; curop < maxop; curop++)
{
if (!adlibop[curop].channel) continue; //Skip invalid operators!
if (adlibop[curop].volenvstatus) //Are we a running envelope?
{
switch (adlibop[curop].volenvstatus)
{
case 1: //Attacking?
EnvelopeGenerator_attack(&adlibop[curop]); //New method: Attack!
adlibop[curop].volenv = LIMITRANGE(adlibop[curop].m_env,0,Silence); //Apply the linear curve
adlibop[curop].gain = ((adlibop[curop].volenv)<<3); //Apply the start gain!
break;
case 2: //Decaying?
EnvelopeGenerator_decay(&adlibop[curop]); //New method: Decay!
if (adlibop[curop].volenvstatus==3)
{
goto startsustain; //Start sustaining if needed!
}
adlibop[curop].volenv = LIMITRANGE(adlibop[curop].m_env,0,Silence); //Apply the linear curve
adlibop[curop].gain = ((adlibop[curop].volenv)<<3); //Apply the start gain!
break;
case 3: //Sustaining?
startsustain:
if (adlibop[curop].ReleaseImmediately) //Release entered?
{
++adlibop[curop].volenvstatus; //Enter next phase!
goto startrelease; //Check again!
}
adlibop[curop].volenv = LIMITRANGE(adlibop[curop].m_env,0,Silence); //Apply the linear curve
adlibop[curop].gain = ((adlibop[curop].volenv)<<3); //Apply the start gain!
break;
case 4: //Releasing?
startrelease:
EnvelopeGenerator_release(&adlibop[curop]); //Release: new method!
adlibop[curop].volenv = LIMITRANGE(adlibop[curop].m_env,0,Silence); //Apply the linear curve
adlibop[curop].gain = ((adlibop[curop].volenv)<<3); //Apply the start gain!
break;
default: //Unknown volume envelope status?
adlibop[curop].volenvstatus = 0; //Disable this volume envelope!
break;
}
}
}
}

//Check for timer occurrences.
void cleanAdlib()
{
//Discard the amount of time passed!
}

//Stuff for the low-pass filter!
HIGHLOWPASSFILTER adlibfilter; //Output filter of the OPL2 output!
float opl2_currentsample; //Current sample!

byte adlib_ticktiming80 = 0; //80us divider!
uint_32 adlib_ticktiming=0; //Sound timing!
void updateAdlib(uint_32 MHZ14passed)
{
//Adlib sound output and counters!
adlib_ticktiming += MHZ14passed; //Get the amount of time passed!
if (adlib_ticktiming>=MHZ14_TICK)
{
do
{
//Adlib timer!
++adlib_ticktiming80; //Tick 80 divider!
if (adlib_ticktiming80 >= TIMER80_TICK) //Enough time passed?
{
adlib_ticktiming80 -= TIMER80_TICK; //Tick once(never more than once!)
adlib_timer80(); //Tick 80us timer!
}
//Now, process the samples required!
OPL2_stepRNG(); //Tick the RNG!
OPL2_stepTremoloVibrato(); //Step tremolo/vibrato!
byte filled;
float sample;
filled = 0; //Default: not filled!
filled |= adlib_channelplaying(0); //Channel 0?
filled |= adlib_channelplaying(1); //Channel 1?
filled |= adlib_channelplaying(2); //Channel 2?
filled |= adlib_channelplaying(3); //Channel 3?
filled |= adlib_channelplaying(4); //Channel 4?
filled |= adlib_channelplaying(5); //Channel 5?
filled |= adlib_channelplaying(6); //Channel 6?
filled |= adlib_channelplaying(7); //Channel 7?
filled |= adlib_channelplaying(8); //Channel 8?
if (filled) sample = adlibgensample(); //Any sound to generate?
else sample = 0.0f;

#ifdef ADLIB_LOWPASS
opl2_currentsample = sample;
//We're applying the low pass filter for the speaker!
applySoundFilter(&adlibfilter, &opl2_currentsample);
sample = opl2_currentsample; //Convert us back to our range!
#endif

sample = LIMITRANGE(sample, (float)SHRT_MIN, (float)SHRT_MAX); //Clip our data to prevent overflow!
#ifdef WAV_ADLIB
writeWAVMonoSample(adlibout,sample); //Log the samples!
#endif
writeDoubleBufferedSound16(&adlib_soundbuffer,(word)sample); //Output the sample to the renderer!
tickadlib(); //Tick us to the next timing if needed!
adlib_ticktiming -= MHZ14_TICK; //Decrease timer to get time left!
} while (adlib_ticktiming>=MHZ14_TICK);
}
}

byte adlib_soundGenerator(void* buf, uint_32 length, byte stereo, void *userdata) //Generate a sample!
{
if (stereo) return 0; //We don't support stereo!

uint_32 c;
c = length; //Init c!

static short last=0;

short *data_mono;
data_mono = (short *)buf; //The data in correct samples!
for (;;) //Fill it!
{
//Left and right samples are the same: we're a mono signal!
readDoubleBufferedSound16(&adlib_soundbuffer,(word *)&last); //Generate a mono sample if it's available!
*data_mono++ = last; //Load the last generated sample!
if (!--c) return SOUNDHANDLER_RESULT_FILLED; //Next item!
}
}

//Multicall speedup!
#define ADLIBMULTIPLIER 0

void initAdlib()
{
float current; //Current values for Tremolo/Vibrato lookup table!
float dummy; //Dummy value for Tremolo/Vibrato lookup!
if (__HW_DISABLED) return; //Abort!

//Initialize our timings!
adlib_scaleFactor = SHRT_MAX / (3000.0f*9.0f); //We're running 9 channels in a 16-bit space, so 1/9 of SHRT_MAX
#ifdef IS_LONGDOUBLE
usesamplerate = 14318180.0L / 288.0L; //The sample rate to use for output!
#else
usesamplerate = 14318180.0 / 288.0; //The sample rate to use for output!
#endif

int i;
for (i = 0; i < 9; i++)
{
memset(&adlibch[i],0,sizeof(adlibch[i])); //Initialise all channels!
}

//Build the needed tables!
for (i = 0; i < (int)NUMITEMS(outputtable); ++i)
{
outputtable[i] = (((word)i)<<5); //Multiply the raw value by 5 to get the actual gain: the curve is applied by the register shifted left!
}

for (i = 0; i < (int)NUMITEMS(adlibop); i++) //Process all channels!
{
memset(&adlibop[i],0,sizeof(adlibop[i])); //Initialise the channel!

//Apply default ADSR!
adlibop[i].volenvstatus = 0; //Initialise to unused ADSR!
adlibop[i].ReleaseImmediately = 1; //Release immediately by default!

adlibop[i].outputlevel = outputtable[0]; //Apply default output!
adlibop[i].ModulatorFrequencyMultiple = calcModulatorFrequencyMultiple(0); //Which harmonic to use?
adlibop[i].ReleaseImmediately = 1; //We're defaulting to value being 0=>Release immediately.
adlibop[i].lastsignal[0] = adlibop[i].lastsignal[1] = 0.0f; //Reset the last signals!
if (adliboperatorsreverse[i]!=0xFF) //Valid operator?
{
adlibop[i].channel = &adlibch[adliboperatorsreverse[i]&0x1F]; //The channel this operator belongs to!
}
}

//Source of the Exp and LogSin tables: https://docs.google.com/document/d/18IGx18NQY_Q1PJVZ-bHywao9bhsDoAqoIn1rIm42nwo/edit
for (i = 0;i < 0x100;++i) //Initialise the exponentional and log-sin tables!
{
OPL2_ExpTable[i] = (word)round((pow(2, (float)i / 256.0f) - 1.0f) * 1024.0f);
OPL2_LogSinTable[i] = (word)round(-log(sin((i + 0.5f)*PI / 256.0f / 2.0f)) / log(2.0f) * 256.0f);
}

//Find the maximum volume archievable with exponential lookups!
MaximumExponential = ((0x3F << 5) + (Silence << 3)) + OPL2_LogSinTable[0]; //Highest input to the LogSin input!
DOUBLE maxresult=0.0,buffer=0.0;
uint_32 n;
n = 0;
do
{
buffer = OPL2_Exponential_real((word)n); //Load the current value translated!
OPL2_ExponentialLookup[n] = buffer; //Store the value for fast lookup!
} while (++n<0x10000); //Loop while not finished processing all possibilities!

maxresult = OPL2_Exponential_real(0); //Zero is maximum output to give!
DOUBLE generalmodulatorfactor = 0.0f; //Modulation factor!
//Now, we know the biggest result given!
#ifdef IS_LONGDOUBLE
generalmodulatorfactor = (1.0L/(DOUBLE)maxresult); //General modulation factor, as applied to both modulation methods!
#else
generalmodulatorfactor = (1.0/(DOUBLE)maxresult); //General modulation factor, as applied to both modulation methods!
#endif

n = 0; //Loop through again for te modified table!
do
{
buffer = OPL2_ExponentialLookup[n]; //Load the current value translated!
buffer *= generalmodulatorfactor; //Apply the general modulator factor to it to convert it to -1.0 to 1.0 range!
OPL2_ExponentialLookup2[n] = (float)buffer; //Store the value for fast lookup!
} while (++n<0x10000); //Loop while not finished processing all possibilities!

adlib_scaleFactor = (((float)(SHRT_MAX))/8.0f); //Highest volume conversion Exp table(resulting mix) to SHRT_MAX (8 channels before clipping)!

for (i = 0;i < (int)NUMITEMS(feedbacklookup2);++i) //Process all feedback values!
{
feedbacklookup2[i] = feedbacklookup[i]; //Don't convert for now!
}

for (n=0;n<(int)NUMITEMS(OPL2_TremoloVibratoLookup);++n) //Process all Tremolo/Vibrato outputs!
{
current = modff(asinf(OPL2_Exponential((word)n)) / (float)PI2, &dummy); //Apply the signal using the OPL2 Sine Wave, reverse the operation and convert to triangle time!
current = (current < 0.5f) ? ((current * 2.0f) - 0.5f) : (0.5f - ((current - 0.5f) * 2.0f));
OPL2_TremoloVibratoLookupPhase[n] = ((current > 0.5f) ? 0x200 : 0) | ((fmodf(current, 0.5f) > 0.25f) ? 0x100 : 0) | ((byte)(fmodf(current, 0.25f) * 0x100)); //The raw OPL2 phase output!
OPL2_TremoloVibratoLookup[n] = current; //Set the used Tremolo/Vibrato value!
}

memset(&tremolovibrato,0,sizeof(tremolovibrato)); //Initialise tremolo/vibrato!
tremolovibrato[0].depth = 1.0f; //Default: 1dB AM depth!
tremolovibrato[1].depth = 7.0f; //Default: 7 cent vibrato depth!
NTS = CSMMode = 0; //Reset the global flags!

//RNG support!
OPL2_RNGREG = OPL2_RNG = 0; //Initialise the RNG!
OPL2_RNGREG = 1; //Seed the noise register to a valid value(must be non-zero)!

adlib_ticktiming = 0; //Reset our output timing!
adlib_ticktiming80 = 0; //80us tick timing!

if (__SOUND_ADLIB)
{
if (allocDoubleBufferedSound16(__ADLIB_SAMPLEBUFFERSIZE,&adlib_soundbuffer,0,usesamplerate)) //Valid buffer?
{
if (!addchannel(&adlib_soundGenerator,NULL,"Adlib",(float)usesamplerate,__ADLIB_SAMPLEBUFFERSIZE,0,SMPL16S,1)) //Start the sound emulation (mono) with automatic samples buffer?
{
dolog("adlib","Error registering sound channel for output!");
}
else
{
setVolume(&adlib_soundGenerator,NULL,ADLIB_VOLUME);
}
}
else
{
dolog("adlib","Error registering double buffer for output!");
}
}
//Ignore unregistered channel, we need to be used by software!
register_PORTIN(&inadlib); //Status port (R)
//All output!
register_PORTOUT(&outadlib); //Address port (W)

#ifdef WAV_ADLIB
adlibout = createWAV("captures/adlib.wav",1,usesamplerate); //Start logging!
#endif

#ifdef WAVE_ADLIB
WAVEFILE *w;
float u,f,c,es,dummyfreq0=0.0f,dummytime=0.0f;
uint_32 samples;
samples = (uint_32)usesamplerate; //Load the current sample rate!
word s,wave;
uint_32 currenttime;
c = (float)(SHRT_MAX); //Conversion for Exponential results!
f = (1.0/(float)usesamplerate); //Time of a wave sample!

w = createWAV("captures/adlibwave.wav", 1, usesamplerate); //Start logging one wave! Wave exponential test!
for (wave=0;wave<4;++wave) //Log all waves!
{
u = 0.0; //Reset the current time!
for (currenttime = 0;currenttime<samples;++currenttime) //Process all samples!
{
s = calcOPL2Signal(wave,1.0f,(dummytime*1024.0f)); //Get the sample(1Hz sine wave)!
es = OPL2_Exponential(s); //Get the raw sample at maximum volume!
es *= c; //Apply the destination factor!
writeWAVMonoSample(w,(word)(LIMITRANGE((sword)es,SHRT_MIN,SHRT_MAX))); //Log 1 wave, looked up through exponential input!
dummytime += f; //Add one sample to the time!
}
}
closeWAV(&w); //Close the wave file!
#endif

initSoundFilter(&adlibfilter,0,ADLIB_LOWPASS, (float)usesamplerate); //Initialize our low-pass filter to use!
}

void doneAdlib()
{
if (__HW_DISABLED) return; //Abort!
#ifdef WAV_ADLIB
closeWAV(&adlibout); //Stop logging!
#endif
if (__SOUND_ADLIB)
{
removechannel(&adlib_soundGenerator,NULL,0); //Stop the sound emulation?
freeDoubleBufferedSound(&adlib_soundbuffer); //Free out double buffered sound!
}
}

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

Reply 34 of 52, by superfury

User metadata
Rank l33t++
Rank
l33t++

Hmmm... Disabling OPL2_Vibrato in incop and forcing calcModulator result to 0 seems to 'fix' audio tones, but remove most basic modulation (because calcModulator is forcing it's output to 0).
Edit: After some work, basic modulation seems to work.
Although vibrato keeps messing things up?

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

Reply 35 of 52, by superfury

User metadata
Rank l33t++
Rank
l33t++

Can anyone explain this diagram? http://midibox.org/forums/topic/18625-opl3-pe … ssion-mode-map/
So channel 17 generates a sine wave(that's the PM block) normally? But how does the part in the center work?
So it looks like channel 8's raw sine wave modulates the SD? But somehow also the 'Ring'? Do all 4 channels have their own RNG, driven by those signals going into it? UniPCemu just ticks it once each clock(~47kHz) cycle?
I'd assume the orange PM at the top isn't the same as the one below it? That's an extra channel being generated not documented?
The 100% forced feedback at the HH/SD also isn't implemented?

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

Reply 36 of 52, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've modified the code now (it can be found at UniPCemu's git repository, file UniPCemu/hardware/adlib.c) to use the new percussion method fully (although the old floating point method is still used for the Tremolo/Vibrato wave generation).

I'm using the Rhythm.DRO from this post:
DOSbox' OPL Emulation - Rhythm Sounds

This is what I get with UniPCemu currently:

Filename
recording_295.zip
File size
351.62 KiB
Downloads
28 downloads
File comment
OPL2 recording of the rhythm.dro
File license
Fair use/fair dealing exception

Edit: After fixing the OPL2 rhythm channels to trigger properly (it was triggering when it shouldn't in Rhythm mode) and fixing the OPL2 Tom-Tom to properly update it's timing.

Filename
recording_298.zip
File size
449.44 KiB
Downloads
26 downloads
File comment
Tom-Tom fixed and double percussion triggering fixed.
File license
Fair use/fair dealing exception

Edit: Although all that is improved, somehow stuff still sounds too high? And Ultima VI's opening sounds weird?
Edit: It might be better to take this back to my old thread (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 37 of 52, by superfury

User metadata
Rank l33t++
Rank
l33t++

I eventually experimented a bit. Adjusted the increment to be 1/388 of the original value (instead of the 64 in the code).
It sounded a bit off.
Then adjusted it to 1/384(which is a division of a power of 2 and multiple of the used 64 in the OPL3EMU), which does seem to sound correct?
Also adjusted the feedback and modulation part to be compatible with both the new rhythm and hopefully the normal synth as well:

OPTINLINE int_64 calcModAndFeedback(byte channel,word flags, float modulator, ADLIBOP *operator, byte forcefull)
{
DOUBLE result,feedback; //The result!
if (flags & 0x40) //Requires modulator conversion first?
{
modulator /= 1024; //Convert to 0.0-1.0!
}
result = modulator; //Default is the normal modulator input!
if ((flags & 0x80) == 0x80) //Apply channel feedback?
{
if (adlibch[channel].feedback || forcefull) //Gotten feedback?
{
feedback = ((((operator->lastsignal[0]+operator->lastsignal[1])) * 0.5f * ((!forcefull) ? adlibch[channel].feedback : 1.0f))); //Calculate current feedback
if (!(flags & 0x40))
{
feedback *= 0.25f;
}
result += feedback; //Apply the feedback!
}
}
if (!(flags & 0x40))
{
result *= 4;
}
result *= 1024; //Plain modulator! 1024!
return (int_64)result; //Give the result!
}

//Calculate an operator signal!
/*
flags:
bit0: disable timing update
bit1: disable feedback update
bit2: ignore volume
bit3: double volume
bit4: force sinus wave
bit5: force 100% feedback
bit6: disable modulator conversion (assume raw phase)
bit7: apply feedback
bit8: disable sinus input.
*/
OPTINLINE float calcOperator(byte channel, byte coreoperator, byte timingoperator, byte volenvoperator, float modulator, word flags)
{
if (coreoperator==0xFF) return 0.0f; //Invalid operator!
INLINEREGISTER word result, gain; //The result to give!
float result2; //The translated result!
int_64 activemodulation;
//Generate the signal!
activemodulation = calcModAndFeedback(channel,flags, modulator, &adlibop[coreoperator], ((flags & 0x20) != 0)); //Apply this feedback signal!

result = calcOPL2Signal((flags&0x10)?0:(adlibop[coreoperator].wavesel&wavemask), (DOUBLE)activemodulation, (flags&0x100)?0:(adlibop[timingoperator].phase>>10)); //Take the last frequency or current frequency!

//Calculate the gain!
gain = 0; //Init gain!
if (flags&4) //Special: ignore main volume control!
{
gain += outputtable[0]; //Always maximum volume, ignore the volume control!
}
else //Normal output level!
{
Show last 26 lines
		gain += adlibop[volenvoperator].outputlevel; //Current gain!
}
gain += adlibop[volenvoperator].gain; //Apply volume envelope and related calculations!
gain += adlibop[volenvoperator].m_kslAdd2; //Add KSL preprocessed!

//Now apply the gain!
result += gain; //Simply add the gain!
if (flags&8) //Double the volume?
{
result = (result&SIGNBIT)|(MIN(((result&SIGNMASK)>>1),SIGNMASK)&SIGNMASK); //Double the volume!
}
result2 = OPL2_Exponential(result); //Translate to Exponential range!

if ((flags & 2) == 0) //Running operator and allowed to update our signal?
{
adlibop[coreoperator].lastsignal[1] = adlibop[coreoperator].lastsignal[0]; //Set last signal #0 to #1(shift into the older one)!
adlibop[coreoperator].lastsignal[0] = result2; //Set last signal #0 to #1(shift into the older one)!
}
if ((flags & 1) == 0) //Running operator and allowed to update our timing?
{
incop(timingoperator); //Increase time for the operator when allowed to increase (frequency=0 during PCM output)!
}
result2 = OPL2_Tremolo(coreoperator,result2); //Apply tremolo as well, after applying the new feedback signal(don't include tremolo in it)!
return result2; //Give the translated result!
}

Would that be correct behaviour? The other code is much unchanged, except for the latest rhythm code:

OPTINLINE float adlibsample(uint8_t curchan, word phase7_1, word phase8_2) {
byte op6_1, op6_2, op7_1, op7_2, op8_1, op8_2; //The four slots used during Drum samples!
word tempop_phase; //Current phase of an operator!
float result;
float immresult; //The operator result and the final result!
byte op1,op2; //The two operators to use!
curchan &= 0xF;
if (curchan >= NUMITEMS(adlibch)) return 0; //No sample with invalid channel!

//Determine the modulator and carrier to use!
op1 = adliboperators[0][curchan]; //First operator number!
op2 = adliboperators[1][curchan]; //Second operator number!
if (adlibpercussion && (curchan >= 6) && (curchan <= 8)) //We're percussion?
{
#ifndef ADLIB_RHYTHM
return 0.0f; //Disable percussion!
#else
INLINEREGISTER word tempphase;
result = 0; //Initialise the result!
//Calculations based on http://bisqwit.iki.fi/source/opl3emu.html fmopl.c
//Load our four operators for processing!
op6_1 = adliboperators[0][6];
op6_2 = adliboperators[1][6];
op7_1 = adliboperators[0][7];
op7_2 = adliboperators[1][7];
op8_1 = adliboperators[0][8];
op8_2 = adliboperators[1][8];
switch (curchan) //What channel?
{
case 6: //Bass drum?
//Generate Bass drum samples!
//Special on Bass Drum: Additive synthesis(Operator 1) is ignored.

//Calculate the frequency to use!
result = 0.0f;
if (adlibop[op6_2].volenvstatus) //Running?
{
result = calcOperator(6, op6_1, op6_1, op6_1, 0.0f, 0x00); //Calculate the modulator for feedback!

if (adlibch[6].synthmode) //Additive synthesis?
{
result = calcOperator(6, op6_2, op6_2, op6_2, 0.0f, 0x08); //Calculate the carrier without applied modulator additive!
}
else //FM synthesis?
{
result = calcOperator(6, op6_2, op6_2, op6_2, result, 0x08); //Calculate the carrier with applied modulator!
}
}

return result; //Apply the exponential! The volume is always doubled!
break;

//Comments with information from fmopl.c:
/* Phase generation is based on: */
/* HH (13) channel 7->slot 1 combined with channel 8->slot 2 (same combination as TOP CYMBAL but different output phases) */
/* SD (16) channel 7->slot 1 */
/* TOM (14) channel 8->slot 1 */
/* TOP (17) channel 7->slot 1 combined with channel 8->slot 2 (same combination as HIGH HAT but different output phases) */


Show last 95 lines
				/* Envelope generation based on: */
/* HH channel 7->slot1 */
/* SD channel 7->slot2 */
/* TOM channel 8->slot1 */
/* TOP channel 8->slot2 */
//So phase modulation is based on the Modulator signal. The volume envelope is in the Carrier signal (Hi-hat/Tom-tom) or Carrier signal().
case 7: //Hi-hat(Carrier)/Snare drum(Modulator)? High-hat uses modulator, Snare drum uses Carrier signals.
immresult = 0.0f; //Initialize immediate result!
if (adlibop[op7_1].volenvstatus) //Hi-hat on Modulator?
{
//Only input is the RNG driven by our own frequency.
//Derive frequency from channel 7(modulator) and 8(carrier).
tempop_phase = phase7_1; //Save the phase!
tempphase = (tempop_phase>>2);
tempphase ^= (tempop_phase>>7);
tempphase |= (tempop_phase>>3);
tempphase &= 1; //Only 1 bit is used!
tempphase = tempphase?(0x200|(0xD0>>2)):0xD0;
tempop_phase = phase8_2; //Calculate the phase of channel 8 carrier signal!
if (((tempop_phase>>3)^(tempop_phase>>5))&1) tempphase = 0x200|(0xD0>>2);
if (tempphase&0x200)
{
if (OPL2_RNG) tempphase = 0x2D0;
}
else if (OPL2_RNG) tempphase = (0xD0>>2);
result = calcOperator(8, op7_1,op7_1,op7_1,(float)(tempphase), 0x1F1); //Calculate the modulator, but only use the current time(position in the sine wave)!
immresult += result; //Apply the tremolo!
}
if (adlibop[op7_2].volenvstatus) //Snare drum on Carrier volume?
{
//Derive phase from the modulator.
tempphase = 0x100 << ((phase7_1 >> 8) & 1); //Bit8=0(Positive) then 0x100, else 0x200! Based on the phase to generate!
tempphase ^= (OPL2_RNG << 8); //Noise bits XOR'es phase by 0x100 when set!
result = calcOperator(7, op7_2,op7_1,op7_2,(float)tempphase, 0x1F1); //Calculate the carrier with applied modulator!
immresult += result; //Apply the tremolo!
}
result = immresult; //Load the resulting channel!
//result *= 0.5f; //We only have half(two channels combined)!
return result; //Give the result, converted to short!
break;
case 8: //Tom-tom(Carrier)/Cymbal(Modulator)? Tom-tom uses Modulator, Cymbal uses Carrier signals.
immresult = 0.0f; //Initialize immediate result!
if (adlibop[op8_1].volenvstatus) //Tom-tom(Modulator)?
{
result = calcOperator(8, op8_1, op8_1, op8_1, 0.0f, 0xD8); //Calculate the carrier without applied modulator additive! Ignore volume!
immresult += result; //Apply the exponential!
}
if (adlibop[op8_2].volenvstatus) //Cymbal(Carrier)?
{
//Derive frequency from channel 7(modulator) and 8(carrier).
tempop_phase = phase7_1; //Save the phase!
tempphase = (tempop_phase>>2);
tempphase ^= (tempop_phase>>7);
tempphase |= (tempop_phase>>3);
tempphase &= 1; //Only 1 bit is used!
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, (float)tempphase, 0x1D1); //Calculate the carrier with applied modulator! Use volume!
immresult += result; //Apply the exponential!
}

//Advance the shared percussion channel by 7-1 and 8-2!
result = calcOperator(7, op7_1, op7_1, op7_1, 0.0f, 2); //Calculate the modulator, but only use the current time(position in the sine wave)!
result = calcOperator(8, op8_2, op8_2, op8_2, 0.0f, 2); //Calculate the carrier with applied modulator! Use volume!

result = immresult; //Load the resulting channel!
//result *= 0.5f; //We only have half(two channels combined)!
return result; //Give the result, converted to short!
break;
default:
break;
}
#endif
//Not a percussion channel? Pass through!
}

//Operator 1!
//Calculate the frequency to use!
result = calcOperator(curchan, op1,op1,op1, 0.0f,0x80); //Calculate the modulator for feedback!

if (adlibch[curchan].synthmode) //Additive synthesis?
{
result += calcOperator(curchan, op2,op2,op2, 0.0f,0x00); //Calculate the carrier without applied modulator additive!
}
else //FM synthesis?
{
result = calcOperator(curchan, op2,op2,op2, result, 0x00); //Calculate the carrier with applied modulator!
}

return result; //Give the result!
}

The result sounds better than ever! 😁
The only things that might improve a bit are the rhythm synthesis, but that is still of unknown cause (although miles better than what it was, it's at least recognisable sounds now).

A recording of the current rhythm sounds (using the rhythm.dro file from these forums):

Filename
recording_302_rhythm.zip
File size
415.52 KiB
Downloads
26 downloads
File comment
UniPCemu OPL2 rhythm channels.
File license
Fair use/fair dealing exception

Edit: According to https://github.com/DhrBaksteen/ArduinoOPL2/bl … ster/indepth.md, C4 according to Google is 261.63Hz, and it says that it's block 4, F-number 342.
UniPCemu calculates an increment of 5534.
During 1 second, it will tick 275127805 on the counter for the phase.
Divide it by 1024 for the wave parts and another 1024 for a full wave to be processed, it's reaching 262.3823Hz.
So that's only slightly out of tune with the true frequency?

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

Reply 38 of 52, by Electronic Genets

User metadata
Rank Newbie
Rank
Newbie
superfury wrote:

I'm using the Rhythm.DRO from this post:

Do not use DRO files with percussion enabled. DosBox sucks and records them incorrectly to DRO file (bad values of B7 and B8).
I finally checked percussion with VGZ.
1) Uncompress it with PHP command gzdecode.
2) String 5A xx yy means: write yy to register xx
3) String 61 XX YY means wait (0xYYXX / 44100) seconds.

I was super fury after discovering that DosBox was badly recording DRO files.

superfury wrote:

int_64 calcModAndFeedback...
{
DOUBLE result,feedback; //The result! (...)

int64... double... you use a cannon to shoot down a fly.

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

Reply 39 of 52, by superfury

User metadata
Rank l33t++
Rank
l33t++
Electronic Genets wrote on 2022-05-14, 20:13:
Do not use DRO files with percussion enabled. DosBox sucks and records them incorrectly to DRO file (bad values of B7 and B8). […]
Show full quote
superfury wrote:

I'm using the Rhythm.DRO from this post:

Do not use DRO files with percussion enabled. DosBox sucks and records them incorrectly to DRO file (bad values of B7 and B8).
I finally checked percussion with VGZ.
1) Uncompress it with PHP command gzdecode.
2) String 5A xx yy means: write yy to register xx
3) String 61 XX YY means wait (0xYYXX / 44100) seconds.

I was super fury after discovering that DosBox was badly recording DRO files.

superfury wrote:

int_64 calcModAndFeedback...
{
DOUBLE result,feedback; //The result! (...)

int64... double... you use a cannon to shoot down a fly.

Perhaps not. When calculating using float instead of double, it might overflow the mabtissa and lose required precision.
I don't know for sure that int_32 has enough bits to not overflow with the inputs modulating. Do you have any idea what the maximum amount of bits required(including sign bit) in this case might be?

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