Issue accessing class properly in AudioSynthWaveform array

Status
Not open for further replies.

RABB17

Well-known member
Hello,

I am having a seemingly simple issue trying get polyphony from the Notes and Volts synth using the MidiSynth example. The issue I am running into is assigning AudioSynthWaveforms to an array of them... when I try to using classes from members of the array, which should be exactly the same as using the classes from the waveform object outside the array, it just stops working. I have attached the code in question. In the oscPlay() function you can comment out the section of code that is active and uncomment the inactive to see the problem.

C++ isn't my native language, but I can't for the life of me figure what is causing it to break. Any help would be appreciated.

Thanks for your time!

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

const int TOTAL_VOICES = 4;

// GUItool: begin automatically generated code
AudioSynthNoisePink      pink1;          //xy=55,87
AudioSynthNoisePink      pink2; //xy=55,212
AudioSynthNoisePink      pink3; //xy=55,339
AudioSynthNoisePink      pink4;          //xy=55,464
AudioSynthWaveform       waveform1;      //xy=64,21
AudioSynthWaveform       waveform2;      //xy=64,54
AudioSynthWaveform       waveform4; //xy=64,146
AudioSynthWaveform       waveform3; //xy=64,179
AudioSynthWaveform       waveform5; //xy=64,273
AudioSynthWaveform       waveform6; //xy=64,306
AudioSynthWaveform       waveform7;      //xy=64,398
AudioSynthWaveform       waveform8;      //xy=64,431
AudioMixer4              mixer1;         //xy=199,61
AudioMixer4              mixer2; //xy=199,185
AudioMixer4              mixer3; //xy=199,313
AudioMixer4              mixer4;         //xy=199,437
AudioFilterStateVariable filter1;        //xy=320,67
AudioFilterStateVariable filter3; //xy=319,319
AudioFilterStateVariable filter2; //xy=323,191
AudioFilterStateVariable filter4;        //xy=323,443
AudioEffectEnvelope      envelope1;      //xy=460,54
AudioEffectEnvelope      envelope2; //xy=460,307
AudioEffectEnvelope      envelope3; //xy=461,178
AudioEffectEnvelope      envelope4;      //xy=461,430
AudioMixer4              mixer5;         //xy=653,244
AudioOutputI2S           i2s1;           //xy=779,245
AudioConnection          patchCord1(pink1, 0, mixer1, 2);
AudioConnection          patchCord2(pink2, 0, mixer2, 2);
AudioConnection          patchCord3(pink3, 0, mixer3, 2);
AudioConnection          patchCord4(pink4, 0, mixer4, 2);
AudioConnection          patchCord5(waveform1, 0, mixer1, 0);
AudioConnection          patchCord6(waveform2, 0, mixer1, 1);
AudioConnection          patchCord7(waveform4, 0, mixer2, 0);
AudioConnection          patchCord8(waveform3, 0, mixer2, 1);
AudioConnection          patchCord9(waveform5, 0, mixer3, 0);
AudioConnection          patchCord10(waveform6, 0, mixer3, 1);
AudioConnection          patchCord11(waveform7, 0, mixer4, 0);
AudioConnection          patchCord12(waveform8, 0, mixer4, 1);
AudioConnection          patchCord13(mixer1, 0, filter1, 0);
AudioConnection          patchCord14(mixer2, 0, filter2, 0);
AudioConnection          patchCord15(mixer3, 0, filter3, 0);
AudioConnection          patchCord16(mixer4, 0, filter4, 0);
AudioConnection          patchCord17(filter1, 0, envelope1, 0);
AudioConnection          patchCord18(filter3, 0, envelope3, 0);
AudioConnection          patchCord19(filter2, 0, envelope2, 0);
AudioConnection          patchCord20(filter4, 0, envelope4, 0);
AudioConnection          patchCord21(envelope1, 0, mixer5, 0);
AudioConnection          patchCord22(envelope3, 0, mixer5, 2);
AudioConnection          patchCord23(envelope2, 0, mixer5, 1);
AudioConnection          patchCord24(envelope4, 0, mixer5, 3);
AudioConnection          patchCord25(mixer5, 0, i2s1, 0);
AudioConnection          patchCord26(mixer5, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=1023,20
// GUItool: end automatically generated code

AudioSynthNoisePink noiseBank[] = {pink1, pink2, pink3, pink4};
AudioSynthWaveform oscBank1[] = {waveform1, waveform3, waveform5, waveform7};
AudioSynthWaveform oscBank2[] = {waveform2, waveform4, waveform6, waveform8};
AudioFilterStateVariable filterBank[] = {filter1, filter2, filter3, filter4};
AudioEffectEnvelope envelopeBank[] = {envelope1, envelope2, envelope3, envelope4};
AudioMixer4 oscMixerBank[] = {mixer1, mixer2, mixer3, mixer4};

//from midisynth

struct voice_t {
  int wavetable_id;
  byte channel;
  byte note;
};
voice_t voices[TOTAL_VOICES];

const byte BUFFER = 8; //Size of keyboard buffer
const float noteFreqs[128] = {8.176, 8.662, 9.177, 9.723, 10.301, 10.913, 11.562, 12.25, 12.978, 13.75, 14.568, 15.434, 16.352, 17.324, 18.354, 19.445, 20.602, 21.827, 23.125, 24.5, 25.957, 27.5, 29.135, 30.868, 32.703, 34.648, 36.708, 38.891, 41.203, 43.654, 46.249, 48.999, 51.913, 55, 58.27, 61.735, 65.406, 69.296, 73.416, 77.782, 82.407, 87.307, 92.499, 97.999, 103.826, 110, 116.541, 123.471, 130.813, 138.591, 146.832, 155.563, 164.814, 174.614, 184.997, 195.998, 207.652, 220, 233.082, 246.942, 261.626, 277.183, 293.665, 311.127, 329.628, 349.228, 369.994, 391.995, 415.305, 440, 466.164, 493.883, 523.251, 554.365, 587.33, 622.254, 659.255, 698.456, 739.989, 783.991, 830.609, 880, 932.328, 987.767, 1046.502, 1108.731, 1174.659, 1244.508, 1318.51, 1396.913, 1479.978, 1567.982, 1661.219, 1760, 1864.655, 1975.533, 2093.005, 2217.461, 2349.318, 2489.016, 2637.02, 2793.826, 2959.955, 3135.963, 3322.438, 3520, 3729.31, 3951.066, 4186.009, 4434.922, 4698.636, 4978.032, 5274.041, 5587.652, 5919.911, 6271.927, 6644.875, 7040, 7458.62, 7902.133, 8372.018, 8869.844, 9397.273, 9956.063, 10548.08, 11175.3, 11839.82, 12543.85};
byte globalNote = 0;
byte globalVelocity = 0;
int octave = 0;
const float DIV127 = (1.0 / 127.0);
float detuneFactor = 1;
float bendFactor = 1;
int bendRange = 12;

unsigned int LFOspeed = 2000;
float LFOpitch = 1;
float LFOdepth = 0;
byte LFOmodeSelect = 0;

int FILfreq =  10000;
float FILfactor = 1;


void setup() {
  Init();
}


void loop() {
  usbMIDI.read();
  LFOupdate(false, LFOmodeSelect, FILfactor, LFOdepth);
}

int allocateVoice(byte channel, byte note);
int findVoice(byte channel, byte note);
void freeVoices();

int used_voices = 0;
int stopped_voices = 0;
int evict_voice = 0;
int notes_played = 0;

void myNoteOn(byte channel, byte note, byte velocity) {
  if ( note > 23 && note < 108 ) {
    globalNote = note;
    globalVelocity = velocity;
    //keyBuff(note, true);
    oscPlay(note);
    LFOupdate(true, LFOmodeSelect, FILfactor, LFOdepth);
  }
}

void myNoteOff(byte channel, byte note, byte velocity) {
  if ( note > 23 && note < 108 ) {
    //keyBuff(note, false);
    oscStop();
  }
}

void myPitchBend(byte channel, int bend) {
  float bendF = bend;
  bendF = bendF / 8192;
  bendF = bendF * bendRange;
  bendF = bendF / 12;
  bendFactor = pow(2, bendF);
  oscSet();
}

void keyBuff(byte note, bool playNote) {
  static byte buff[BUFFER];
  static byte buffSize = 0;
  /*

    // Add Note
    if (playNote == true && (buffSize < BUFFER) ) {
    oscPlay(note);
    buff[buffSize] = note;
    buffSize++;
    return;
    }

    // Remove Note
    else if (playNote == false && buffSize != 0) {
    for (byte found = 0; found < buffSize; found++) {
      if (buff[found] == note) {
        for (byte gap = found; gap < (buffSize - 1); gap++) {
          buff[gap] = buff[gap + 1];
        }
        buffSize--;
        buff[buffSize] = 255;
        if (buffSize != 0) {
          oscPlay(buff[buffSize - 1]);
          return;
        }
        else {
          oscStop();
          return;
        }
      }
    }
    }
  */
}

void oscPlay(byte note) {

  waveform1.frequency(noteFreqs[note] * bendFactor * LFOpitch);
  waveform2.frequency(noteFreqs[note + octave] * detuneFactor * bendFactor * LFOpitch);
  float velo = (globalVelocity * DIV127);
  waveform1.amplitude(velo);
  waveform2.amplitude(velo);
  pink1.amplitude(velo);

  envelope1.noteOn();
  /*
    oscBank1[0].frequency(noteFreqs[note] * bendFactor * LFOpitch);
    oscBank2[0].frequency(noteFreqs[note + octave] * detuneFactor * bendFactor * LFOpitch);
    float velo = (globalVelocity * DIV127);
    oscBank1[0].amplitude(velo);
    oscBank2[0].amplitude(velo);
    noiseBank[0].amplitude(velo);

    envelopeBank[0].noteOn();

    Serial.print("Velocity in OscPlay: ");
    Serial.print(velo);
    Serial.println();
    Serial.print("envelope is active after noteOn: ");
    Serial.print(envelopeBank[0].isActive());
    Serial.println();
  */
}

void oscStop() {
  envelope1.noteOff();
  //envelopeBank[0].noteOff();
}

void oscSet() {
  oscBank1[0].frequency(noteFreqs[globalNote] * bendFactor * LFOpitch);
  oscBank2[0].frequency(noteFreqs[globalNote + octave] * detuneFactor * bendFactor * LFOpitch);
}

void myControlChange(byte channel, byte control, byte value) {
  switch (control) {
    case 100:
      mixer1.gain(0, (value * DIV127));
      break;

    case 101:
      mixer1.gain(1, (value * DIV127));
      break;

    case 102:
      mixer1.gain(2, (value * DIV127));
      break;

    case 103:
      switch (value) {
        case 0:
          octave = 24;
          break;
        case 1:
          octave = 12;
          break;
        case 2:
          octave = 0;
          break;
        case 3:
          octave = -12;
          break;
        case 4:
          octave = -24;
          break;
      }
      oscSet();
      break;

    case 104:
      envelope1.attack(3000 * (value * DIV127));
      break;

    case 105:
      envelope1.decay(3000 * (value * DIV127));
      break;

    case 106:
      envelope1.sustain(value * DIV127);
      break;

    case 107:
      envelope1.release(3000 * (value * DIV127));
      break;

    case 108:
      switch (value) {
        case 0:
          waveform1.begin(WAVEFORM_SINE);
          break;
        case 1:
          waveform1.begin(WAVEFORM_TRIANGLE);
          break;
        case 2:
          waveform1.begin(WAVEFORM_SAWTOOTH);
          break;
        case 3:
          waveform1.begin(WAVEFORM_PULSE);
          break;
      }
      break;

    case 109:
      switch (value) {
        case 0:
          waveform2.begin(WAVEFORM_SINE);
          break;
        case 1:
          waveform2.begin(WAVEFORM_TRIANGLE);
          break;
        case 2:
          waveform2.begin(WAVEFORM_SAWTOOTH);
          break;
        case 3:
          waveform2.begin(WAVEFORM_PULSE);
          break;
      }

    case 110:
      detuneFactor = 1 - (0.05 * (value * DIV127));
      oscSet();
      break;

    case 111:
      FILfactor = value * DIV127;
      FILfreq = 10000 * (value * DIV127);
      if (LFOmodeSelect < 1 || LFOmodeSelect > 5)filter1.frequency(FILfreq);
      break;

    case 112:
      filter1.resonance((4.3 * (value * DIV127)) + 0.7);
      break;

    case 113:
      if (value <= 12 && value > 0) bendRange = value;
      break;

    case 114:
      {
        float xSpeed = value * DIV127;
        xSpeed = pow(100, (xSpeed - 1));
        LFOspeed = (70000 * xSpeed);
        break;
      }

    case 115:
      LFOdepth = value * DIV127;
      break;

    case 116:
      LFOmodeSelect = value;
      break;
  }
}

void LFOupdate(bool retrig, byte mode, float FILtop, float FILbottom) {
  static float LFO = 0;
  static unsigned long LFOtime = 0;
  static bool LFOdirection = false;
  unsigned long currentMicros = micros();
  static bool LFOstop = false;
  static float LFOrange = 0;
  static byte oldMode = 0;
  static bool retriggered = false;

  if (retrig == true) retriggered = true;


  if (currentMicros - LFOtime >= LFOspeed) {
    LFOtime = currentMicros;

    if (mode != oldMode) {
      if (mode == 0 || mode == 8) {
        LFOpitch = 1;
        oscSet();
        filter1.frequency(FILfreq);
      }
      else if (mode >= 1 || mode <= 7) {
        LFOpitch = 1;
        oscSet();
      }
      else if (mode >= 9 || mode <= 13) {
        filter1.frequency(FILfreq);
      }
      oldMode = mode;
    }

    LFOrange = FILtop - FILbottom;
    if (LFOrange < 0) LFOrange = 0;

    // LFO Modes
    switch (mode) {

      case 0: //Filter OFF
        return;
        break;
      case 1: //Filter FREE
        filter1.frequency(10000 * ((LFOrange * LFO) + LFOdepth));
        break;
      case 2: //Filter DOWN
        if (retriggered == true) {
          LFOdirection = true;
          LFO = 1.0;
        }
        filter1.frequency(10000 * ((LFOrange * LFO) + LFOdepth));
        break;
      case 3: //Filter UP
        if (retriggered == true) {
          LFOdirection = false;
          LFO = 0;
        }
        filter1.frequency(10000 * ((LFOrange * LFO) + LFOdepth));
        break;
      case 4: //Filter 1-DN
        if (retriggered == true) {
          LFOstop = false;
          LFOdirection = true;
          LFO = 1.0;
        }
        if (LFOstop == false) filter1.frequency(10000 * ((LFOrange * LFO) + LFOdepth));
        break;
      case 5: //Filter 1-UP
        if (retriggered == true) {
          LFOstop = false;
          LFOdirection = false;
          LFO = 0;
        }
        if (LFOstop == false) filter1.frequency(10000 * ((LFOrange * LFO) + LFOdepth));
        break;
      case 8: //Pitch OFF
        return;
        break;
      case 9: //Pitch FREE
        LFOpitch = (LFO * LFOdepth) + 1;
        oscSet();
        break;
      case 10: //Pitch DOWN
        if (retriggered == true) {
          LFOdirection = true;
          LFO = 1.0;
        }
        LFOpitch = (LFO * LFOdepth) + 1;
        oscSet();
        break;
      case 11: //Pitch UP
        if (retriggered == true) {
          LFOdirection = false;
          LFO = 0;
        }
        LFOpitch = (LFO * LFOdepth) + 1;
        oscSet();
        break;
      case 12: //Pitch 1-DN
        if (retriggered == true) {
          LFOstop = false;
          LFOdirection = true;
          LFO = 1.0;
        }
        if (LFOstop == false) {
          LFOpitch = (LFO * LFOdepth) + 1;
          oscSet();
        }
        break;
      case 13: //Pitch 1-UP
        if (retriggered == true) {
          LFOstop = false;
          LFOdirection = false;
          LFO = 0;
        }
        if (LFOstop == false) {
          LFOpitch = (LFO * LFOdepth) + 1;
          oscSet();
        }
        break;
    }

    retriggered = false;

    // Update LFO
    if (LFOdirection == false) { //UP
      LFO = (LFO + 0.01);
      if (LFO >= 1) {
        LFOdirection = true;
        LFOstop = true;
      }
    }

    if (LFOdirection == true) { //Down
      LFO = (LFO - 0.01);
      if (LFO <= 0) {
        LFOdirection = false;
        LFOstop = true;
      }
    }
  }
}

int allocateVoice(byte channel, byte note) {
  int i;
  int nonfree_voices = stopped_voices + used_voices;
  if (nonfree_voices < TOTAL_VOICES) {
    for (i = nonfree_voices; i < TOTAL_VOICES && voices[i].channel != channel; ++i);
    if (i < TOTAL_VOICES) {
      voice_t temp = voices[i];
      voices[i] = voices[nonfree_voices];
      voices[nonfree_voices] = temp;
    }
    i = nonfree_voices;
    used_voices++;
  }
  else {
    if (stopped_voices) {
      i = evict_voice % stopped_voices;
      voice_t temp = voices[i];
      stopped_voices--;
      voices[i] = voices[stopped_voices];
      voices[stopped_voices] = temp;
      used_voices++;
      i = stopped_voices;
    }
    else
      i = evict_voice;
  }

  voices[i].channel = channel;
  voices[i].note = note;

  evict_voice++;
  evict_voice %= TOTAL_VOICES;

  return voices[i].wavetable_id;
}

int findVoice(byte channel, byte note) {
  int i;
  //find match
  int nonfree_voices = stopped_voices + used_voices;
  for (i = stopped_voices; i < nonfree_voices && !(voices[i].channel == channel && voices[i].note == note); ++i);
  //return TOTAL_VOICES if no match
  if (i == (nonfree_voices)) return TOTAL_VOICES;

  voice_t temp = voices[i];
  voices[i] = voices[stopped_voices];
  voices[stopped_voices] = temp;
  --used_voices;

  return voices[stopped_voices++].wavetable_id;
}

void freeVoices() {
  for (int i = 0; i < stopped_voices; i++)
    if (!true/*wavetable[voices[i].wavetable_id].isPlaying() == false*/) {
      voice_t temp = voices[i];
      --stopped_voices;
      voices[i] = voices[stopped_voices];
      int nonfree_voices = stopped_voices + used_voices;
      voices[stopped_voices] = voices[nonfree_voices];
      voices[nonfree_voices] = temp;
    }
}

const char* note_map[] = {
  "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
};

void printVoices() {
  static int last_notes_played = notes_played;
  if (last_notes_played == notes_played)
    return;
  last_notes_played = notes_played;
  int usage = AudioProcessorUsage();
  Serial.printf("\nCPU:%03i voices:%02i CPU/Voice:%02i evict:%02i", usage, used_voices, usage / used_voices, evict_voice);
  for (int i = 0; i < used_voices; ++i)
    Serial.printf(" %02hhu %-2s", voices[i].channel, note_map[voices[i].note % 12]);
}

void Init()
{
  AudioMemory(120);
  usbMIDI.setHandleControlChange(myControlChange);
  usbMIDI.setHandleNoteOff(myNoteOff);
  usbMIDI.setHandleNoteOn(myNoteOn);
  usbMIDI.setHandlePitchChange(myPitchBend);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.52);
  sgtl5000_1.dacVolumeRamp();

  envelopeBank[0] = envelope1;
  envelopeBank[1] = envelope2;
  envelopeBank[2] = envelope3;
  envelopeBank[3] = envelope4;

  for (int i = 0; i < TOTAL_VOICES; i++)
  {
    oscBank1[i].begin(WAVEFORM_SINE);
    oscBank1[i].amplitude(1.0);
    oscBank1[i].frequency(82.41);
    oscBank1[i].pulseWidth(0.15);

    oscBank2[i].begin(WAVEFORM_SINE);
    oscBank2[i].amplitude(1.0);
    oscBank2[i].frequency(82.41);
    oscBank2[i].pulseWidth(0.15);

    noiseBank[i].amplitude(0.3);

    for (int j = 0; j < 4; j++)
    {
      oscMixerBank[i].gain(j, 1.0);
    }

    envelopeBank[i].delay(0);
    envelopeBank[i].attack(0);
    envelopeBank[i].decay(0);
    envelopeBank[i].sustain(1);
    envelopeBank[i].release(500.0);
    envelopeBank[i].releaseNoteOn(7.0);

    mixer5.gain(i, 1.0);
  }
}
 
