VOGONS


Reply 40 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

I currently have 'fixed' this strange behaviour by converting the 2520 into a factor (0-2520 becomes 0-1) which is then multiplied by the full strength sample (-2137 to 2137) to get the resulting sample. This sample (in the range -2137 to 2137) is converted into 0-255(or alternatively 0 to -255 converted to 0-255 with the negative bit being re-added after lookup in the Exp table) to act as an index into the Exp table, resulting in the positive sample at output strength. Finally this resulting sample will be made negative when the original input value (0 to -255) was also negative. Thus this way it results in a value of -ExpTable[255] to ExpTable[255].

I know this isn't the official way, but is the simple adding of the volume&envelope before looking the resulting value up in the Exponential table really correct? As far as I know volume envelopes(ADSR) always start with 0, then ramp up to max(attack), then decay to sustain, wait during sustain, finally to release until back at 0(Silence). If the volume envelope would start at full volume and end at it, it won't end in silence as it's supposed to?
It simply start out normally(100% strength), becomes loud(attack), then slightly less loud(decay), keep being reasonably loud(sustain), become normal(release). Then from full volume(100% sinus strength from the LogSin table) it will be cut off because the volume envelope has ended(and thus the tone itself in the case of the 'carrier' signal(operators 3,4,5 etc.)).

I think this isn't supposed to happen? So that(simply adding it to the LogSin table value) cannot be the correct way to use the volume input? If so, what's the correct way to use it? I currently have the tables from https://docs.google.com/document/d/18IGx18NQY … n1rIm42nwo/edit implemented in my emulator.

It tells me:
[quote]When such a table is used for calculation of the exponential, the table is read at the position given by the 8 LSB's of the input. The value + 1024 (the hidden bit) is then the significand of the floating point output and the yet unused MSB's of the input are the exponent of the floating point output. Indeed, YM3812 sends the audio to the YM3014B DAC in floating point, so it is quite possible that summing of voices is done in floating point also.[quote]

How do I use the sample from the LogSin table (16-bit signed word) and the volume (16-bit unsigned word) with this table? I currently use:

float expfactor = 1.0f;
float explookup = 1.0f;

OPTINLINE float OPL2_Exponential(float v)
{
if (v>=0.0f) //Positive?
return OPL2_ExpTable[(int)(v*explookup)]*expfactor; //Convert to exponent!
else //Negative?
return (-OPL2_ExpTable[(int)((-v)*explookup)])*expfactor; //Convert to negative exponent!
}

Where v is the value from the LogSin table with the volume added to it.

Expfactor simply converts it to output (to fit in 16-bit range). This is later multiplied when mixing the channels to get the value that's 1/9 of the 16-bit address space (9 channels: channels 0-5(Melodic only),6( Bass Drum/Melodic),7(Combined/Melodic),8(Combined/Melodic)) to make it fully fill that space.

	explookup = (1.0f/(OPL2_LogSinTable[0]+outputtableraw[0]+Silence))*256.0f; //Exp lookup factor for LogSin values!
expfactor = (1.0f/OPL2_ExpTable[255]); //The highest volume conversion to apply with our exponential table!
adlib_scaleFactor = (float)((1.0f/expfactor)*((1.0f/OPL2_ExpTable[255])*3639.0f)); //Highest volume conversion to SHRT_MAX (9 channels)!

(Silence is essentially the range of the volume envelope: 64)

outputtableraw is the lookup table for the (i<<5) value of the volume control(6-bits, index 0-3F).
adlib_scaleFactor is the multiplier the output of OPL2_Exponential is multiplied with to get the output to add with the other 9 channels to get the single channel 16-bit value, that's a mix of all 9 OPL2 channels.

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

Reply 41 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've managed to get it running somewhat, but still can't properly figure out how to apply the volume envelope. Also made more of the emulation use integer values instead of floating point (only when calculating the signal position and when applying relative conversions and phase calculations(and volume control until the correct method of applying it is found) this is applied (as it needs multiples of PI to work properly atm)).

https://bitbucket.org/superfury/x86emu/src/b5 … lib.c?at=master

Recording of Ultima VI music playing using this code:
https://www.dropbox.com/s/0hym1kkh2g68u8l/adl … 9_0259.zip?dl=0

Not 100% yet, but close (according to Youtube recordings).

Anyone knows how to fully apply the volume? At what point is it added? After the Exponential table? Before the Exponential table(to the value read from the LogSin table)? To the raw index into the LogSin table(doubt it, because it only would deform the signal(not apply volume), thus giving incorrect sound depending on volume)?

