Envelopes

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

Re: Envelopes

Postby carbon14 » Fri Jun 08, 2012 12:01 pm

I confirmed what opl3 said about the attack starting point not necessarily being 511. I allowed a waveform to rise completely, then during the decay / release phase I turned off the note and then turned it back on again. The result is that the attack starts from the point that the envelope had fallen to during the decay / release phase. The first attack started from a putative envelope level of 511, through 383, 287, 215, 161,120,89,66,49,36,26,19,14,10,7,5,3,2,1,0.
Then having fallen to 60 a new attack phase took it up through 44, 32,23,17,12,8,5,3,2,1,0.
A further fall to 96 was then followed by a third attack through 71,53,39,29,21,15,11,8,5,3,2,1,0.

The attack rate in the experiment was 14, so this is using the f(x) = x - (x/4) - 1 algorithm.
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Re: Envelopes

Postby carbon14 » Fri Jun 08, 2012 12:04 pm

I also confirmed that an attack rate of zero does not seem to produce any envelope at all, no matter the level of key rate scaling which is applied.
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Re: Envelopes

Postby carbon14 » Wed Jun 13, 2012 9:19 am

The envelope steps appear to be synchronised to the same clock that drives the tremolo. Working with envelope rates that give 8 and 16 sample long steps, the steps always seem to occur n * 8 + 1 samples after step changes in the tremolo. I need to do more work on this, I've got over 100 runs of data to collate to try to bring this together.

Note that I've not yet actually proven that the tremolo clock is independant of the individual channels. If each operator pair had it's own tremolo, then I'd be no further forward. That seems very unlikely and should be easy to disprove (or perhaps to disprove to a certain high degree of confidence less than 100%).
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Re: Envelopes

Postby carbon14 » Wed Jun 13, 2012 9:22 am

The attack rate algorithm will go as fast as

f(x) = x - (x/2 + 1)

where the key scale number is between 57 and 59, the algorithm is a mix of
f(x) = x - (x/4 + 1)
and
f(x) = x - (x/2 + 1)

once the key scale number reaches 60 however, the attack is instantaneous, as previously stated.
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Re: Envelopes

Postby carbon14 » Wed Jun 13, 2012 9:28 am

For clarification:

For the decay and release phases, the envelope attenuation is incremented by 8 every so often,
At higher rates, the period between increments reduces until the attenuation is incremented every sample
After that, the increments themselves are increased to 16 and then 32.

It's tempting to suggest that this represents two or four increments in the space of one sample, and certainly there are plenty of clock ticks within the space of each sample so this could have been used as an algorithm, but it was not.

The timing of the two different sized increments are inconsistent with the suggestion. And it is also inconsistent with the attack phase. The faster attack curves cannot be attained by two iterations of the slower algorithm.
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Re: Envelopes

Postby carbon14 » Thu Jun 14, 2012 2:36 pm

Ok, this is a little bit tentative, I've still got shed loads of data to get through.

Imagine a global clock, which increments every sample.

For an attack rate of 8, 32 samples per step.
On the rising edge of the 4th bit (so that represents every 16th sample)
we'll consider stepping (but we actually only step 4 times in every 8 occasions)
If we're stepping 5 times in 8, then the extra step occurs when the 5th and 6th bits are 0

<rubbish>
I think this bit below is probably complete rubbish - no inversion is necessary

For the decay we do the same thing, BUT we are operating on the inverse of the clock

I suspect there is another inversion when we get to the release.
</rubbish>


Finally, in this model, the tremolo appears to be recalculated AFTER all the operators are processed, it's effect seems to lag 1 sample behind the step timings.



This is probably not very clear, but I wanted to write it down before I forgot it. If the remaining data and subsequent experiments support it, I think I'll have to write a code snippet to demonstrate.
Last edited by carbon14 on Tue Jun 19, 2012 8:43 am, edited 1 time in total.
Reason: Flagging up that I said something stupid
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Re: Envelopes

Postby carbon14 » Tue Jul 03, 2012 12:15 pm

Below is a code fragment for my envelope. This code does not take into account the change in KON which is necessary to trigger a change to the ENV_PHASE_AR state, or to the ENV_PHASE_RR state.
the variable env which is output is the 9 bit envelope value, it starts at 511, drops to 0 during AR and then falls back to 511, this would be shifted left 3 bits and added to the attenuation to shape the wave.

I have included a 4x4 lookup table for the 4 fractional rates and as far as I can see, these timings are correct. If the required number of less significant clock bits are 0, then the envelope may change. Whether or not a change takes place depends on the next 3 bits of the clock. If the lsb of these is 1 then the change takes place, else the remaining 2 bits are used as the minor index to the table. The fractional part of the rate (held in the variable rate2) is used as the major index to the lookup table.

Note that when the envelope reaches a target value, the envelope phase changes, but 1 sample is lost. This is consistent with my measurements. A minimum of 2 samples occur at the peak amplitude, and a minimum of 2 samples occur at the sustain level.

The fractional timings (i.e. which steps are used and which are skipped) are taken from experiments where the rates were changed between 8 and 11 every 100 or so samples. At a rate of 8, the basic step interval is 16, at 11, the step interval is 2 samples. So a set of 8 steps at the faster rate, fit into the gap defined by a single step at the slower rate. This seems to consistently show the timings that I have included in my table.

