sensorium / Mozzi

sound synthesis library for Arduino
https://sensorium.github.io/Mozzi/
GNU Lesser General Public License v2.1
1.09k stars 187 forks source link

swapping out different wave-table arrays in struct #2

Closed konsumer closed 12 years ago

konsumer commented 12 years ago

I am trying to build a multi-channel synth (4 voices with choice of whatever wave-table you want.) To make it simple, I am using all 512-sized wave-tables. I want to define each channel as a struct, like this:

#include <tables/sin512_int8.h>
#include <tables/saw_analogue512_int8.h>
#include <tables/triangle_analogue512_int8.h>
#include <tables/square_analogue512_int8.h>

#define SIN 0
#define SAW 1
#define TRI 2
#define SQR 3

#define VOICE_COUNT 4

struct  {
    byte gain;
    int freq;
    byte wavenum;
} channel[VOICE_COUNT] = {0, 440, SIN};

I imagine that things like this would be happening in updateControl():

void updateControl(){
    channel[1].gain = 1;
    channel[1].wavenum = TRI;
    channel[1].freq = mtof(64);
}

I am having trouble understanding how to init Oscil() into an array structure (in example: wave_tables,) so in my updateAudio() I can just do this:

int updateAudio(){
  output=0;

  for(i=0;i<VOICE_COUNT;i++){
    if (channel[i].gain != 0){
        wave_tables[ channel[i].wavenum ].setFreq(channel[i].freq);
        output += wave_tables[ channel[i].wavenum ].next() * channel[i].gain;
    }
  }

  return output;
}

I don't understand how to make wave_tables array. Am I thinking about this the wrong way? Do I need to do something with pointers or manually mess with phase?

konsumer commented 12 years ago

Also, it seems like with this code, if I had multiple channels set to TRI, updateAudio() would call next() several times, which would mess up the phase, right?

konsumer commented 12 years ago

Here is another try (full code example) that seems closer, but it's still re-initializing the wave too much for my taste. I feel like there is some C-thing I am probably forgetting/don't know:

#include <MozziGuts.h>
#include <Oscil.h>
#include <utils.c>
#include <EventDelay.h>

#include <tables/sin512_int8.h>
#include <tables/saw_analogue512_int8.h>
#include <tables/triangle_analogue512_int8.h>
#include <tables/square_analogue512_int8.h>

#define CONTROL_RATE 64

#define SIN 0
#define SAW 1
#define TRI 2
#define SQR 3

#define VOICE_COUNT 4

struct channel{
  byte gain;
  unsigned int freq;
  byte wave;
  byte oldwave;
};

// initialize channels with SIN @ 440Hz
channel channels[VOICE_COUNT] = {0, mtof(60), SIN, SIN};
Oscil<512, AUDIO_RATE> wave_table[VOICE_COUNT] = { Oscil<512, AUDIO_RATE>(SIN512_DATA), Oscil<512, AUDIO_RATE>(SIN512_DATA), Oscil<512, AUDIO_RATE>(SIN512_DATA), Oscil<512, AUDIO_RATE>(SIN512_DATA) };

// audio output
int output;

// generic iterator
byte i = 0;

EventDelay noteDelay(CONTROL_RATE);

void setup(){
  startMozzi(CONTROL_RATE);

  // change 1 channel's freq every second
  noteDelay.set(1000);

  // set wave #'s
  channels[1].wave = SAW;
  channels[2].wave = TRI;
  channels[3].wave = SQR;

  // set freq, so you can tell channels apart
  channels[1].freq = mtof(30);
  channels[2].freq = mtof(45);
  channels[3].freq = mtof(75);

  Serial.begin(9600);
}

void loop(){  
  audioHook();
}

unsigned long randnum;

void updateControl(){
  // update wave tables to refelect channels
  for(i=0;i<VOICE_COUNT;i++){
    if (channels[i].oldwave != channels[i].wave){
      switch(channels[i].wave){
        case SIN: wave_table[i] = Oscil<512, AUDIO_RATE>(SIN512_DATA); break;
        case SAW: wave_table[i] = Oscil<512, AUDIO_RATE>(saw_analogue512_data); break;
        case TRI: wave_table[i] = Oscil<512, AUDIO_RATE>(TRIANGLE_ANALOGUE512_DATA); break;
        case SQR: wave_table[i] = Oscil<512, AUDIO_RATE>(SQUARE_ANALOGUE512_DATA); break;
      }
      channels[i].oldwave = channels[i].wave;
    }
    wave_table[i].setFreq(channels[i].freq);
  }

  // tester code: change 1 channel's freq every second
  if (noteDelay.ready()){
    randnum = xorshift96();
    // offset note changes to make voices easier to tell apart
    for(i=0;i<VOICE_COUNT;i++){
      if (byteMod(randnum, VOICE_COUNT) == i){
        channels[i].gain = 0;
        channels[i].freq = mtof(byteMod(randnum, 48) + (12*i));
        channels[i].gain = 1;
        channels[i].wave = byteMod(randnum, 4);
        noteDelay.start();
        break;
      }
    }
  }
}

int updateAudio(){
  output=0;

  for(i=0;i<VOICE_COUNT;i++){
    if (channels[i].gain != 0){
        output += wave_table[ i ].next() * channels[i].gain;
    }
  }

  return output;
}
sensorium commented 12 years ago

Hi, have you tried using pointers/references? I've had a crack at it like this:

// array of references to initialised tables
Oscil <512, AUDIO_RATE> * wave_tables[NUM_TABLES] ={&sin_table, &tri_table};

// pointer to a table, should work for all if they are the same size
Oscil <512, AUDIO_RATE> * current_table;

// then in updateAudio()....
current_table = wave_tables[i];
out += (*current_table).next();

I think that works (here) - let me know ..

konsumer commented 12 years ago

I think that would also effect multiple tables when I make changes to 2 voices with same waveform. I think my solution (full code example) gets around that, because each wave_table is a separate Oscil().

sensorium commented 12 years ago

You are right about calling next() several times on the same Oscil changing the phase - The way I've approached this is to use a separate Oscil to store each set of variables (ie. phase info!) -- they can read from the same wavetable. Open to any ideas about other ways / suggestions, etc.

Woops! Just got your last comment!

And I just realised/noticed the initialising the Oscils in updateControl() - does that play? I would have expected it to glitch or freeze...

A simple way might be to save each Oscil's next() value once per update(Audio(), and use this as many times as you want.

I didn't include an Oscil::current() method because I was wary of possible overhead in th extra layer of reference - in my tests, a saving local char is faster if you want to re-use the next() value.

Thanks for the feedback.

konsumer commented 12 years ago

It seems to play ok, but it is hard to tell with so many channels going. I am working on MIDI, now, so I can have better control of which channels are playing at once. Since the wave-tables change only happens once per change (channel.old_wave) it seems to be ok. I tried it in loop() and updateControl() and it seems fine in both.

konsumer commented 12 years ago

Ok, after some hefty testing with 4 channels + midi + switching wave-tables a lot, my code sounds a bit jerky (but less than I might have thought!) Even though it's messed up, it sounds kinda cool. I am going to experiment with optimizing by phase.

Feel free to have a look at my experiments : arduino code + output + renoise file I used to send midi.

The hardware is:

sensorium commented 12 years ago

Great! pasted this topic to https://groups.google.com/forum/#!forum/mozzi-users the Mozzi-users group, just set up now...