I’m suspect your array initializers made copies of the AudioSynthWaveform objects.
I’d try using pointers, or maybe references.

I just checked - arrays of references aren't allowed in C++. So, pointers it is.
 
Last edited:
I think I understand... Essentially I need to create an array of AudioSynthWaveform pointers and reference that? I don't use Cpp terribly often, so I apologize for my ignorance on the issue. Can you use a pointer to the class to access member functions or do you need to specifically create a pointer to the member function of the class?

If you've indeed hit upon the issue, which I feel pretty confident you have, I wonder if it is not possible to declare the audiosynthwaveform objects in an array initially so no copies are made and one could work with that directly.

Anyways, thanks for taking the time to reply
 
This might seem pedantic; I prefer the term 'precise'. :)

@neurofun suggested the following, to create an array of 4 objects of type AudioSynthwaveform
AudioSynthWaveform* waveform = new AudioSynthWaveform[4];

Yes, it creates an array, and the constructors are called. But it creates an unnamed array, and stores the address of that nameless array in a pointer variable named waveform.

And yes, waveform can the be dereferenced using array syntax, with [].

BUT, here's the distinction: waveform IS A POINTER, not an array. Since it's a pointer, you can assign to it:

waveform = (void *)NULL;


On the other hand:

AudioSynthWaveform waveform[4];

Creates a true array:

waveform = (void *)NULL; //will result in a compiler error.

Like I said, pedantic.
 
@tele_player
I think being precise or pedantic as you say, is important to avoid confusion or misunderstandings, especially when dealing with technological stuff.
Thanks for the clarification.
 
Indeed, I find precision a quality to be admired in engineering endeavors :) Thank you for the edification! I posted a quick and dirty 4 voice version of the notes and volts synth in the Audio projects.
 
Status
Not open for further replies.
Back
Top