Code

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

Code

Postby carbon14 » Fri Aug 27, 2010 9:34 am

Below is a lookup table generator. It simply spits out c code for a pair of lookup tables, each an array of 256 short unsigned integers.

The algorithms come from Olli Niemitalo and Matthew Gambrell link

Code: Select all
#include <math.h>
#include <stdio.h>

static unsigned short int expgen (unsigned char input)
{
   return (round((pow(2,input/256.0f)-1)*1024));
}

static unsigned short int lsgen (unsigned char input)
{
   return (round(-log(sin((input+0.5f)*M_PI/256/2))/log(2)*256));
}


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


   printf("unsigned short int expTable[] = {\n");

   for (i = 0; i < 0x100; i++)
   {
      if (i)
         printf(",\n");
      printf("\t0x%04x", expgen(i));
   }
   printf ("\n};\n\n");

   printf("unsigned short int logsinTable[] = {\n");

   for (i = 0; i < 0x100; i++)
   {
      if (i)
         printf(",\n");
      printf("\t0x%04x", lsgen(i));
   }
   printf ("\n};\n");
   return 0;
}
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Waveform Select

Postby carbon14 » Fri Aug 27, 2010 9:35 am

A waveform lookup function.
This takes two parameters, one is the value of the WS register, a 3-bit value representing the specific waveform to select. The second parameter is a counter. Since the waveform lookup is 256 entries long and represents ¼ of a wave, the lower 10 bits of the counter are used to represent the 1024 steps in the complete waveform.
The return value is an unsigned short integer, in which the msb is used as a sign-bit. When the msb is 1, the output value represents a negative output.
More notes on this will follow.

There is nothing new in this table, I have simply pulled together various notes already in the field - specifically from:

Olli Niemitalo,
Matthew Gambrell,
Hellwig Geisse,
Jani

Code: Select all
//
// tbl.c is a pre-generated lookup table
//
#include "tbl.c"

/*
                 /-\
                |   |
Waveform 1      +   +   +  ABCD
                    |   |
                     \-/

                 /-\
                |   |
Waveform 2      +   +---+  ABCX

                 /-\ /-\
                |   |   |
Waveform 3      +   +   +  ABAB

                 /+  /+
                | | | |
Waveform 4      + +-+ +--  AXAX

                 ^ ^
                | | |
Waveform 5      +-+-+----  EEXX

                 ^
                | |
Waveform 6      +-+-+----  EFXX
                  | |
                   v

                +---+
                |   |
Waveform 7      +   +   +  GGHH
                    |   |
                    +---+

                |\
                | \
Waveform 8      +  ---  +  IJKL
                      \ |
                       \|

*/

unsigned short int WaveSelect(unsigned char ws, unsigned short int count)
{
   unsigned char index = (unsigned char) count;

   switch (ws | (count & 0x0300))
   {
      // rising quarter wave  Shape A
      case 0x0000:
      case 0x0001:
      case 0x0002:
      case 0x0202:
      case 0x0003:
      case 0x0203:
         return logsinTable[index];

      // falling quarter wave  Shape B
      case 0x0100:
      case 0x0101:
      case 0x0102:
      case 0x0302:
         return logsinTable[index ^ 0xFF];

      // rising quarter wave -ve  Shape C
      case 0x0200:
         return logsinTable[index] | 0x8000;

      // falling quarter wave -ve  Shape D
      case 0x0300:
         return logsinTable[index ^ 0xFF] | 0x8000;

      // fast wave +ve  Shape E
      case 0x0004:
      case 0x0005:
      case 0x0105:
         return logsinTable[(index ^ ((index & 0x80)?0xFF:0x00)) << 1];

      // fast wave -ve  Shape F
      case 0x0104:
         return logsinTable[(index ^ ((index & 0x80)?0xFF:0x00)) << 1] | 0x8000;

      // square wave +ve  Shape G
      case 0x0006:
      case 0x0106:
         return 0;

      // square wave -ve  Shape H
      case 0x0206:
      case 0x0306:
         return 0x8000;

      // Shape I
      case 0x0007:
         return index << 3;

      // Shape J
      case 0x0107:
         return index << 3 | 0x800;

      // Shape K
      case 0x0207:
         return (index ^ 0xFF) << 3 | 0x8800;

      // Shape L
      case 0x0307:
         return (index ^ 0xFF) << 3 | 0x8000;
   }

   // Shape X
   return 0x0C00;
}
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Re: Waveform select - notes