Edit: Managed to get the DRO player fully working (one dune1.dro file seems corrupt, according to it's data when viewed in a hex editor).
https://www.dropbox.com/s/8bf7id7a9e5fr1i/adl … 9_1753.zip?dl=0
This is a recording of x86EMU playing various songs.
0:00 Ultima VI opening song.
1:01 Main menu theme of Ultima VI
1:29 Introduction of Ultima VI
3:00 Playing the 6 drums of rhythm.dro
3:21 Song of the first level of Megarace
5:44 Song of Zeliard (don't know the song)
6:10 Nautical.dro (https://www.youtube.com/watch?v=GmcqKUefY8w)

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

Reply 42 of 112, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:

Anyone knows how to fully apply the volume? At what point is it added? After the Exponential table? Before the Exponential table(to the value read from the LogSin table)? To the raw index into the LogSin table(doubt it, because it only would deform the signal(not apply volume), thus giving incorrect sound depending on volume)?

I think you already know this:
in linear domain, you have sine wave A with some amplitude and envelope B with amplitude of say 0 to 1, and you get result C by multiplying them.
So output C = A*B.

In logarithmic domain, log(C) = log(A*B) = log(A)+log(B), so yes, the envelope is summed to sine wave.

And if you use a nearby calculator, you'll see that max envelope, which is 1 in linear domain, is log(1) in logarithmic domain, which is 0.
Half envelope, which is 0.5 in linear domain, is log(0.5) in logarithmic domain, and that is about minus 0.3.
Zero envelope, 0 in linear domain, is log(0) or minus infinity.

If you consider the sine wave also going from 0 to 1, then also the sine wave goes from minus inifity to zero.

Reply 43 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

Let's see if I understand this correctly:
Sinus ROM is 11-bit positive signed value.
Negative sinus is negated of positive sinus(~x). This results in 11-bit unsigned value.
To this, volume is added(11-bit unsigned).
This is looked up in exponential table(10-bit result based on low 8 bits).
The rest is passed through on (the high 3 bits(sign=bit 10, and bits 8-9 are passed into the result?)) and added to the result of the exponential table to provide sign(bit 10) and something else(bits 8-9? Where does this go? Straight to the result? This adds 256, 512 or 768 to the output?)?
When rendered, convert to signed word by sign extending to 16-bit:
0000000000=0
1000000000=-Max(Max is biggest 9-bit value, 511)
1000000001=(-Max+)1
1111111111=-1
0000000000=0
0111111111=Max

Then I just convert from -512 to +511 to 1/9th of 32767(Signed short) with fractional multiplication after adding all 9 channels(6 melodic, 3 drum channels) to get the mixed channels to render?

Edit: reading some more reveals the formula:
(exp[ logsin[x] % 256 ] + 1024) * 2^(logsin[x] / 256)

So I just need to add 1024 to the result of the exponential table, then multiply with pow(2,logsin[x]>>8)? Although together with logsin[x], the volume is also added?

So:
i1 = logsin[x]+(vol<<5)+(volenv<<3)
Out=(exp[i1&0xFF]+0x400)*pow(2,i1>>8)

The values i1, logsin and exp are 12-bit values unsigned.
The values vol and volenv are 6 bits unsigned. Thus combined 11 bits to make the sign unaffected?
The Out value is in floating point.
logsin[x] is negated(~) with negative phase(180-360 degrees) and x is reversed(or (~x)+1 to make it faster?) with 90-180 and 270-360 degrees.

Is this correct?

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

Reply 44 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've managed to implement the above in my emulator:
https://bitbucket.org/superfury/x86emu/src/80 … lib.c?at=master

I've currently made it convert the full range (after the exponential table lookup) back to range 0-1(by dividing with exponential table lookup of LogSin entry #0(maximum input value)), then multiplying with 2 times PI(normal modulator->carrier modulation) or multiplying with the factor from the feedback (PI/16, PI/8, PI/4, PI/2, PI, 2*PI, 4*PI) with feedback.

I do seem to get some output, but it seems way too loud (the result from the Exponential lookup is multiplied at the end by the factor of (1/(MaximumExponentialResult))*(SHRT_MAX/9) ). This divides the full offset space (Signed short) into 9 parts for each of the Exponential table lookup values to use. MaximumExponentialResult is gotten by calculating the Exponential table lookup of the LogSin entry #0(the biggest value the LogSin wave has).

Is this correct? Why am I getting lots of noise with my samples (I notice periodic spikes(at maximum level(+32767)) in the signal when playing my dro recording of the Ultima VI opening). Also for some reason the Megarace first level music (Newsan) gives no output at all(though recorded through Dosbox since my emulator doesn't support 32-bits instructions and some other required support(32-bit multitasking and interrupts) yet). Did it using a downloaded version of the game, although at home I (and family) did use to own a copy of Megarace(still have it's manual somewhere), which has gone lost somewhere:( .

Although now the volume control is implemented the documented way (adding it to the sine wave, then applying full range using the low 8 bits with the higher bits being used as a power of 2)>

OPTINLINE float getHiddenBit(word v)
{
return (v&0x400)?-1.0f:1.0f;
}

OPTINLINE float OPL2_Exponential(word v)
{
//Exponential lookup also reverses the input, since it's a -logSin table!
return getHiddenBit(v)*(OPL2_ExpTable[v&0xFF])*pow(1,((v>>8)&3)); //Convert to exponent!
}

Is this correct? It doesn't seem to work.

Edit: maybe it's more correct to do this:

return getHiddenBit(v)*(OPL2_ExpTable[v&0xFF])*pow(2,((v>>8)&3)); //Convert to exponent!

Is this correct?

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

Reply 45 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

Managed to improve my OPL2 emulation:

A little recording playing the first level of Megarace (at 0.5 speed to compensate for the 2.0GHz vs 4.0GHz CPU):
https://www.dropbox.com/s/zehvg6cyznfg37t/meg … 1_1657.wav?dl=0

It sounds fine, looking at the music, but it seems that besides adding noise(vibration?) to the signal, the feedback also adds noise to the audio?

Jepael? Can you see what's going wrong here?

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

Reply 46 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

Managed to fix the volume by making it substract (lowering volume) instead of add(adding volume on top of the wave). This seems to fix most problems (other problems being the low-pass filter messing up test .wav file output, Silence being 64 instead of 63, Wave(11-bit signed) vs signed word conversions, LogSin table lookup problems (this needs to be negated, although ~ doesn't work(messes up output, giving noise instead), so I used entry #0 and substract the current sample from it to archieve the next best thing, giving correct sinus output afaik). Volume is now substracted(positive output) or added(negative output) without overflow (substracting with overflow past 0 will become 0).

Currently normal modulator output (converted from full range to -1 - 1) is multiplied by 2*PI and feedback is multiplied by 1 times the specified feedback (from PI/16 to PI*4, multiplying each step by 2). Also left the wave dumping routine in there (WAVE_ADLIB define) to dump one sine wave at OPL2 frequency (~49kHz) to verify correct functioning.

https://bitbucket.org/superfury/x86emu/src/95 … lib.c?at=master

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

Reply 47 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

Just added vibrato and tremolo effects (minus effects: they substract from full volume(tremolo) and decrease signal speed(vibrato) when going from 0 to 1. When going back to 0 it returns to normal speed/volume. Although according to some documentation the vibrato increases speed to 114% and 107% and back to 100% speed. Is this correct?

https://bitbucket.org/superfury/x86emu/src/9f … lib.c?at=master

I do notice the sound seems to be too low.
Maybe an error in modulation amount? I'm using 2*PI(constant named PI2 in my emulation) for normal modulation, custom amount (PI/4) for drum phase(based on opl3emu essentially using a lookup table of 4 quarter sinuses to look those values(drums) up) and normal (PI/16 to PI*4(and PI/8, 4, 2, 1, PI itself and 2PI/PI2)) for the 7 feedback ratios?

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

Reply 48 of 112, by stohrendorf

User metadata
Rank Newbie
Rank
Newbie

Hi,

instead of trying to fiddle around with your code until it works, I'd suggest you simply copy my code and convert it to plain C 😉

The thing is that my current code base is actually pretty bit-accurate (just read the comments here and here, or listen to Nautical, which is a direct dump from the emulation code).

If you want to emulate the older OPL2, not the OPL3, you can still use my code, but need to do at least the following adjustments:

  • Feedback is delayed by one sample.
  • The rhythm mode uses different noise calculations (although the formulas are already discovered and used in several places).
  • The global noise calculation is also different.

Nevertheless, I highly recommend not putting so much effort into re-inventing the wheel 😉

Cheers.

Reply 49 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

One thing I'm currently worrying about is the FM modulation and feedback factor.

Currently I'm applying the following factors:
- With FM, the output of the previous stage (result of the Exponential table conversion to float) is divided to become in the range -1 to 1 using multiplicatio(see below)n, then multiplied with PI to get the correct phase to add(phase 0=No modulation, Phase 2PI=Add 1 sine wave to the frequency*time, Phase -2PI=Substract 1 sine wave to the frequency*time).
- With feedback, the output is converted to range -1 to 1(the same as with FM), then multiplied with the different divides of PI(0, PI/16, PI/8, PI/4, PI/2, PI, 2*PI, 4*PI) selected by the feedback strength. This value is used as the phase to add.

The base signal (with sin() function taken from the OPL2 lookup table) uses 2*PI*frequency*time. The phase is simply added to this(So you'll get ((2*PI*frequency*time)+phase) as the input to the table. The table is then looked up using a quarter of that input(0.5*PI), which is reversed or flipped depending on the phase((x>=PI) for negative and ((x modulo PI)>=0.5) for lowering. The value passed to the sin() function is the same as with a normal sin() function, except it retrieves it's data by simple conversion (rate conversion) from 0.5PI to 256.

The range of the input signal when converting to -1 to +1 range is simply the maximum positive value of the Exponential table lookup (Which is it's result given by inputting the first LogSin table entry into it (which is the maximum sample at maximum volume)). This is simply converted into a factor to convert from that maximum positive value to 1 to obtain the linear factor for using in modulation and output.

The thing I'm wondering about:
1. Does the FM modulation actually use PI as it's factor (looking at the ranges possible in feedback)? I'm assuming it actually uses PI to multiply into it's final result for phase modulation?
2. Is the range when selecting PI(value 5) as feedback a feedback value of -PI to +PI?

Stuff like nautical plays fine, except for some points, at which a very loud high tone gives strange sharp output (high volume)? With the video at https://www.youtube.com/watch?v=GmcqKUefY8w at 1:10 that tune starts(sounds like some piano) gives hard distorted sound. I have to reduce the volume of Windows to about 10% not to be blown away by it's volume and screech.

If I look at that output by IMFplay it uses x12 multiplier with vibrato on the modulator and x4 with KSR (ADSR FF07 and FF17)?

It looks like it's making use of the KSR to affect the instant attack to sound right. It sounds like it's pumping out square waves instead in my emulator.

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

Reply 50 of 112, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:

The thing I'm wondering about:
1. Does the FM modulation actually use PI as it's factor (looking at the ranges possible in feedback)? I'm assuming it actually uses PI to multiply into it's final result for phase modulation?
2. Is the range when selecting PI(value 5) as feedback a feedback value of -PI to +PI?

1. No, there is no PI anywhere. The range of modulator is about 4096 which is about four times the 1024 which is one full wave, 2*pi.
2. I recall modulation indexes usually say the max deviation without sign, so yes, modulation index of pi means from -pi to +pi. But better check Sto's code, it should be correct.

Reply 51 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

Fixed the generation of simple sinus waves and basic Exponential translation:

//Sign bit, disable mask and extension to 16-bit value!
#define SIGNBIT 0x8000
#define SIGNMASK 0x7FFF

OPTINLINE sword Wave2Word(word w)
{
if (w&SIGNBIT) //Negative?
{
w &= SIGNMASK; //Only past the sign!
return -1 * (sword)w; //Give the converted value!
}
else //Positive?
{
w &= SIGNMASK; //Only past the sign!
return (sword)w; //Give the converted value!
}
}

OPTINLINE word OPL2SinWave(const float r)
{
const float halfpi = (0.5f*(float)PI); //Half PI!
const float halfpi1 = (1.0f/halfpi); //Half pi division factor!
float index;
word entry; //The entry to convert!
byte location; //The location in the table to use!
byte PIpart;
PIpart = 0; //Default: part 0!
index = fmod(r,PI2); //Loop the sinus infinitely!
if (index>=(float)PI) //Second half?
{
PIpart = 2; //Second half!
index -= PI; //Convert to first half!
}
if (index>=halfpi) //Past quarter?
{
PIpart |= 1; //Second half!
index -= halfpi; //Convert to first quarter!
}
index = (index*halfpi1)*256.0f; //Convert to full range!
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!
entry = ~entry; //Reverse us!
entry &= SIGNMASK; //Only the signed part is used!
if (PIpart&2) //Second half is negative?
{
entry |= SIGNBIT; //We're negative instead!
}
return entry; //Give the processed entry!
}

float expfactor = 1.0f;

OPTINLINE float getHiddenBit(word v)
{
return (v&SIGNBIT)?-1.0f:1.0f; //Significant!
Show last 41 lines
}

OPTINLINE word removeHiddenBit(word v)
{
return (v&SIGNMASK); //No significant!
}

OPTINLINE float OPL2_Exponential(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)
return getHiddenBit(v)*((float)OPL2_ExpTable[v&0xFF]+0x400)*powf(2,(removeHiddenBit(v)>>8)); //Lookup normally!
}

OPTINLINE word OPL2_Sin(byte signal, const float frequencytime) {
double x;
float t;
word result;
switch (signal) {
case 0: //SINE?
return OPL2SinWave(frequencytime); //The sinus function!
default:
t = (float)modf(frequencytime/PI2, &x); //Calculate rest for special signal information!
switch (signal) { //What special signal?
case 1: // Negative=0?
if (t >= 0.5f) return 0; //Negative!
result = OPL2SinWave(frequencytime); //The sinus function!
return result; //Positive!
case 3: // Absolute with second half=0?
if (fmod(t, 0.5f) >= 0.25) return 0; //Are we the second half of the half period? Clear the signal if so!
case 2: // Absolute?
result = OPL2SinWave(frequencytime); //The sinus function!
result &= SIGNMASK; //Make positive!
return result; //Simply absolute!
default: //Unknown signal?
return 0;
}
}
}

The test sinus wave generated dumps without problems, but when I try to start a simple sound (with/without modulation, a simple DRO recording from Dosbox playing the intro theme of Ultima VI makes the very first call of OPL2_Exponential for feedback purposes return infinity? Anyone knows what's going wrong here? The sample inputted into the OPL2_Exponential has value 0x77A6. 0xA6 translates to 0x245 according to the Exponential table. So this results in 1*(0x245+0x400)*powf(2,0x77)=Infinity.

Edit: Converting the various variables to double percision seems to remove the Infinity value being reported. The problem that's still left is converting from the result of OPL2_Exponential's double percision to a simple factor between -1 and +1. Anyone knows accurate values to give OPL2_Exponential to use as the maximum value ever returned by the function in OPL2? I tried the LogSin value of 0.5*PI(The top of the sinus range) with 63 levels of volume and volume envelope added to it. But this already gives invalid results in the first sample of the song: It's in the range of 1x10^46, as the division only goes to 1x10^9 or something close.

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

Reply 53 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

Although the overflow itself's fixed, I can't seem to get the Exponential conversion working fully.

https://bitbucket.org/superfury/x86emu/src/44 … lib.c?at=master

Changed the general modulation factor(which converts from OPL2 Exponential 10.3 float to a float from -1 to +1 for converting to PI or output volume.

Since adding gain directly to the LogSin sample makes it overflow it's 12-bits signed value, I've passed the LogSin value first for Exponential lookup. After that it simply multiplies with Exponential volume looked up seperately, which is converted to factor 0-1 using the general modulation factor, then multiplied into the Exponential result of the LogSin to affect it's volume. The result of this(double percision floating point after exponential table lookups) is finally used as feedback value, output value for modulation or carrier output, depending on the path.

Is this correct? Afaik the volume can't directly be added to the LogSin, since it will overflow the 11-bits value(affecting sign bit 11 with maximum attenuation(or is it the reverse? Volume level<<5+Volume envelope<<3 will overflow into bit 11 when added to the sine wave.

Jepael? Stohrendorf? Any thoughts on this? Btw according to the source I've gotten it from the Volume envelope is actually a different floating point? So this has different structure from the normal Exponential table(4.5(Volume envelope) and 3.3(Total level) floating point), thus even those shifts may be incorrect?

Jani wrote:
Hi Olli. I have also verified on real YMF262 OPL3 chip that the tables and formulas in your reverse engineering report are accur […]
Show full quote

Hi Olli. I have also verified on real YMF262 OPL3 chip that the tables and formulas in your reverse engineering report are accurate. I might add that the gain (or actually attennuation) is just a sum of Total Level (TL) register and ADSR Envelope Generator output. And since the gain also goes through the 256-entry table, gain has 8 fractional bits, so 0x100 means 6dB attennuation and 0x080 means 3dB attennuation etc. Total Level register has 6 bits and 48dB range, so 3 bits are integer part and 3 bits fractional. From OPL4 FM part datasheet it is shown that ADSR envelope has 96dB range and 0.1875 dB resolution (yes it is a typo in the datasheet), it means the envelope is a 9-bit value, 4 integer bits and 5 fractional bits. Sustain Level register bits also match the same gain formula when shifted into right position.

Thus, gain = (TotLev<<5) + (Envelope<<3);

Envelope is a linear ramp for decay and release, but attack is exponential curve and I have not been able to figure out the exact formula for it yet. The values may come from the logsin or exp table that are indexed linearly. Current emulators just approximate it, it would be nice to get it right.

Comment by Jani — 2010-05-14 @ 15:39

So the biggest value through summing is (63*32)+(63*8)+2137=4657.
Since 4657 is 1001000110001 in binary, it's 13 bits in length. Since the bit 12 is the sign bit, doing this by addition would simply result into the peak of the volume envelope overwriting the sign(bit 12(0-based))? Unless the LogSin table result is a 14-bit value, including the sign bit?

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

Reply 54 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

I managed to get the normal (addition) method somewhat working. I'm currently trying to get the Drum correct:

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 = calcOperator(6, op6_0, adlibfreq(op6_0,6), 0.0f,0,op6_0,1); //Calculate the modulator for feedback!

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

return OPL2_Tremolo(op6_1,result*2.0f); //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 Modulator 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; //Initialize immediate result!
if (adlibop[op7_1].volenvstatus) //Snare drum on modulator?
{
//Derive frequency from channel 0.
tempphase = 0x100<<((getphase(7,0,adlibfreq(op7_0,7))>>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_0, op1frequency, 0,0,op7_0,(!adlibop[op8_1].volenvstatus && !adlibop[op7_0].volenvstatus)?1:0); //Calculate the modulator, but only use the current time(position in the sine wave)!
result = calcOperator(7, op7_1, adlibfreq(op7_1, 7),convertphase(tempphase), 0,op7_1,1); //Calculate the carrier with applied modulator!
immresult += OPL2_Tremolo(op7_1,result*2.0f); //Apply the tremolo!
}
if (adlibop[op7_0].volenvstatus) //Hi-hat on carrier?
{
//Derive frequency from channel 7(modulator) and 8(carrier).
tempop_phase = getphase(7,0,adlibfreq(op7_0,7)); //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 = getphase(8,1,adlibfreq(op8_1,8)); //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;
Show last 40 lines
					}
else if (OPL2_RNG) tempphase = (0xD0>>2);

result = calcOperator(8, op8_1, adlibfreq(op8_1, 8), 0,0,op8_1,(!adlibop[op8_1].volenvstatus)?1:0); //Calculate the modulator, but only use the current time(position in the sine wave)!
result = calcOperator(7, op7_0, adlibfreq(op7_0, 7), convertphase(tempphase), 0,op7_0,((!adlibop[op8_1].volenvstatus)?1:0)|2); //Calculate the carrier with applied modulator!
immresult += OPL2_Tremolo(op7_0,result*2.0f); //Apply the tremolo!
}
result = immresult; //Load the resulting channel!
result *= 0.5; //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; //Initialize immediate result!
if (adlibop[op8_1].volenvstatus) //Cymbal(Modulator)?
{
//Derive frequency from channel 7(modulator) and 8(carrier).
tempop_phase = getphase(7,0,adlibfreq(op7_0,7)); //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 = getphase(8,1,adlibfreq(op8_1,8)); //Calculate the phase of channel 8 carrier signal!
if (((tempop_phase>>3)^(tempop_phase>>5))&1) tempphase = 0x300;

result = calcOperator(8, op8_1, adlibfreq(op8_1, 8), 0,0,op8_1,1); //Calculate the modulator, but only use the current time(position in the sine wave)!
result = calcOperator(7, op7_0, adlibfreq(op7_0, 7), convertphase(tempphase), 0,op7_0,1); //Calculate the carrier with applied modulator! Use volume!
immresult += OPL2_Tremolo(op8_1,result*2.0f); //Apply the exponential!
}
if (adlibop[op8_0].volenvstatus) //Tom-tom(Carrier)?
{
result = calcOperator(8, op8_0, adlibfreq(op8_0, 8), 0, 0,op8_0,3); //Calculate the carrier without applied modulator additive! Ignore volume!
immresult += OPL2_Tremolo(op8_0,result*2.0f); //Apply the exponential!
}
result = immresult; //Load the resulting channel!
result *= 0.5; //We only have half(two channels combined)!
return result; //Give the result, converted to short!
break;
}

Phase conversion from operator and to OPL2 sample for modulation(which is passed through the Exponential table in convertphase. The results of the convertphase_real results are stored in the phaseconversion lookup table for faster calculations):

OPTINLINE word getphase(byte channel, byte operator, float frequency) //Get the current phrase of the operator!
{
return (word)(((fmod(adlibop[operator].time*frequency*PI2,PI2)/PI2)*((float)0x3D0)))>>6; //Give the 10-bits value
}

word convertphase_real(word phase)
{
return ((phase&0x200)?SIGNBIT:0)|(word)((phase&0x1FF)*((1.0f/(float)0x1FF)*SIGNMASK)); //Give the phase to execute, as a full sinus modulation!
}

float convertphase(word phase)
{
return OPL2_Exponential(phaseconversion[phase]); //Lookup the phase translated!
}

For some reason, the Bass drum barely gives sound. Also the other instruments don't quite sound like they're supposed to. Maybe a problem in the way they're called?

The function that produces the output is:

OPTINLINE float calcOperator(byte channel, byte operator, float frequency, float modulator, byte feedback, byte volenvoperator, byte updateoperator)

- Channel is the channel number to use.
- Operator is the operator to use.
- Frequency is the frequency to produce.
- Modulator is the output from the Exponential lookup(range is the 10.3 floating point range for a full sinus(-2PI to +2PI)).
- Feedback is 1 for using feedback instead of modulator(passes part of itself as modulator back to itself the next sample. Starts at 0.0f for the first sample played).
- Volenvoperator is the volume operator to use for producing output.
- Updateoperator is a bitflag: Bit0=1 for updating the feedback, frequency and time, Bit1=1 makes it force the Output level to 0(maximum volume).

Is the way this is called correct?

Last edited by superfury on 2016-05-13, 15:28. Edited 1 time in total.

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

Reply 56 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

As you can see in the code, the result after the Exponential table is looked up is already doubled (times 2.0f). But I'm still barely getting output when playing the drum example DRO(Re: DOSbox' OPL Emulation - Rhythm Sounds . It's the Rhythm DRO file I'm testing with). Also the output doesn't sound right? Is the calcOperator function being called correctly?

Btw, the calcOperator function itself:

//Calculate an operator signal!
OPTINLINE float calcOperator(byte channel, byte operator, float frequency, float modulator, byte feedback, byte volenvoperator, byte updateoperator)
{
if (operator==0xFF) return 0; //Invalid operator!
INLINEREGISTER word sample; //Our variables?
word result; //The result to give!
float result2; //The translated result!
float activemodulation;
//Generate the signal!
if (feedback) //Apply channel feedback?
{
activemodulation = adlibop[operator].lastsignal;
activemodulation *= adlibch[channel].feedback; //Calculate current feedback
}
else
{
activemodulation = 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))!
result = calcOPL2Signal(adlibop[operator].wavesel&wavemask, (frequency?frequency:adlibop[operator].lastfreq),activemodulation, &adlibop[operator].freq0, &adlibop[operator].time); //Take the last frequency or current frequency!

//Apply the gain!
if (updateoperator&2) //Special: ignore main volume control!
{
result += outputtable[0]; //Always maximum volume, ignore the volume control!
}
else //Normal output level!
{
result += adlibop[operator].outputlevel; //Current gain!
}
result += adlibop[operator].gain; //Apply volume envelope and related calculations!
result += adlibop[operator].m_kslAdd; //Add KSL!
result2 = OPL2_Exponential(result); //Translate to Exponential range!

skipvolenv: //Skip vol env operator!
if (frequency && (updateoperator&1)) //Running operator and allowed to update our signal?
{
adlibop[operator].lastsignal = OPL2_Tremolo(operator,result2); //Set last signal #0 to #1(shift into the older one)!
adlibop[operator].lastfreq = frequency; //We were last running at this frequency!
incop(operator,frequency); //Increase time for the operator when allowed to increase (frequency=0 during PCM output)!
}
return result2; //Give the translated result!
}

Both outputlevel(data from outputtable indexed by the Output level register) and gain are already shifted left by 5 and 3 bits respectively. Both have a range of 0x00-0x3F.

Is the way this is handled correct? Is the range of the modulations by non-Bass drum drums from 0 to 0x3D0? What about the signal that drives the calculations? I've just assumed it's the conversion of the current frequency*time converted from it's 0-PI2 range to 0x3D0 range, then shifted right by 6 for the modulation to use in those calculations? So you'll get values 0-F, depending on the current phase?

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

Reply 57 of 112, by stohrendorf

User metadata
Rank Newbie
Rank
Newbie

Ah, I remember that rhythm DRO, and forgot to investigate this further... my current implementation also has the problem that the bass drum is barely audible, yet the other percussion sounds fine. The problem is that once I made this DRO's BD sound good, the other chiptunes I have lying around started to sound pretty bad, so I decided for the majority of good-sounding bass drums.

Reply 58 of 112, by superfury

User metadata
Rank l33t++
Rank
l33t++

Do you have a good Rhythm DRO so I can properly test the rhythm sounds instead of that one? Also I seem to notice that software using the OPL2 'PCM' technique give no sound anymore? Something is going wrong here. Also it seems that there isn't any modulation anymore?

This is my current version of my emulation:

	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 = calcOperator(6, op6_1,op6_1,op6_1, adlibfreq(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, adlibfreq(op6_2), 0.0f, 0x00); //Calculate the carrier without applied modulator additive!
}
else //FM synthesis?
{
result = calcOperator(6, op6_2,op6_2,op6_2,adlibfreq(op6_2), result, 0x00); //Calculate the carrier with applied modulator!
}

return result*2.0f; //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 Modulator 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 = getphase(op7_1,adlibfreq(op7_1)); //Save the phase!
tempphase = (tempop_phase>>2);
tempphase ^= (tempop_phase>>7);
tempphase |= (tempop_phase>>3);
tempphase &= 1; //Only 1 bit is used!
Show last 58 lines
					tempphase = tempphase?(0x200|(0xD0>>2)):0xD0;
tempop_phase = getphase(op8_2,adlibfreq(op8_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,op8_2,adlibfreq(op8_2), 0.0f,2|((adlibop[op8_2].volenvstatus)?1:0)); //Calculate the modulator, but only use the current time(position in the sine wave)!
result = calcOperator(7, op7_1,op7_1,op7_1,adlibfreq(op7_1), convertphase(tempphase), 2|((adlibop[op8_2].volenvstatus||adlibop[op7_2].volenvstatus)?0x01:0x00)); //Calculate the carrier with applied modulator!
immresult += result*2.0f; //Apply the tremolo!
}
if (adlibop[op7_2].volenvstatus) //Snare drum on Carrier volume?
{
//Derive frequency from channel 0.
tempphase = 0x100 << ((getphase(op7_1, adlibfreq(op7_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,adlibfreq(op7_2), convertphase(tempphase), 2); //Calculate the carrier with applied modulator!
result = calcOperator(7, op7_1,op7_1,op7_2,adlibfreq(op7_1), convertphase(tempphase), ((adlibop[op8_2].volenvstatus) ? 0x01 : 0x00) | 0x02); //Calculate the carrier with applied modulator!
immresult += result*2.0f; //Apply the tremolo!
}
result = immresult; //Load the resulting channel!
result *= 0.5; //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_2].volenvstatus) //Cymbal(Carrier)?
{
//Derive frequency from channel 7(modulator) and 8(carrier).
tempop_phase = getphase(op7_1,adlibfreq(op7_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 = getphase(op8_1,adlibfreq(op8_1)); //Calculate the phase of channel 8 carrier signal!
if (((tempop_phase>>3)^(tempop_phase>>5))&1) tempphase = 0x300;

result = calcOperator(7, op7_1,op7_1,op7_1, adlibfreq(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, adlibfreq(op8_2), convertphase(tempphase), 0x02); //Calculate the carrier with applied modulator! Use volume!
immresult += result*2.0f; //Apply the exponential!
}
if (adlibop[op8_1].volenvstatus) //Tom-tom(Modulator)?
{
result = calcOperator(8, op8_1,op8_1,op8_1, adlibfreq(op8_1), 0.0f, 0x02); //Calculate the carrier without applied modulator additive! Ignore volume!
immresult += result*2.0f; //Apply the exponential!
}
result = immresult; //Load the resulting channel!
result *= 0.5; //We only have half(two channels combined)!
return result; //Give the result, converted to short!
break;
}
#endif
//Not a percussion channel? Pass through!
}

Nautical now sounds better (although the piano(I think it's supposed to be a piano) at 1:10 still doesn't sound 100% as a piano) without the extreme square-wave notes at that point.

I've also updated the operator function to be more able to apply the different parameters needed with the rhythm mode:

OPTINLINE float calcModulator(float modulator)
{
return modulator*PI2; //Calculate current modulation!
}

OPTINLINE float calcFeedback(byte channel, float modulator)
{
return (modulator*adlibch[channel].feedback); //Calculate current feedback
}

//Calculate an operator signal!
OPTINLINE float calcOperator(byte channel, byte operator, byte timingoperator, byte volenvoperator, float frequency, float modulator, byte flags)
{
if (operator==0xFF) return 0; //Invalid operator!
INLINEREGISTER word sample; //Our variables?
word result; //The result to give!
float result2; //The translated result!
float activemodulation;
//Generate the signal!
if (flags&0x80) //Apply channel feedback?
{
activemodulation = calcFeedback(channel,adlibop[timingoperator].lastsignal); //Apply this feedback signal!
}
else //Apply normal modulation?
{
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))!
result = calcOPL2Signal(adlibop[operator].wavesel&wavemask, (frequency?frequency:adlibop[timingoperator].lastfreq),activemodulation, &adlibop[timingoperator].freq0, &adlibop[timingoperator].time); //Take the last frequency or current frequency!

//Apply the gain!
if (flags&2) //Special: ignore main volume control!
{
result += outputtable[0]; //Always maximum volume, ignore the volume control!
}
else //Normal output level!
{
result += adlibop[volenvoperator].outputlevel; //Current gain!
}
result += adlibop[volenvoperator].gain; //Apply volume envelope and related calculations!
result += adlibop[volenvoperator].m_kslAdd; //Add KSL!
result2 = OPL2_Exponential(result); //Translate to Exponential range!

skipvolenv: //Skip vol env operator!
if (frequency && ((flags&1)==0)) //Running operator and allowed to update our signal?
{
adlibop[timingoperator].lastsignal = result2; //Set last signal #0 to #1(shift into the older one)!
adlibop[timingoperator].lastfreq = frequency; //We were last running at this frequency!
incop(timingoperator,frequency); //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!
}

The parameters now are:
- channel: The channel to sample.
- operator: The operator to sample.
- timingoperator: The operator to use for timing of the wave and storing feedback.
- volenvoperator: The operator to use as the volume envelope.
- frequency: The current frequency to generate.
- modulator: The modulating output signal from the Exponential lookup.
- flags: Special actions to be applies: Bit0=Don't update timing when set, Bit1=Ignore main volume control(Forces the Output level to 0, used with ), Bit7=Apply feedback(only used with the melodic modulators)

Also it seems to be generating some kind of noise somehow? Anyone can see what's going wrong(Testing with Dosbox recordings of Megarace, Nautical and Ultima VI(all except Nautical recorded myself)? https://bitbucket.org/superfury/x86emu/src/0c … lib.c?at=master

Enabling the WAVE_DUMP constant and looking at the dump, it looks like a fine sine-wave. Maybe an error in the modulation, conversion or frequency? Stohrendorf, Jepael?

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

Reply 59 of 112, by stohrendorf

User metadata
Rank Newbie
Rank
Newbie

I'm not sure what happens here, but if I manually set the BLK value everywhere to 6, I get the expected output for the bass drum (it even looks nearly similar when comparing my emulator output with the recording from the real chip), yet the other percussion operators are then operating on a much too high frequency. But I don't see any register write to the BLK registers:

WRITE 0x1=0x20   LSI Test
WRITE 0x8=0x40 NTS = 1
# AM1_VIB1_EGT1_KSR1_MULT4
WRITE 0x20=0x1 AM1_VIB1_EGT1_KSR1_MULT4[0] = 0x01
WRITE 0x21=0x1 AM1_VIB1_EGT1_KSR1_MULT4[1] = 0x01
WRITE 0x22=0x1 AM1_VIB1_EGT1_KSR1_MULT4[2] = 0x01
WRITE 0x23=0x11 AM1_VIB1_EGT1_KSR1_MULT4[3] = 0x11
WRITE 0x24=0x11 AM1_VIB1_EGT1_KSR1_MULT4[4] = 0x11
WRITE 0x25=0x11 AM1_VIB1_EGT1_KSR1_MULT4[5] = 0x11
WRITE 0x28=0x1 AM1_VIB1_EGT1_KSR1_MULT4[8] = 0x01
WRITE 0x29=0x1 AM1_VIB1_EGT1_KSR1_MULT4[9] = 0x01
WRITE 0x2a=0x1 AM1_VIB1_EGT1_KSR1_MULT4[10] = 0x01
WRITE 0x2b=0x11 AM1_VIB1_EGT1_KSR1_MULT4[11] = 0x01
WRITE 0x2c=0x11 AM1_VIB1_EGT1_KSR1_MULT4[12] = 0x11
WRITE 0x2d=0x11 AM1_VIB1_EGT1_KSR1_MULT4[13] = 0x11
WRITE 0x31=0x1 AM1_VIB1_EGT1_KSR1_MULT4[14] = 0x01
WRITE 0x32=0x4 AM1_VIB1_EGT1_KSR1_MULT4[15] = 0x04
WRITE 0x34=0xc AM1_VIB1_EGT1_KSR1_MULT4[16] = 0x01
WRITE 0x35=0x1 AM1_VIB1_EGT1_KSR1_MULT4[17] = 0x01
# KSL2_TL6
WRITE 0x40=0x4f
WRITE 0x41=0x4f
WRITE 0x42=0x4f
WRITE 0x43=0x10
WRITE 0x44=0x10
WRITE 0x45=0x10
WRITE 0x48=0x4f
WRITE 0x49=0x4f
WRITE 0x4a=0x4f
WRITE 0x4b=0x10
WRITE 0x4c=0x10
WRITE 0x4d=0x10
WRITE 0x50=0xb ***** BD0
WRITE 0x51=0x10
WRITE 0x52=0x10
WRITE 0x53=0x10 ***** BD1
WRITE 0x54=0x10
WRITE 0x55=0x10
# AR4_DR4
WRITE 0x60=0xf1
WRITE 0x61=0xf1
WRITE 0x62=0xf1
WRITE 0x63=0xd2
WRITE 0x64=0xd2
WRITE 0x65=0xd2
WRITE 0x68=0xf1
WRITE 0x69=0xf1
WRITE 0x6a=0xf1
WRITE 0x6b=0xd2
WRITE 0x6c=0xd2
WRITE 0x6d=0xd2
WRITE 0x70=0xa8 ***** BD0
WRITE 0x71=0xf7
WRITE 0x72=0xf7
WRITE 0x73=0xd6 ***** BD1
WRITE 0x74=0xf8
WRITE 0x75=0xf5
# SL4_RR4
WRITE 0x80=0x53
WRITE 0x81=0x53
Show last 41 lines
WRITE 0x82=0x53  
WRITE 0x83=0x74
WRITE 0x84=0x74
WRITE 0x85=0x74
WRITE 0x88=0x53
WRITE 0x89=0x53
WRITE 0x8a=0x53
WRITE 0x8b=0x74
WRITE 0x8c=0x74
WRITE 0x8d=0x74
WRITE 0x90=0x4c ***** BD0
WRITE 0x91=0xb5
WRITE 0x92=0xb5
WRITE 0x93=0x4f ***** BD1
WRITE 0x94=0xb5
WRITE 0x95=0xb5
# FNUML8
WRITE 0xa0=0x57
WRITE 0xa1=0x57
WRITE 0xa2=0x57
WRITE 0xa3=0x57
WRITE 0xa4=0x57
WRITE 0xa5=0x57
WRITE 0xa6=0x57 *** BD
WRITE 0xa7=0x3
WRITE 0xa8=0x57

WRITE 0xbd=0x20 DAM1_DVB1_RYT1_BD1_SD1_TOM1_TC1_HH1

# CH4_FB3_CNT1
WRITE 0xc0=0x6
WRITE 0xc1=0x6
WRITE 0xc2=0x6
WRITE 0xc3=0x6
WRITE 0xc4=0x6
WRITE 0xc5=0x6
WRITE 0xc7=0x1
WRITE 0xc8=0x1

WRITE 0xbd=0x30 DAM1_DVB1_RYT1_BD1_SD1_TOM1_TC1_HH1
WRITE 0xbd=0x20 DAM1_DVB1_RYT1_BD1_SD1_TOM1_TC1_HH1