OPL3 C++ research implementation

An investigation into the behaviour of the Yamaha OPL3 chipset.
With a view to a more accurate emulator

Re: OPL3 C++ research implementation

Postby opl3 » Fri Nov 30, 2012 10:07 am

Even with the simple case of ratehi=13 (the attack rate in register) and ratelo=0 (no rate scaling, so actual rate is 13*4=52), so the attack level changes one step per one sample, I cannot get your formula to match the actual levels used by the chip.

Also the period slows down by doubling the amount of samples when AR is decreased by 1, so when AR=1 one level of attack envelope stays for 4096 samples. No intermediate values are used.

The only formula that will match the actual attack envelope levels is already on this forum, but I have only verified it when the envelope starts from silence, I have not verified when attack starts during release phase and envelope is not silence.

I repeat this from my memory: silence is integer 511, formula for next step at AR=13 is env=env-trunc(env/8)-1 which most likely matches env=env+(~(env>>3)), as the subtraction is done by summing with inverted bits so the value is larger.

Regarding your comment how the subtraction or sum can be reduced when taking a look at the (env>>(15-ratehi)) part, I think the chip does not sum or subtract 15 and ratehi to know how much to shift like computers do, most likely it just uses ratehi as offset where to start taking bits of env from, using muxes or perhaps delay lines when to start load in values. It might use "serial adders", who knows, but a lot of stuff inside the chip just seems to be shifting data left or right by some amount of bits, as sums and subtractions are also expensive.
opl3
 
Posts: 55
Joined: Sun Sep 26, 2010 8:11 pm

Re: OPL3 C++ research implementation

Postby sto » Fri Nov 30, 2012 11:54 am

Well, then I didn't read the post carefully enough... but what I am thinking of now that I read the thread is that when the generator is in the attack phase (thinking of a 9.15 bit fractional int), the 9 MSB are still used as the actual output, and the lower 15 bits could then be used as the local envelope clock (which could explain some of the asynchronous ideas). Maybe (I have to test it/think about it) the increment of the clock is the same as in the decay/release phases which would simplify that a bit, but that's only a quick idea. Although... when the fractional part is 15 bits long, something like 1<<AR or the like could be used for the increment, and the overflow would then be used to adjust the envelope level.

PS: I like brainstorming, and errors can be kept by the finder :)
sto
 
Posts: 60
Joined: Thu Nov 08, 2012 4:33 am

Re: OPL3 C++ research implementation

Postby sto » Fri Nov 30, 2012 6:36 pm

Could you maybe give me some test data/output? I'd like to experiment with it myself, as I don't own any hardware except my computer (which doesn't contain an OPL), and this would also allow me to write a unit test for the envelope generator. Thanks in advance.
sto
 
Posts: 60
Joined: Thu Nov 08, 2012 4:33 am

Re: OPL3 C++ research implementation

Postby sto » Mon Dec 03, 2012 6:45 pm

Sorry for triple posting, but I found something out again. I played around with the attack phase, and now got this piece of python code which gives me nearly exact results for rates up to 56, then it gets a little odd:
Code: Select all
    env = 511
    total2 = 0
    counter = 0
    while env > 0:
        print(env, end=", ")
        total2 += 1
        counter += (4|rateLo)<<rateHi
        if (counter>>15) != 0:
            overflow = (counter>>15)
            counter &= (1<<15)-1
            if overflow&4 != 0:
                shr = 1
            elif overflow&2 != 0:
                shr = 2
            elif overflow&1 != 0:
                shr = 3
            else:
                continue
            env -= ((env>>shr) + 1) & 511

The thing is that it perfectly fits into my 9.15 bit envelope value theory, though I'm not sure that this algorithm is really used as it seems too complicated to be simply built in hardware.
sto
 
Posts: 60
Joined: Thu Nov 08, 2012 4:33 am

Re: OPL3 C++ research implementation

Postby carbon14 » Wed Dec 05, 2012 9:45 am

I thought I had some test results handy and I was going to send them.

But I don't seem to have them here. I'll have to get them off another computer. I'll get them to you as soon as I can, but it might not be until the weekend.
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Re: OPL3 C++ research implementation

Postby sto » Sat Feb 16, 2013 1:10 pm

So, after some time I got another possible solution to the problem:
  • Being rateLo the lower 2 bits and rateHi the upper 4 bits of the effective rate, a speed value is calculated as (4|rateLo)<<rateHi and added every clock cycle to a 15-bit counter.
  • A temporary value delta is in the attack phase calculated as envelope>>3, otherwise it is simply 1.
  • When the counter overflows, the overflow is multiplied with delta and (depending on the phase) either added to or (by 1's complement) subtracted from envelope.
That's it. It involves another multiplier, but due to the fact that the counter overflow can only be 3 bits at the highest rate, I think it would be a simple multiplier.
sto
 
Posts: 60
Joined: Thu Nov 08, 2012 4:33 am

Re: OPL3 C++ research implementation

Postby carbon14 » Mon Mar 18, 2013 12:16 pm

I'm not sure I completely understand what you are saying.

Can you post some pseudo code?
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Re: OPL3 C++ research implementation

Postby sto » Tue Mar 19, 2013 4:51 pm

It's already implemented: http://sourceforge.net/p/peepeeplayer/c ... erator.cpp

See lines 97 to 122 for the counter and attenuation and lines 81 to 95 for the effective rate calculation (which I got from this forum). The advanceCounter() function, which is called on every output sample (not on every clock cycle as I wrote, sorry), returns the overflow I mentioned.

But here's also some rough pseudo-code:
Code: Select all
int counter, envelope; // persistent values
int effectiveRate = calcEffectiveRate(attackOrDecayOrReleaseRate);
int rateLo = effectiveRate & 3;
int rateHi = effectiveRate >> 2;
counter += (4|rateLo)<<rateHi;
int overflow = counter>>15;
if(phase==Attack) {
    if(overflow!=0) {
        int delta = envelope>>3;
        envelope -= ~(delta * overflow);
    }
}
else
    envelope += overflow;


PS: The overflow thing matches nearly perfectly this post in the decay and release phases, and some re-ordering of the attack phase code suggested the multiplication I'm using.
sto
 
Posts: 60
Joined: Thu Nov 08, 2012 4:33 am

Re: OPL3 C++ research implementation

Postby carbon14 » Wed Mar 20, 2013 2:54 pm

Thanks,

I'll look at that as soon as I can. It looks very straightforward.
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Re: OPL3 C++ research implementation

Postby sto » Wed Mar 20, 2013 7:44 pm

Indeed, but I'm not sure whether the three envelope bits are thrown away before or after the multiplication. There isn't a great audible difference, but I think that right-shifting after the multiplication sounds smoother.
sto
 
Posts: 60
Joined: Thu Nov 08, 2012 4:33 am

PreviousNext

Return to Yamaha OPL-3 research

Who is online

Users browsing this forum: No registered users and 1 guest

cron