Postby carbon14 » Fri Aug 27, 2010 9:36 am

Most of the waveforms break down into variations on the basic 256 entry lookup table, so it made sense to me to combine the wave select, and the two msb of the counter to form a lookup (implemented as a switch) into those variant algorithms. This is probably efficient, and I hope that each individual variation is efficient in itself.
If you want, you can trade memory for cpu cycles and generate a full set of 8, 1024 entry lookups for these waveforms. 16K bytes is quite a chunk of memory, but can save a significant number of cpu operations per lookup, and you need to make 1 lookup per operator, per sample. You can then dispense with the original 256 entry logsin table completely, because it is incorporated in the first 256 entries of the waveform 1 table.

It seems likely that the ymf262 uses 1s complement internally for it's arithmetic, and it may well also use a floating point representation internally. Since it shouldn't have to do anything more arduous that addition and subtraction, this could be very efficient. I have chosen not to attempt that in my algorithms because generally microprocessors are better at 2s complement arithmetic and lack the end-around-carry operations that aid 1s complement. As I have noted elsewhere, the 1s complement output from the ymf262 is actually not quite in step with the expectations of the YAC512 DAC, but the operational difference is minimal. The sign-bit approach that I have taken will allow me to do the exponential lookups independantly of the sign, and then quicly convert the data to 2s complement before accumulation. The final output of my code is likely to be an offset 2s complement result which will differ from that produced by the ymf262, but which should produce the expected result when used directly in wav or aiff form.
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Exponent lookup

Postby carbon14 » Fri Aug 27, 2010 9:36 am

Below is code to convert by signed logarithmic wave form lookup to a linear output as a signed short integer.
The input parameter is the output of the waveform lookup plus any attenuation. Every 256 added to the input, represents a halving of the linear output (-3dB).

Code: Select all
signed short int Exp(unsigned short int expVal)
{
   unsigned short int signBit = expVal & 0x8000;
   signed short int result = expVal & 0xFF;

   expVal &= 0x7FFF;
   result = expTable[result ^ 0xFF] << 1;
   result |= 0x0800;
   result >>= (expVal >> 8);
   if (signBit)
      result = -result - 1;

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

Re: Waveform select - notes

Postby carbon14 » Fri Aug 27, 2010 9:58 am

Waveforms 5 and 6 which feature a double speed sine wave and implemented on chip by taking every second sample from the lookup table, not by sampling the table twice as often. This seemed the most likely hypothesis to work from, but I was able to confirm it by generating waveforms at exactly 2048 samples per cycle. Waveforms 5 and 6 produce pairs of identical adjacent samples.

Various people have suggested in the past that the last waveform, a rough sawtooth form with exponential curves, is generated directly from the exponential table. I have been able to confirm that my algorithm produces an output which matches the real device. The logarithmic value output from the waveform select is simply the counter value shifted up three places. This gives each half of the waveform an internal range from 0 to 4088. This represents a dynamic range of 0dB down to ~ -48dB sufficient to produce a zero value output at the center of the wave.
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Re: Code

Postby carbon14 » Thu Jul 26, 2012 11:03 am

I think that my code for waveforms 5 and 6 is incorrect.

Below is the corrected code for wave segments E and F

Code: Select all
      // fast wave +ve  Shape E
      case 0x0004:
      case 0x0005:
      case 0x0105:
         return logsinTable[((index << 1) ^ ((index & 0x80)?0x1FF:0x00))];

      // fast wave -ve  Shape F
      case 0x0104:
         return logsinTable[((index << 1) ^ ((index & 0x80)?0x1FF:0x00))] | 0x8000;


I need to go back and check against my original tests to make sure.
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England

Re: Code

Postby carbon14 » Fri Jul 27, 2012 10:03 am

Yes, I confirmed that there was indeed an error in my original post, shapes E and F should not produce the symmetric results that they used to.
User avatar
carbon14
 
Posts: 124
Joined: Tue Aug 05, 2008 9:11 am
Location: York, England


Return to Yamaha OPL-3 research

Who is online

Users browsing this forum: No registered users and 2 guests