Code: Select all
unsigned char stepTable[16] = {0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1};

#define ENV_PHASE_AR 1
#define ENV_PHASE_DR 2
#define ENV_PHASE_SL 3
#define ENV_PHASE_RR 0

int main(int argc, char *argv[])
{

   int AR = 13;
   int DR = 13;
   int SL = 2;
   int RR = 13;
   int EGT = 0;
   int FNUM = 0xFF;
   int BLOCK = 1;
   int NTS = 0;
   int KSR = 0;
   int phase = ENV_PHASE_AR;
   int env = 511;

   unsigned int clock = 0;

   while (env < 512)
   {
      switch (phase)
      {
         case ENV_PHASE_AR:
            if (env)
            {
               int rate1 = (((BLOCK << 1) + ((FNUM >> (NTS?8:9)) & 0x1)) >> (KSR?0:2)) + (AR << 2);
               if (rate1 > 60)
                  rate1 = 60;
               int rate2 = rate1 & 0x03;
               rate1 >>= 2;

               if (AR)
               {
                  if (!(clock & (0x0FFF >> rate1)))
                  {
                     if (rate1 == 15)
                     {
                        env = 0;
                     }
                     else if (rate1 > 12)
                     {
                        int stepState = clock & 0x07;
                        env -= (env >> (16 - rate1 - stepTable[(rate2 << 2) + (stepState >> 1)])) + 1;
                     }
                     else
                     {
                        int stepState = (clock >> (12 - rate1)) & 0x07;
                        if ((stepState & 0x01) || stepTable[(rate2 << 2) + (stepState >> 1)])
                           env -= (env >> 3) + 1;
                     }
                  }
               }
            }
            else
            {
               phase = ENV_PHASE_DR;
            }
            break;

         case ENV_PHASE_DR:
            if (env < (1 << (SL + 3)))
            {
               if (DR)
               {
                  int rate1 = (((BLOCK << 1) + ((FNUM >> (NTS?8:9)) & 0x1)) >> (KSR?0:2)) + (DR << 2);
                  if (rate1 > 60)
                     rate1 = 60;
                  int rate2 = rate1 & 0x03;
                  rate1 >>= 2;

                  if (!(clock & (0x0FFF >> rate1)))
                  {
                     if (rate1 > 12)
                     {
                        int stepState = clock & 0x07;
                        env += (1 << (rate1 + stepTable[(rate2 << 2) + (stepState >> 1)] - 13));
                     }
                     else
                     {
                        int stepState = (clock >> (12 - rate1)) & 0x07;
                        if ((stepState & 0x01) || stepTable[(rate2 << 2) + (stepState >> 1)])
                           env++;
                     }
                  }
               }
            }
            else
               phase = EGT?ENV_PHASE_SL:ENV_PHASE_RR;
            break;
         case ENV_PHASE_SL:
            break;
         case ENV_PHASE_RR:
            if (RR)
            {
               int rate1 = (((BLOCK << 1) + ((FNUM >> (NTS?8:9)) & 0x1)) >> (KSR?0:2)) + (RR << 2);
               if (rate1 > 60)
                  rate1 = 60;
               int rate2 = rate1 & 0x03;
               rate1 >>= 2;

               if (!(clock & (0x0FFF >> rate1)))
               {
                  if (rate1 > 12)
                  {
                     int stepState = clock & 0x07;
                     env += (1 << (rate1 - stepTable[(rate2 << 2) + (stepState >> 1)] - 13));
                  }
                  else
                  {
                     int stepState = (clock >> (12 - rate1)) & 0x07;
                     if ((stepState & 0x01) || stepTable[(rate2 << 2) + (stepState >> 1)])
                        env++;
                  }
               }
            }
            break;
      }
      printf("%d\n", env);
      clock++;
   }

}
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Re: Envelopes

Postby carbon14 » Tue Jul 03, 2012 12:19 pm

The lookup table could be replaced by the following logical expression:

Code: Select all
(clock & 0x01) || (clock & 0x04 && rate2 & 0x01 && !(clock & 0x02)) || (rate2 & 0x02 && clock & 0x02);


where clock is the next 3 least significant bits of the clock after the string of zeros that trigger the step
and rate2 is the fractional part of the rate. i.e. the 2 lsb of the key rate number
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Re: Envelopes

Postby carbon14 » Wed Jul 11, 2012 9:14 am

I did a quick test last night to see what happens if you change from a sustained envelope EGT=1 to a non-sustained envelope during the sustain phase.

It seems to enter the release phase immediately.

I started a note with AR / DR / RR set to 0x0C and SL set to 0x00 and EGT = 1

This reached and maintained a sustained peak amplitude.

After approximately 200 samples I changed EGT to 0
whereupon it started to drop off (entered release phase)

After a further 200 samples I changed TL on both operators so as to provide a recognisable marker in the output

After a further 200 samples I reset KON



My intention was to release the sound with the KON reset even if the sustain was maintained despite the change in EGT,
by using the marker provided by TL, I could have confidence in my timing, (i.e. that I had indeed reached sustain before the EGT change) and that I could see a difference between a potential release due to the EGT change and the release due to the KON refresh.
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Previous

Return to Yamaha OPL-3 research

Who is online

Users browsing this forum: No registered users and 3 guests