MiniSpartan6+ Audio

From Hamsterworks Wiki!

Jump to: navigation, search

This FPGA project was started in Jan 2015.

synth_test.c

Here's the guts of a wave table based synth with 64 note polyphony, with 48kHz/16-bit output. I've implemented it as a C software, but with an eye to putting it in an FPGA:

/*************************************************
* synth_test.c : Testing a musical synth
*                before implementing it in an FPGA
*
* Author : Mike Field <hamster@snap.net.nz>
*
**************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define MAX_NOTES 96
#define SAMPLE_RATE 48000
#define SAMPLE_SIZE 1024
static int attack  = 0xFFF;
static int decay   = 0xFF80;
static int release = 0xFFF0;
static int sustain = 0xAFFFFF;
static int release_time = 11000;

static int mode[MAX_NOTES];
static long long level[MAX_NOTES];
static int phase[MAX_NOTES];
static int stride[MAX_NOTES];
static short int samples[SAMPLE_SIZE];
static int t = 0;

void setup(void) {
  int note;
  int i;
  double q = pow(2.0,1.0/12);
  for(note = 0; note < MAX_NOTES; note++)
  {
    double f = 1024*(1.0 / 12.25) * pow(q,note)/SAMPLE_RATE * (1<<22);
    fprintf(stderr, "%i %f\r\n",note,f);
    stride[note] = (int)(f+0.5);
  }

  for(i = 0; i < SAMPLE_SIZE; i++) {
    float f =  sin(i*2*3.141592/SAMPLE_SIZE);
    samples[i] = f*f*f*0x7FFF;
  }
}

int main(int c, char *argv[])
{
  int note;
  setup();

  while(1)
  {
    short int sample1;
    short int sample2;
    int total = 0;

    /* Play a scale */
    if(t%(SAMPLE_RATE/8) == 0)
      mode[(t/(SAMPLE_RATE/8))%MAX_NOTES] = 1;

    /* Process the envelope generators */
    for(note = 0; note < MAX_NOTES; note++)
    {
      int mult = 0x10000;
      int add_on = 0;
      /* Get the envelope generator constants */
      switch(mode[note])
      {
        case 1:
          add_on = attack;
          break;
        case 2:
          mult   = decay;
          break;
        case 3:
          add_on = attack;
          break;
        case 4:
          mult   = release;
          break;
        default:
          mult = 0;
          break;
      }

      /* Process the envelope generator FSM state */
      switch(mode[note])
      {
        case 0:
          break;
        case 1:
          if(level[note] > 0xFFFFFF-2*attack) mode[note] = 2;
          break;
        case 2:
          if(level[note] < sustain) mode[note] = 3;
          break;
        case 3:
          if(t > release_time) mode[note] = 4;
          break;
        case 4:
          if(level[note] < 0xFF) mode[note] = 0;
          break;
        default:
          break;
      }

      /* Perform the envelope generator MAC */
      level[note] = ((level[note]*mult)>>16) + add_on;

      /* Update the phase accumulator */
      phase[note] += stride[note];

#if 1
      /* A simple moving average DSP filter  */
      sample1 = ((long long)(samples[(phase[note]>>14)&0x3FF]) * level[note])>>26;
      sample2 = ((long long)(samples[((phase[note]+stride[note]/2)>>14)&0x3FF]) * level[note])>>26;
      total += (sample1+sample2)/2;
#else
      sample1 = ((long long)(samples[(phase[note]>>14)&0x3FF]) * level[note])>>26;
      total += sample1;
#endif
    }
    /* Clip the total */
    if(total > 32765) total = 32765;
    if(total < -32765) total = -32765;

    /* Output the total */
    sample1 = total;
    fwrite(&sample1,sizeof(sample1),1,stdout);

    /* Update the time counter */
    t++;
  }
  return 0;
}

It provides 64 channels, each with it's own oscillator and envelope generators. YOu can test it with "synth_test | aplay -r 48000 -c 1 -f S16_LE".

It is designed around a Multiply Accumulate (MAC) core for the envelope generation, and a multiplier to apply it to the tone sourced using a phase accumulator. When implemented in an FPGA it will be able process a channel every clock cycle, so at 48MHz and generating 48kHz audio it will be able to play 1,000 notes at the same time using only two DSP blocks and a few block RAM.

I will make it a bit more musical soon.

Personal tools