stuck with polyphony (last note priority)

Status
Not open for further replies.

emmanuel63

Well-known member
Hello,

I try to make a simple polyphonic synth. I would like to have 5 voices with last note priority. I have been fighting all day with the "last note priority" feature. I would really appreciate if someone could indicate me a method or approach. I prefer not posting my code, because it is a mess and it doesn't work. I think I need a new fresh start.

Emmanuel
 
Hello Emmanuel. Yes, handing voices to in-coming notes is something that took me a while to implement on TSynth until I just sat down and wrote a series of operations based on what should happen. It isn't the best way perhaps, but it's reliably worked. You need to record when voices are assigned to notes, so that in-coming notes can be assigned to either 'free', no longer active voices or active voices that were assigned at the oldest time. This is from my code:

Code:
#define NO_OF_VOICES 12

long earliestTime = millis(); //For voice allocation - initialise to now

struct VoiceAndNote {
  uint32_t note;
  long timeOn;
  uint32_t voiceOn;//just a boolean 0 off / 1 on
};

struct VoiceAndNote voices[NO_OF_VOICES] = {{ -1, 0, 0}, { -1, 0, 0}, { -1, 0, 0}, { -1, 0, 0}, { -1, 0, 0}, { -1, 0, 0}, { -1, 0, 0}, { -1, 0, 0}, { -1, 0, 0}, { -1, 0, 0}, { -1, 0, 0}, { -1, 0, 0}};

//This returns the number of the voice you are assigning the note to
// note = -1 means you want to be assigned a voice for note on
// note= 0 - 127 is returning the voice that was previously assigned to that note, for note off
//voices are numbered from 1 not 0
int getVoiceNo(int note) {
  uint32_t voiceToReturn = -1;      //Initialise
  earliestTime = millis(); //Initialise to now
  if (note == -1) {
    //NoteOn() - Get the oldest free voice (recent voices may be still on release stage)
    for (int i = 0; i < NO_OF_VOICES; i++) {
      if (voices[i].voiceOn == 0) {
        if (voices[i].timeOn < earliestTime) {
          earliestTime = voices[i].timeOn;
          voiceToReturn = i;
        }
      }
    }
    if (voiceToReturn == -1) {
      //No free voices, need to steal oldest sounding voice
      earliestTime = millis(); //Reinitialise
      for (int i = 0; i < NO_OF_VOICES; i++) {
        if (voices[i].timeOn < earliestTime) {
          earliestTime = voices[i].timeOn;
          voiceToReturn = i;
        }
      }
    }
    return voiceToReturn + 1;
  } else {
    //NoteOff() - Get voice number from note
    for (int i = 0; i < NO_OF_VOICES; i++) {
      if (voices[i].note == note && voices[i].voiceOn == 1) {
        return i + 1;
      }
    }
  }
  //Shouldn't get here, return voice 1
  return 1;
}

//Then set the voices[].note in the code where the voice is sounded:
 voices[0].note = note;
  voices[0].timeOn = millis();
  voices[0].voiceOn= 1;

//And set this for note off
  voices[0].voiceOn= 0;

This is partly why synths like the SCI Prophets were expensive, they had a Z80 CPU to control among other things the distribution of voices to notes.
 
Last edited:
Here's one approach:

First a filter layer to generate extra noteOff and noteOn messages/calls to ensure the live note count is <= 5.

Secondly a standard mapping from notes to oscillators which never has to deal with more than 5 notes.

I suspect you want a different ADSR setting for when a note gets restarted due to a later note going away and
freeing up an oscillator.

There's an issue with handover of an oscillator from one note to a new one - the release phase has to be
expedited to allow the new note to start in a timely fashion, but not so fast that there's an audible click.
 
You can use UHF:s approach but instead of using time on you can just simply count up a var each time a new note is played and use that as the ref. for first note "replace", using a unsigned long (32bit) makes it possible to play 5*10 notes each second in ~2.7years straight without need to consider for overflow.
 
You can use UHF:s approach but instead of using time on you can just simply count up a var each time a new note is played and use that as the ref. for first note "replace", using a unsigned long (32bit) makes it possible to play 5*10 notes each second in ~2.7years straight without need to consider for overflow.

A simple approach is to create a circular buffer of pointers to voices. Increment a pointer into that buffer when starting a new note. Roll over when you've hit the number of voices available. This doesn't take duration into account, but you could alternately add a sort based on how many milliseconds remain, or percentage done for each note, relative volume, etc...
 
If you get stuck expanding the referenced post, let me know and I'll provide some base code. It's a good mental exercise, however, and I recommend giving it a try.
 
Thank you all.

I finally succeed in making an 8 voices synth, following examples and ideas of this thread.
Emmanuel
 
Status
Not open for further replies.
Back
Top