The fallthrough of case 3 into case 2 is correct: case 3 is case 2 with each second quarter 0(giving a sinus based sawtooth at double the sinus frequency).
So all I need to so is apply the volume envelope before setting the new last signal and divide the new lastsignal by 2?
About the SHRT_MAX: I'm currently clipping to fix that without modifying the volume of the sepetate adlib channels (it's at the end of the adlibgensample() function). It seems to work with multiple sounds. If I would give every channel a maximum range of 8195, wouldn't the sound be too soft when only one channel is playing?
So all I need to so is apply the volume envelope before setting the new last signal and divide the new lastsignal by 2?
You can keep unmodified previous samples, and divide the sum of the two. Or, you can halve the output value before sending it to lastsignal. Or you can change your feedback factor table. Or you can divide the result when you set the value read from feedback table. As long as you halve the value before using it as modulation.
superfury wrote:
About the SHRT_MAX: I'm currently clipping to fix that without modifying the volume of the sepetate adlib channels (it's at the end of the adlib sample function). It seems to work with multiple sounds. If I would give every channel a maximum range of 8195, wouldn't the sound be too soft when only one channel is playing?
Yes, but real chip can output (more than) 8 (but not 9) operators in 16 bits worth of data without clipping because single channel is worth 13 bits (including sign bit). You can currently output only 1 operator without clipping. It does not sound like original if it has to clip while playing more than 1 channel.
Yes, of course it is softer but that's what you have to do to fit at least 8 channels into 16 bits.
Currently a total divide of 128 to get the feedback set close to the youtube reference (I'm testing with Supaplex atm). Is this correct?
Btw how does Dosbox deal with this? Keeping the total range of all adlib channels together at maximum volume without modifying volume of each channel seperate? Afaik Dosbox should be reasonably accurate afaik? Or does it let the mixer deal with it (clip when mixing)?
Currently a total divide of 128 to get the feedback set close to the youtube reference (I'm testing with Supaplex atm). Is this correct?
I honestly don't know if it is correct based on the calculations I assumed it should have been just divide by 2.
So adlibcalcsignal gives out -1 to +1?
Outputlevel is 0 to 1?
Volenv is 0 to 1?
Therefore the output to feedback range is also -1 to +1, before converting to PCM integer with huge multiplication?
superfury wrote:
Btw how does Dosbox deal with this? Keeping the total range of all adlib channels together at maximum volume without modifying volume of each channel seperate? Afaik Dosbox should be reasonably accurate afaik? Or does it let the mixer deal with it (clip when mixing)?
But your idea about maximum volume is too loud. The whole 8 operators summed together fills the 16-bit full scale range, so that must mean a single operator is running at one eighth of the full 16-bit scale. Yes, DosBOX OPL emulation is quite good.
I've changed the output range to 4095 instead of SHRT_MAX-1.
So adlibcalcsignal gives out -1 to +1?
Outputlevel is 0 to 1?
Volenv is 0 to 1?
Therefore the output to feedback range is also - […] Show full quote
So adlibcalcsignal gives out -1 to +1?
Outputlevel is 0 to 1?
Volenv is 0 to 1?
Therefore the output to feedback range is also -1 to +1, before converting to PCM integer with huge multiplication?
This is correct. The huge multiplication used is stored in feedbacklookup (0, PI/16, PI/8, PI/4, PI/2, PI, PI*2, PI*4). So the resulting signals (range -1 to +1) gets multiplied with this feedbacklookup value (range PI/16 to PI*4). This value is used as modulation in the same way OP1 modulates OP2 when using 'FM' synthesis mode(it's PM actually). So I should normalize those values to a range of -1 to +1 instead of multiples of PI?
Edit: I've changed the range of feedback to 0-1 (converting the lookup table by multiplying with 1/(4*PI)). I've also changed the divide by 128 back to 2. Is this correct?
I also noticed the FM (PM) modulation needs fixing. If the modulator outputs -1 to +1 range, it would need to modulate the carrier phase with also at something * pi range. Why? Because original chip operator outputs -4085 to +4084 and the sine wave has 1024 phases for full wave (2*pi). Is that almost 4*2*pi then?
So I simply need to multiply result with 4085/1024 before sending it as input to calcOperator (after else //FM synthesis)?
I think so. I was going to point out you forgot the 2*pi about it, but it appears adlibwave does 2*pi multiplication already.
I did not notice this earlier, so it could be that now the feedback is off. I am more better at what range converts to what range in general, but I am not so good following your code which range should be fed to which function to achieve a specific range in the end.
I've just tried the 8086 hacked version of Wolfenstein 3D ( http://www.youtube.com/watch?v=5f7gW5X24ao ) on my x86EMU emulator with adlib. It seems to detect both normal and EMS memory (1MB out of 4MB) correctly. The highhat it uses (feedback?) during the opening sounds odd (it sounds more like a distorted tone than a highhat (although it glitches pretty much for some reason)). Is the way feedback is handled correct?
Is this correct? I don't know how to implement the two tables to the current emulation yet, so it's only used in the wave generator.
Edit: I've improved it a bit, but I get white noise together with the samples when I use the part below the return sin();
1byte lastdecodedlocation = 0xFF; 2byte PIpart=0; 3 4OPTINLINE float OPL2SinWave(const float r) 5{ 6 return sinf(r); 7 float index; 8 byte location; //The location in the table to use! 9 index = fmod(r,PI2); //Loop the sinus infinitely! 10 PIpart = 0; //Reset PI part to use! 11 if (index>=(float)PI) //Second half? 12 { 13 PIpart = 2; //Second half! 14 } 15 if (fmod(index,(float)PI)>=(0.5f*(float)PI)) //Past quarter? 16 { 17 PIpart |= 1; //Second half! 18 } 19 index = (fmod(index,(0.5f*(float)PI))/(0.5f*(float)PI))*255.0f; //Convert to full range! 20 location = (byte)index; //Set the location to use! 21 if (PIpart&1) //Reversed quarter? 22 { 23 location = ~location; //Reverse us! 24 ++location; //Reversed! 25 } 26 27 lastdecodedlocation = location; //Save the location for reference during sanity checks! 28 if (PIpart&2) //Second half is negative? 29 { 30 return -OPL2_LogSinTable[location]; //First quarter lookup reversed! 31 } 32 return OPL2_LogSinTable[location]; //First quarter lookup normal! 33}
The tables are built using the commented source page:
1//Source of the Exp and LogSin tables: https://docs.google.com/document/d/18IGx18NQY_Q1PJVZ-bHywao9bhsDoAqoIn1rIm42nwo/edit 2 for (i = 0;i < 0x100;++i) //Initialise the exponentional and log-sin tables! 3 { 4 OPL2_ExpTable[i] = round((pow(2, i / 256) - 1) * 1024); 5 OPL2_LogSinTable[i] = round(-log(sin((i + 0.5)*PI / 256 / 2)) / log(2) * 256); 6 }
The arrays themselves:
1float OPL2_ExpTable[0x100], OPL2_LogSinTable[0x100]; //The OPL2 Exponential and Log-Sin tables!
Applying the ExpTable to the float-to-word result before the 9 channels are mixed mutes the sound(no sound anymore). Although it's value is in the range 0-1(sample before multiplying to get channel sample to be added to get the 16-bit PCM sample to be played. That's effectively that 4096 multiplication).
I now get a waveform, which results in a bit of a strange 'sinus':
The attachment Snap 2016-04-21 at 16.01.33.jpg is no longer available
Is this correct? Aren't the curves supposed to be round using the table? It's supposed to be a D#.
1 adlibsetreg(0x20, 0x21); //Modulator multiple to 1! 2 adlibsetreg(0x40, 0x10); //Modulator level about 40dB! 3 adlibsetreg(0x60, 0xF7); //Modulator attack: quick; decay long! 4 adlibsetreg(0x80, 0xFF); //Modulator sustain: medium; release: medium 5 adlibsetreg(0xA0, 0x98); //Set voice frequency's LSB (it'll be a D#)! 6 adlibsetreg(0x23, 0x21); //Set the carrier's multiple to 1! 7 adlibsetreg(0x43, 0x00); //Set the carrier to maximum volume (about 47dB). 8 adlibsetreg(0x63, 0xFF); //Carrier attack: quick; decay: long! 9 adlibsetreg(0x83, 0x0F); //Carrier sustain: medium; release: medium! 10 adlibsetreg(0xB0, 0x31); //Turn the voice on; set the octave and freq MSB!
Edit: I seem to have fixed it by implementing the 0th entry as max instead of 255th entry(which is 0). This gives a proper waveform(sort of a sinus, although not perfectly round):
1OPTINLINE float OPL2SinWave(const float r) 2{ 3 float index; 4 float entry; //The entry to convert! 5 byte location; //The location in the table to use! 6 byte PIpart = 0; //Default: part 0! 7 index = fmod(r,PI2); //Loop the sinus infinitely! 8 if (index>=(float)PI) //Second half? 9 { 10 PIpart = 2; //Second half! 11 } 12 if (fmod(index,(float)PI)>=(0.5f*(float)PI)) //Past quarter? 13 { 14 PIpart |= 1; //Second half! 15 } 16 index = (fmod(index,(0.5f*(float)PI))/(0.5f*(float)PI))*255.0f; //Convert to full range! 17 location = (byte)index; //Set the location to use! 18 if (PIpart&1) //Reversed quarter(first and third quarter)? 19 { 20 location = 255-location; //Reverse us! 21 } 22 23 entry = OPL2_LogSinTable[0] - OPL2_LogSinTable[location]; //First quarter lookup! Reverse the signal (it goes from Umax to Umin instead of a normal sin(0PI to 0.5PI)) 24 if (PIpart & 2) //Second half is negative? 25 { 26 entry = -entry; //First quarter lookup reversed! 27 } 28 return entry; //Give the processed entry! 29}
The adlib emulation is now correctly generating sound again(this time using the two lookup tables (Exponential and Log-sin tables) instead of the old sin() functions).
I've now adjusted the adlib to (re)start the modulator and/or carrier signals depending on the channel being turned on/off:
- Melodic channels affect both the modulator and generator channels.
- Bass drum affects both channels as well.
- Snare drum affects channel 7 carrier channel.
- Tom-tom affects channel 7 modulator channel.
- Cymbal affects channel 8 carrier channel.
- Hi-hat affects channel 8 modulator channel.
The channel numbers are from 0-8 in the above description (0-based).
I assume the Bass drum is a normal melodic channel mostly? How does the RNG bit(1-bit value generated every time an output signal is generated for all channels), which is based on the shifts of the RNG register (23-bit shift register) affect this signal?
How is the output of the other rhythm channels generated? What's the difference between those rhythm channels and normal melodic channels? How does the RNG 1-bit value affect those? Is it simply used as the modulator, with the modulator or carrier (see above list) being used as the carrier instead?
So:
Snare drum = RNG bit -> channel 7 carrier -> output
Tom-tom = RNG bit -> channel 7 modulator -> output
Cymbal = RNG bit -> channel 8 carrier -> output
Hi-hat = RNG bit -> channel 8 modulator -> output
Is this correct? How does the RNG affect those 4 outputs? Is the carrier/modulator being used as carrier correct? Jepael?
I've not very knowledgeable about the rhythm mode, but the RNG may be applied differently to each channel because each rhythm instrument needs different timbre. At least in one very simple test case which I don't remember, the RNG was just xoring a certain phase bit, so the real chip matched what was in MAME source code.
1 //Determine the modulator and carrier to use! 2 op1 = adliboperators[0][curchan]; //First operator number! 3 op2 = adliboperators[1][curchan]; //Second operator number! 4 op1frequency = adlibfreq(op1, curchan); //Load the first frequency! 5 6 if (adlibpercussion && (curchan >= 6) && (curchan <= 8)) //We're percussion? 7 { 8 result = 0.0f; //Initialise the result! 9 //Calculations based on http://bisqwit.iki.fi/source/opl3emu.html fmopl.c 10 switch (curchan) //What channel? 11 { 12 case 6: //Bass drum? 13 //Generate Bass drum samples! 14 //Calculate the frequency to use! 15 result = calcOperator(curchan, op1, op1frequency, 0.0f,1); //Calculate the modulator for feedback! 16 if (adlibch[curchan].synthmode) //Additive synthesis? 17 { 18 //Special on Bass Drum: Additive synthesis(Operator 1) is ignored. 19 result = calcOperator(curchan, op2, adlibfreq(op2, curchan), 0.0f, 0); //Calculate the carrier without applied modulator additive! 20 } 21 else //FM synthesis? 22 { 23 result = calcOperator(curchan, op2, adlibfreq(op2, curchan), OPL2_Exponential(result), 0); //Calculate the carrier with applied modulator! 24 } 25 26 result = OPL2_Exponential(result); //Apply the exponential! 27 28 result *= adlib_scaleFactor; //Convert to output scale (We're only going from -1.0 to +1.0 up to this point), convert to signed 16-bit scale! 29 return (short)result; //Give the result, converted to short! 30 break; 31 32 //Comments with information from fmopl.c: 33 /* Phase generation is based on: */ 34 35 /* HH (13) channel 7->slot 1 combined with channel 8->slot 2 (same combination as TOP CYMBAL but different output phases) */ 36 37 /* SD (16) channel 7->slot 1 */ 38 39 /* TOM (14) channel 8->slot 1 */ 40 41 /* TOP (17) channel 7->slot 1 combined with channel 8->slot 2 (same combination as HIGH HAT but different output phases) */ 42 43 44 /* Envelope generation based on: */ 45 46 /* HH channel 7->slot1 */ 47 48 /* SD channel 7->slot2 */ 49 50 /* TOM channel 8->slot1 */ 51 52 /* TOP channel 8->slot2 */ 53 case 7: //Hi-hat/Snare drum? High-hat uses modulator, Snare drum uses Carrier signals. 54 if (adlibop[op1].volenvstatus) //Hi-hat? 55 { 56 //Still unimplemented until the calculations have been figured out. 57 } 58 if (adlibop[op2].volenvstatus) //Snare drum? 59 { 60 //Derive frequency from channel 0.
…Show last 28 lines
61 result = calcOperator(curchan, op1, op1frequency, 0.0f,1); //Calculate the modulator for feedback! 62 tempphase = (result>=0.0f)?0x200:0x100; //Bit8=0(Positive) then 0x100, else 0x200! 63 tempphase ^= (OPL2_RNG<<8); //Noise bits XOR'es phase by 0x100! 64 result = calcOperator(curchan, op2, adlibfreq(op2, curchan), OPL2_Exponential((float)tempphase), 0); //Calculate the carrier with applied modulator! 65 result = OPL2_Exponential(result); //Apply the exponential! 66 } 67 result *= 0.5; //We only have half(two channels combined)! 68 result *= adlib_scaleFactor; //Convert to output scale (We're only going from -1.0 to +1.0 up to this point), convert to signed 16-bit scale! 69 return (short)result; //Give the result, converted to short! 70 break; 71 case 8: //Tom-tom/Cymbal? Tom-tom uses Modulator, Cymbal uses Carrier signals. 72 if (adlibop[op1].volenvstatus) //Tom-tom? 73 { 74 result = calcOperator(curchan, op1, adlibfreq(op1, curchan), 0.0f, 0); //Calculate the carrier with applied modulator! 75 result = OPL2_Exponential(result); //Apply the exponential! 76 } 77 if (adlibop[op2].volenvstatus) //Cymbal? 78 { 79 //Still unimplemented until the calculations have been figured out. 80 } 81 result *= 0.5; //We only have half(two channels combined)! 82 result *= adlib_scaleFactor; //Convert to output scale (We're only going from -1.0 to +1.0 up to this point), convert to signed 16-bit scale! 83 return (short)result; //Give the result, converted to short! 84 break; 85 } 86 return 0; //Percussion isn't supported yet for this channel! 87 ... Normal melodic channel procesing ... 88 }
I also extended the key-on function by adding better on detection and applying:
1void writeadlibKeyON(byte channel, byte forcekeyon) 2{ 3 byte keyon; 4 byte oldkeyon; 5 oldkeyon = adlibch[channel].keyon; //Current&old key on! 6 keyon = ((adlibregmem[0xB0 + (channel&0xF)] >> 5) & 1)?3:0; //New key on for melodic channels? Affect both operators! This disturbs percussion mode! 7 if (adlibpercussion && (channel&0x80)) //Percussion enabled and percussion channel changed? 8 { 9 switch (channel&0xF) //What channel? 10 { 11 case 6: //Bass drum? Uses the channel normally! 12 keyon = (adlibregmem[0xBD]&0x10)?3:0; //Bass drum on? Key on on both operators! 13 channel = 6; //Use channel 6! 14 break; 15 case 7: //Hi-hat/Snare drum? High-hat uses modulator, Snare drum uses Carrier signals. 16 keyon = adlibregmem[0xBD]; //Snare drum/Hi-hat on? 17 keyon = ((keyon>>2)&2)|(keyon&1); //Shift the information to modulator and carrier positions! 18 channel = 7; //Use channel 7! 19 break; 20 case 8: //Tom-tom/Cymbal? Tom-tom uses Modulator, Cymbal uses Carrier signals. 21 keyon = adlibregmem[0xBD]; //Cymbal/hi-hat on? Use the full register for processing! 22 keyon = ((keyon>>2)&1)|(keyon&2); //Shift the information to modulator and carrier positions! 23 channel = 8; //Use channel 8! 24 break; 25 default: //Unknown channel? 26 //New key on for melodic channels? Don't change anything! 27 break; 28 } 29 } 30 if ((adliboperators[0][channel]!=0xFF) && (((keyon&1) && ((oldkeyon^keyon)&1)) || (forcekeyon&1))) //Key ON on operator #1? 31 { 32 adlibop[adliboperators[0][channel]].volenvstatus = 1; //Start attacking! 33 adlibop[adliboperators[0][channel]].volenvcalculated = adlibop[adliboperators[0][channel]].volenv = 0.0025f; 34 adlibop[adliboperators[0][channel]].freq0 = adlibop[adliboperators[0][channel]].time = 0.0f; //Initialise operator signal! 35 memset(&adlibop[adliboperators[0][channel]].lastsignal, 0, sizeof(adlibop[0].lastsignal)); //Reset the last signals! 36 } 37 if ((adliboperators[1][channel]!=0xFF) && (((keyon&2) && ((oldkeyon^keyon)&2)) || (forcekeyon&2))) //Key ON on operator #2? 38 { 39 adlibop[adliboperators[1][channel]].volenvstatus = 1; //Start attacking! 40 adlibop[adliboperators[1][channel]].volenvcalculated = adlibop[adliboperators[0][channel]].volenv = 0.0025f; 41 adlibop[adliboperators[1][channel]].freq0 = adlibop[adliboperators[1][channel]].time = 0.0f; //Initialise operator signal! 42 memset(&adlibop[adliboperators[1][channel]].lastsignal, 0, sizeof(adlibop[1].lastsignal)); //Reset the last signals! 43 } 44 adlibch[channel].freq = adlibregmem[0xA0 + channel] | ((adlibregmem[0xB0 + channel] & 3) << 8); 45 adlibch[channel].convfreq = ((double)adlibch[channel].freq * 0.7626459); 46 adlibch[channel].keyon = keyon | forcekeyon; //Key is turned on? 47 adlibch[channel].octave = (adlibregmem[0xB0 + channel] >> 2) & 7; 48}
As you can see it now applies key-on to either both the modulator and carrier (melodic channel) or to modulator or carrier signals (Drum channels, except Bass drum).
The RNG is simply updated once a sample of the adlib is generating (one time for all channels, so about 49000 times per second, before the adlib starts generating a sample for each of the 9 channels):
1uint_32 OPL2_RNGREG = 0; 2uint_32 OPL2_RNG = 0; //The current random generated sample! 3 4OPTINLINE void OPL2_stepRNG() //Runs at the sampling rate! 5{ 6 OPL2_RNG = ( (OPL2_RNGREG) ^ (OPL2_RNGREG>>14) ^ (OPL2_RNGREG>>15) ^ (OPL2_RNGREG>>22) ) & 1; //Get the current RNG! 7 OPL2_RNGREG = (OPL2_RNG<<22) | (OPL2_RNGREG>>1); 8}
Is this correct? Trying to run Eye of the Beholder seems to give strange sounds at some points? Or is this simply because the OPL2 now uses the lookup tables?
The key-On is detected by looking at the 0xBD register's bits:
1 case 0xA0: 2 case 0xB0: 3 if (portnum <= 0xB8) 4 { //octave, freq, key on 5 if ((portnum & 0xF) > 8) goto unsupporteditem; //Ignore A9-AF! 6 portnum &= 0xF; //Only take the lower nibble (the channel)! 7 writeadlibKeyON((byte)portnum,0); //Write to this port! Don't force the key on! 8 } 9 else if (portnum == 0xBD) //Percussion settings etc. 10 { 11 adlibpercussion = (value & 0x20)?1:0; //Percussion enabled? 12 if (((oldval^value)&0x1F) && adlibpercussion) //Percussion enabled and changed state? 13 { 14 writeadlibKeyON(0x86,0); //Write to this port(Bass drum)! Don't force the key on! 15 writeadlibKeyON(0x87,0); //Write to this port(Snare drum/Tom-tom)! Don't force the key on! 16 writeadlibKeyON(0x88,0); //Write to this port(Cymbal/Hi-hat)! Don't force the key on! 17 } 18 } 19 break;
Btw forcing the key on (value 1 given) is only used when in CSM mode (Composite Speech synthesis Mode). This simply calls the writeadlibKeyON(channel,3) function when either or both timers expire.
Also I seem to notice that applying the 640K memory hole (640K-1M memory being nonexistant) seems to cause stuff like Eye of the Beholder to stop running (crashing into the BIOS at FFFF:FFFF) for some reason?
1//Direct memory access (for the entire emulator) 2byte MMU_directrb(uint_32 realaddress) //Direct read from real memory (with real data direct)! 3{ 4 byte invalidlocation=0; 5 //Apply the 640K memory hole! 6 if (realaddress&0x100000) //1MB+? 7 { 8 realaddress -= (0x100000-0xA0000); //Patch to less memory to make memory linear! 9 } 10 else if (realaddress>=0xA0000) //640K+ memory hole addressed? 11 { 12 invalidlocation = 1; //We're an invalid location! 13 } 14 if ((realaddress>=MMU.size) || invalidlocation) //Overflow/invalid location? 15 { 16 MMU.invaddr = 1; //Signal invalid address! 17 execNMI(1); //Execute an NMI from memory! 18 return 0xFF; //Nothing there! 19 } 20 return MMU.memory[realaddress]; //Get data, wrap arround! 21} 22 23byte LOG_MMU_WRITES = 0; //Log MMU writes? 24 25void MMU_directwb(uint_32 realaddress, byte value) //Direct write to real memory (with real data direct)! 26{ 27 byte invalidlocation=0; 28 if (LOG_MMU_WRITES) //Data debugging? 29 { 30 dolog("debugger","MMU: Writing to real %08X=%02X (%c)",realaddress,value,value?value:0x20); 31 } 32 //Apply the 640K memory hole! 33 if (realaddress&0x100000) //1MB+? 34 { 35 realaddress -= (0x100000-0xA0000); //Patch to less memory to make memory linear! 36 } 37 else if (realaddress>=0xA0000) //640K+ memory hole? 38 { 39 invalidlocation = 1; //We're an invalid location! 40 } 41 if ((realaddress>=MMU.size) || invalidlocation) //Overflow/invalid location? 42 { 43 MMU.invaddr = 1; //Signal invalid address! 44 execNMI(1); //Execute an NMI from memory! 45 return; //Abort: can't write here! 46 } 47 MMU.memory[realaddress] = value; //Set data, full memory protection! 48 if (realaddress>user_memory_used) //More written than present in memory (first write to addr)? 49 { 50 user_memory_used = realaddress; //Update max memory used! 51 } 52}
I've implemented an optimized x86EMU adaption of the code in opl3emu's fmopl.c:
1OPTINLINE short adlibsample(uint8_t curchan) { 2 byte op7_0, op7_1, op8_0, op8_1; //The four slots used during Drum samples! 3 byte tempop_phase; //Current phase of an operator! 4 float result,immresult; //The operator result and the final result! 5 byte op1,op2; //The two operators to use! 6 float op1frequency; 7 curchan &= 0xF; 8 if (curchan >= NUMITEMS(adlibch)) return 0; //No sample with invalid channel! 9 10 //Determine the modulator and carrier to use! 11 op1 = adliboperators[0][curchan]; //First operator number! 12 op2 = adliboperators[1][curchan]; //Second operator number! 13 op1frequency = adlibfreq(op1, curchan); //Load the first frequency! 14 15 if (adlibpercussion && (curchan >= 6) && (curchan <= 8)) //We're percussion? 16 { 17 register uint_32 tempphase; 18 result = 0.0f; //Initialise the result! 19 //Calculations based on http://bisqwit.iki.fi/source/opl3emu.html fmopl.c 20 //Load our four operators for processing! 21 op7_0 = adliboperators[0][7]; 22 op7_1 = adliboperators[1][7]; 23 op8_0 = adliboperators[0][8]; 24 op8_1 = adliboperators[1][8]; 25 switch (curchan) //What channel? 26 { 27 case 6: //Bass drum? 28 //Generate Bass drum samples! 29 //Calculate the frequency to use! 30 result = calcOperator(curchan, op1, op1frequency, 0.0f,1,op1,1); //Calculate the modulator for feedback! 31 if (adlibch[curchan].synthmode) //Additive synthesis? 32 { 33 //Special on Bass Drum: Additive synthesis(Operator 1) is ignored. 34 result = calcOperator(curchan, op2, adlibfreq(op2, curchan), 0.0f, 0,op2,1); //Calculate the carrier without applied modulator additive! 35 } 36 else //FM synthesis? 37 { 38 result = calcOperator(curchan, op2, adlibfreq(op2, curchan), OPL2_Exponential(result), 0,op2,1); //Calculate the carrier with applied modulator! 39 } 40 41 result = OPL2_Exponential(result*2.0f); //Apply the exponential! The volume is always doubled! 42 43 result *= adlib_scaleFactor; //Convert to output scale (We're only going from -1.0 to +1.0 up to this point), convert to signed 16-bit scale! 44 return (short)result; //Give the result, converted to short! 45 break; 46 47 //Comments with information from fmopl.c: 48 /* Phase generation is based on: */ 49 /* HH (13) channel 7->slot 1 combined with channel 8->slot 2 (same combination as TOP CYMBAL but different output phases) */ 50 /* SD (16) channel 7->slot 1 */ 51 /* TOM (14) channel 8->slot 1 */ 52 /* TOP (17) channel 7->slot 1 combined with channel 8->slot 2 (same combination as HIGH HAT but different output phases) */ 53 54 55 /* Envelope generation based on: */ 56 /* HH channel 7->slot1 */ 57 /* SD channel 7->slot2 */ 58 /* TOM channel 8->slot1 */ 59 60 /* TOP channel 8->slot2 */
…Show last 89 lines
61 //So phase modulation is based on the Modulator signal. The volume envelope is in the Modulator signal (Hi-hat/Tom-tom) or Carrier signal () 62 case 7: //Hi-hat(Carrier)/Snare drum(Modulator)? High-hat uses modulator, Snare drum uses Carrier signals. 63 immresult = 0.0f; //Initialize immediate result! 64 if (adlibop[op7_1].volenvstatus) //Snare drum on modulator? 65 { 66 //Derive frequency from channel 0. 67 tempphase = 0x100<<((getphase(op7_0)>>8)&1); //Bit8=0(Positive) then 0x100, else 0x200! Based on the phase to generate! 68 tempphase ^= (OPL2_RNG<<8); //Noise bits XOR'es phase by 0x100 when set! 69 result = calcOperator(curchan, op7_0, op1frequency, 0.0f,1,op7_0,(!adlibop[op8_1].volenvstatus && !adlibop[op7_0].volenvstatus)); //Calculate the modulator, but only use the current time(position in the sine wave)! 70 result = calcOperator(curchan, op7_1, adlibfreq(op7_1, curchan), ((((float)tempphase)/(float)0x300)*(float)(PI2)), 0,op7_1,1); //Calculate the carrier with applied modulator! 71 immresult += OPL2_Exponential(result); //Apply the exponential! 72 } 73 if (adlibop[op7_0].volenvstatus) //Hi-hat on carrier? 74 { 75 //Derive frequency from channel 7(modulator) and 8(carrier).~ 76 tempop_phase = getphase(op7_0); //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 = getphase(op8_1); //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 90 result = calcOperator(curchan, op7_0, adlibfreq(op7_0, curchan), 0.0f,1,op7_0,!adlibop[op8_1].volenvstatus); //Calculate the modulator, but only use the current time(position in the sine wave)! 91 result = calcOperator(curchan, op7_1, adlibfreq(op7_1, curchan), ((((float)tempphase)/(float)0x300)*(float)(PI2)), 0,op7_0,1); //Calculate the carrier with applied modulator! 92 immresult += OPL2_Exponential(result); //Apply the exponential! 93 } 94 result = immresult; //Load the resulting channel! 95 result *= 0.5f; //We only have half(two channels combined)! 96 result *= adlib_scaleFactor; //Convert to output scale (We're only going from -1.0 to +1.0 up to this point), convert to signed 16-bit scale! 97 return (short)result; //Give the result, converted to short! 98 break; 99 case 8: //Tom-tom(Carrier)/Cymbal(Modulator)? Tom-tom uses Modulator, Cymbal uses Carrier signals. 100 immresult = 0.0f; //Initialize immediate result! 101 if (adlibop[op8_1].volenvstatus) //Cymbal(Modulator)? 102 { 103 //Derive frequency from channel 7(modulator) and 8(carrier). 104 tempop_phase = getphase(op7_0); //Save the phase! 105 tempphase = (tempop_phase>>2); 106 tempphase ^= (tempop_phase>>7); 107 tempphase |= (tempop_phase>>3); 108 tempphase &= 1; //Only 1 bit is used! 109 tempphase <<= 9; //0x200 when 1 makes it become 0x300 110 tempphase |= 0x100; //0x100 is always! 111 tempop_phase = getphase(op8_1); //Calculate the phase of channel 8 carrier signal! 112 if (((tempop_phase>>3)^(tempop_phase>>5))&1) tempphase = 0x300; 113 114 result = calcOperator(curchan, op7_0, adlibfreq(op7_0, curchan), 0.0f,1,op7_0,1); //Calculate the modulator, but only use the current time(position in the sine wave)! 115 result = calcOperator(curchan, op7_1, adlibfreq(op7_1, curchan), ((((float)tempphase)/(float)0x300)*(float)(PI2)), 0,op8_1,1); //Calculate the carrier with applied modulator! 116 immresult += OPL2_Exponential(result); //Apply the exponential! 117 } 118 if (adlibop[op8_0].volenvstatus) //Tom-tom(Carrier)? 119 { 120 result = calcOperator(curchan, op8_0, adlibfreq(op8_0, curchan), 0.0f, 0,op8_0,1); //Calculate the carrier with applied modulator! 121 immresult += OPL2_Exponential(result); //Apply the exponential! 122 } 123 result = immresult; //Load the resulting channel! 124 result *= 0.5f; //We only have half(two channels combined)! 125 result *= adlib_scaleFactor; //Convert to output scale (We're only going from -1.0 to +1.0 up to this point), convert to signed 16-bit scale! 126 return (short)result; //Give the result, converted to short! 127 break; 128 } 129 return 0; //Percussion isn't supported yet for this channel! 130 } 131 132 //Operator 1! 133 //Calculate the frequency to use! 134 result = calcOperator(curchan, op1, op1frequency, 0.0f,1,op1,1); //Calculate the modulator for feedback! 135 136 if (adlibch[curchan].synthmode) //Additive synthesis? 137 { 138 result += calcOperator(curchan, op2, adlibfreq(op2, curchan), 0.0f, 0,op2,1); //Calculate the carrier without applied modulator additive! 139 } 140 else //FM synthesis? 141 { 142 result = calcOperator(curchan, op2, adlibfreq(op2, curchan), OPL2_Exponential(result), 0,op2,1); //Calculate the carrier with applied modulator! 143 } 144 145 result = OPL2_Exponential(result); //Apply the exponential! 146 147 result *= adlib_scaleFactor; //Convert to output scale (We're only going from -1.0 to +1.0 up to this point), convert to signed 16-bit scale! 148 return (short)result; //Give the result, converted to short! 149}
Although some stuff sounds fine (Bass drum and Tom-tom afaik, as they're the same(in additive synthesis mode, fm mode just adds the modulator's modulating the bass drum carrier)) the other rhythm parts give incorrect results as far as I know (don't have a reference). The hi-hat sounds too clean (although some small vibrato-like vibration is present) and the cymbal(I think) seems too hard.
I've improved it a bit (also added a DRO file player to the current MIDI player (thus now renamed Music player for that reason) to test with actual correct OPL2 output recordings):
1//Calculate an operator signal! 2OPTINLINE float calcOperator(byte curchan, byte operator, float frequency, float modulator, byte feedback, byte volenvoperator, byte updateoperator) 3{ 4 if (operator==0xFF) return 0.0f; //Invalid operator! 5 float result,feedbackresult; //Our variables? 6 //Generate the signal! 7 if (feedback) //Apply channel feedback? 8 { 9 modulator = adlibop[operator].lastsignal[0]; //Take the previous last signal! 10 modulator += adlibop[operator].lastsignal[1]; //Take the last signal! 11 modulator *= adlibch[curchan].feedback; //Calculate current feedback! 12 modulator = OPL2_Exponential(modulator); //Apply the exponential conversion! 13 } 14 15 //Generate the correct signal! 16 result = calcAdlibSignal(adlibop[operator].wavesel&wavemask, modulator, frequency?frequency:adlibop[operator].lastfreq, &adlibop[operator].freq0, &adlibop[operator].time); //Take the last frequency or current frequency! 17 if (volenvoperator==0xFF) goto skipvolenv; 18 result *= adlibop[volenvoperator].outputlevel; //Apply the output level to the operator! 19 result *= adlibop[volenvoperator].volenv; //Apply current volume of the ADSR envelope! 20 skipvolenv: //Skip vol env operator! 21 if (frequency && updateoperator) //Running operator and allowed to update our signal? 22 { 23 feedbackresult = result; //Load the current feedback value! 24 feedbackresult *= 0.5f; //Prevent overflow (we're adding two values together, so take half the value calculated)! 25 adlibop[operator].lastsignal[0] = adlibop[operator].lastsignal[1]; //Set last signal #0 to #1(shift into the older one)! 26 adlibop[operator].lastsignal[1] = feedbackresult; //Set the feedback result! 27 adlibop[operator].lastfreq = frequency; //We were last running at this frequency! 28 incop(operator,frequency); //Increase time for the operator when allowed to increase (frequency=0 during PCM output)! 29 } 30 return result; //Give the result! 31} 32 33float adlib_scaleFactor = 65535.0f / 1018.0f; //We're running 8 channels in a 16-bit space, so 1/8 of SHRT_MAX 34 35OPTINLINE uint_32 getphase(byte operator) //Get the current phrase of the operator! 36{ 37 return (word)((fmod(adlibop[operator].time,PI2)/PI2)*511.0f); //512 points (9-bits value?) 38} 39 40OPTINLINE short adlibsample(uint8_t curchan) { 41 byte op7_0, op7_1, op8_0, op8_1; //The four slots used during Drum samples! 42 byte tempop_phase; //Current phase of an operator! 43 float result,immresult; //The operator result and the final result! 44 byte op1,op2; //The two operators to use! 45 float op1frequency; 46 curchan &= 0xF; 47 if (curchan >= NUMITEMS(adlibch)) return 0; //No sample with invalid channel! 48 49 //Determine the modulator and carrier to use! 50 op1 = adliboperators[0][curchan]; //First operator number! 51 op2 = adliboperators[1][curchan]; //Second operator number! 52 op1frequency = adlibfreq(op1, curchan); //Load the first frequency! 53 54 if (adlibpercussion && (curchan >= 6) && (curchan <= 8)) //We're percussion? 55 { 56 register uint_32 tempphase; 57 result = 0.0f; //Initialise the result! 58 //Calculations based on http://bisqwit.iki.fi/source/opl3emu.html fmopl.c 59 //Load our four operators for processing! 60 op7_0 = adliboperators[0][7];
…Show last 111 lines
61 op7_1 = adliboperators[1][7]; 62 op8_0 = adliboperators[0][8]; 63 op8_1 = adliboperators[1][8]; 64 switch (curchan) //What channel? 65 { 66 case 6: //Bass drum? 67 //Generate Bass drum samples! 68 //Calculate the frequency to use! 69 result = calcOperator(curchan, op1, op1frequency, 0.0f,1,op1,1); //Calculate the modulator for feedback! 70 if (adlibch[curchan].synthmode) //Additive synthesis? 71 { 72 //Special on Bass Drum: Additive synthesis(Operator 1) is ignored. 73 result = calcOperator(curchan, op2, adlibfreq(op2, curchan), 0.0f, 0,op2,1); //Calculate the carrier without applied modulator additive! 74 } 75 else //FM synthesis? 76 { 77 result = calcOperator(curchan, op2, adlibfreq(op2, curchan), OPL2_Exponential(result), 0,op2,1); //Calculate the carrier with applied modulator! 78 } 79 80 result = OPL2_Exponential(result*2.0f); //Apply the exponential! The volume is always doubled! 81 82 result *= adlib_scaleFactor; //Convert to output scale (We're only going from -1.0 to +1.0 up to this point), convert to signed 16-bit scale! 83 return (short)result; //Give the result, converted to short! 84 break; 85 86 //Comments with information from fmopl.c: 87 /* Phase generation is based on: */ 88 /* HH (13) channel 7->slot 1 combined with channel 8->slot 2 (same combination as TOP CYMBAL but different output phases) */ 89 /* SD (16) channel 7->slot 1 */ 90 /* TOM (14) channel 8->slot 1 */ 91 /* TOP (17) channel 7->slot 1 combined with channel 8->slot 2 (same combination as HIGH HAT but different output phases) */ 92 93 94 /* Envelope generation based on: */ 95 /* HH channel 7->slot1 */ 96 /* SD channel 7->slot2 */ 97 /* TOM channel 8->slot1 */ 98 99 /* TOP channel 8->slot2 */ 100 //So phase modulation is based on the Modulator signal. The volume envelope is in the Modulator signal (Hi-hat/Tom-tom) or Carrier signal () 101 case 7: //Hi-hat(Carrier)/Snare drum(Modulator)? High-hat uses modulator, Snare drum uses Carrier signals. 102 immresult = 0.0f; //Initialize immediate result! 103 if (adlibop[op7_1].volenvstatus) //Snare drum on modulator? 104 { 105 //Derive frequency from channel 0. 106 tempphase = 0x100<<((getphase(op7_0)>>8)&1); //Bit8=0(Positive) then 0x100, else 0x200! Based on the phase to generate! 107 tempphase ^= (OPL2_RNG<<8); //Noise bits XOR'es phase by 0x100 when set! 108 result = calcOperator(curchan, op7_0, op1frequency, 0.0f,1,op7_0,(!adlibop[op8_1].volenvstatus && !adlibop[op7_0].volenvstatus)); //Calculate the modulator, but only use the current time(position in the sine wave)! 109 result = calcOperator(curchan, op7_1, adlibfreq(op7_1, curchan), OPL2_Exponential((((float)tempphase)/(float)0x300)*3137.0f), 0,op7_1,1); //Calculate the carrier with applied modulator! 110 immresult += OPL2_Exponential(result); //Apply the exponential! 111 } 112 if (adlibop[op7_0].volenvstatus) //Hi-hat on carrier? 113 { 114 //Derive frequency from channel 7(modulator) and 8(carrier).~ 115 tempop_phase = getphase(op7_0); //Save the phase! 116 tempphase = (tempop_phase>>2); 117 tempphase ^= (tempop_phase>>7); 118 tempphase |= (tempop_phase>>3); 119 tempphase &= 1; //Only 1 bit is used! 120 tempphase = tempphase?(0x200|(0xD0>>2)):0xD0; 121 tempop_phase = getphase(op8_1); //Calculate the phase of channel 8 carrier signal! 122 if (((tempop_phase>>3)^(tempop_phase>>5))&1) tempphase = 0x200|(0xD0>>2); 123 if (tempphase&0x200) 124 { 125 if (OPL2_RNG) tempphase = 0x2D0; 126 } 127 else if (OPL2_RNG) tempphase = (0xD0>>2); 128 129 result = calcOperator(curchan, op7_0, adlibfreq(op7_0, curchan), 0.0f,1,op7_0,!adlibop[op8_1].volenvstatus); //Calculate the modulator, but only use the current time(position in the sine wave)! 130 result = calcOperator(curchan, op7_1, adlibfreq(op7_1, curchan), OPL2_Exponential((((float)tempphase)/(float)0x300)*3137.0f), 0,op7_0,1); //Calculate the carrier with applied modulator! 131 immresult += OPL2_Exponential(result); //Apply the exponential! 132 } 133 result = immresult; //Load the resulting channel! 134 result *= 0.5f; //We only have half(two channels combined)! 135 result *= adlib_scaleFactor; //Convert to output scale (We're only going from -1.0 to +1.0 up to this point), convert to signed 16-bit scale! 136 return (short)result; //Give the result, converted to short! 137 break; 138 case 8: //Tom-tom(Carrier)/Cymbal(Modulator)? Tom-tom uses Modulator, Cymbal uses Carrier signals. 139 immresult = 0.0f; //Initialize immediate result! 140 if (adlibop[op8_1].volenvstatus) //Cymbal(Modulator)? 141 { 142 //Derive frequency from channel 7(modulator) and 8(carrier). 143 tempop_phase = getphase(op7_0); //Save the phase! 144 tempphase = (tempop_phase>>2); 145 tempphase ^= (tempop_phase>>7); 146 tempphase |= (tempop_phase>>3); 147 tempphase &= 1; //Only 1 bit is used! 148 tempphase <<= 9; //0x200 when 1 makes it become 0x300 149 tempphase |= 0x100; //0x100 is always! 150 tempop_phase = getphase(op8_1); //Calculate the phase of channel 8 carrier signal! 151 if (((tempop_phase>>3)^(tempop_phase>>5))&1) tempphase = 0x300; 152 153 result = calcOperator(curchan, op7_0, adlibfreq(op7_0, curchan), 0.0f,1,op7_0,1); //Calculate the modulator, but only use the current time(position in the sine wave)! 154 result = calcOperator(curchan, op7_1, adlibfreq(op7_1, curchan), OPL2_Exponential((((float)tempphase)/(float)0x300)*3137.0f), 0,op8_1,1); //Calculate the carrier with applied modulator! 155 immresult += OPL2_Exponential(result); //Apply the exponential! 156 } 157 if (adlibop[op8_0].volenvstatus) //Tom-tom(Carrier)? 158 { 159 result = calcOperator(curchan, op8_0, adlibfreq(op8_0, curchan), 0.0f, 0,op8_0,1); //Calculate the carrier with applied modulator! 160 immresult += OPL2_Exponential(result); //Apply the exponential! 161 } 162 result = immresult; //Load the resulting channel! 163 result *= 0.5f; //We only have half(two channels combined)! 164 result *= adlib_scaleFactor; //Convert to output scale (We're only going from -1.0 to +1.0 up to this point), convert to signed 16-bit scale! 165 return (short)result; //Give the result, converted to short! 166 break; 167 } 168 return 0; //Percussion isn't supported yet for this channel! 169 } 170... Melodic channel processing follows after this ...
The sound doesn't sound right, none of the instruments give correct output for some reason.
The ADSR is now implemented (goes from 0 to 64(attack), then back to 0 during the decay and release phases). The problem left is that, while the volume envelope and volume are now implemented according to http://yehar.com/blog/?p=665 , it practically does nothing: The input data (the result from the LogSin table) has a range of 0 to 2137. Adding the volume envelope (range 0-64, uses times 8(SHL 3)) and volume itself (SHL 5=times 32) results in adding up to 2520 to the sample from the LogSin table. Thus a result of 0-4657. Thus the volume is only half dictated by the envelope and volume. Having zero volume and envelope (=silence) will result in the source signal being full strength(2137) instead of 0?