"MIDI devices have been changed...refreshing"

Status
Not open for further replies.

sandman

Member
Hi all,

Hopefully some of you have some experience with FL Studio and maybe have encountered this problem. I've checked Image Line forums and people have had this issue before on many versions of FL Studio going back to version 11, but they were rarely resolved. When they were, it was about some MIDI service that needed to be disabled which was related to the midi input instruments they had purchased. I don't have any midi input instruments, just Teensy MIDI playback instruments. Anyways, my issue is below:

I've had an issue with FL Studio interrupting playback of music by refreshing MIDI devices. This forces playback to stop for the duration that it is refreshing (less than 1 second duration) and then restart from the beginning of the song. It refreshes 3 times in quick succesion; the same number of times as I have Teensy devices acting as Native USB MIDI devices.

I use my MIDI devices as playback devices rather than input devices. My channel rack on all my files consists solely of "MIDI Out" Channels. My teensy MIDI devices use MIDI Events to trigger interrupts to actuate some instruments that I've made. The devices are plugged into a powered USB 3.0 port and the port goes to the front panel USB 2.0 port on my PC. I have a PCIE USB 3.0 port that I had used in place of the front panel, but I encountered the problem there as well.

The timing on when this issue occurs is seemingly random. It doesn't occur at specific points within specific songs. I've gone several songs at times without encountering the issue. Often enough to be problematic though, I can't go more than 1 minute 30 seconds from start of playback to encounter it.

Here's the source code and compile settings for my instruments. My coding practices are probably shoddy; I'm not a professional.

Musical Floppy Drives:
Board - Teensy 3.6
USB Type - MIDI
CPU Speed - 180 MHz
Optimize - "Faster"

Code:
#include <TimerOne.h>

//Conditions and constraints
unsigned int period[60] = {     30402,  28696 , 27085 , 25565 , 24130 , 22776 , 21497 , 20291 , 19152 , 18077 , 17063 , 16105,
                                15201 , 14348 , 13543 , 12782 , 12065 , 11388 , 10749 , 10145 , 9576  , 9039  , 8531  , 8052,
                                7600  , 7174  , 6771  , 6391  , 6033  , 5694  , 5374  , 5073  , 4788  , 4519  , 4266  , 4026,
                                3800  , 3587  , 3386  , 3196  , 3016  , 2847  , 2687  , 2536  , 2394  , 2260  , 2133  , 2013,
                                1900  , 1793  , 1693  , 1598  , 1508  , 1423  , 1344  , 1268  , 1197  , 1130  , 1066  , 1007

                                //                               Uncorrected frequencies. I find that pulsing at 20 cents above the nominal frequency gets each note better tuned.
                                //                               https://docs.google.com/spreadsheets/d/12MbrmTmzfQR1kzVH3Orp17z25sn0WuJCodh59aa5hxo/edit?usp=sharing
                                //30581 , 28864 , 27244 , 25715 , 24272 , 22909 , 21624 , 20410 , 19264 , 18183 , 17163 , 16199 ,
                                //                           15290 , 14432 , 13622 , 12857 , 12136 , 11454 , 10812 , 10205 , 9632 , 9091 , 8581 , 8099 ,
                                //                           7645 , 7216 , 6811 , 6428 , 6068 , 5727 , 5406 , 5102 , 4816 , 4545 , 4290 , 4049 ,
                                //                           3822 , 3608 , 3405 , 3214 , 3034 , 2863 , 2703 , 2551 , 2408 , 2272 , 2145 , 2024,
                                //                           1911 , 1804 , 1702 , 1607 , 1517 , 1431 , 1351 , 1275 , 1204 , 1136 , 1072 , 1012 ,


                          };

float pitchfactor[164] = {0.500 , 0.504 , 0.509 , 0.513 , 0.517 , 0.522 , 0.526 , 0.531 , 0.535 , 0.540 , 0.544 , 0.549 , 0.554 , 0.558 , 0.563 , 0.568 , 0.573 , 0.578 , 0.583 , 0.588 ,
                          0.593 , 0.598 , 0.603 , 0.608 , 0.613 , 0.618 , 0.624 , 0.629 , 0.634 , 0.640 , 0.645 , 0.651 , 0.656 , 0.662 , 0.668 , 0.673 , 0.679 , 0.685 , 0.691 , 0.697 ,
                          0.703 , 0.709 , 0.715 , 0.721 , 0.727 , 0.733 , 0.739 , 0.746 , 0.752 , 0.759 , 0.765 , 0.772 , 0.778 , 0.785 , 0.791 , 0.798 , 0.805 , 0.812 , 0.819 , 0.826 ,
                          0.833 , 0.840 , 0.847 , 0.854 , 0.862 , 0.869 , 0.876 , 0.884 , 0.892 , 0.899 , 0.907 , 0.915 , 0.922 , 0.930 , 0.938 , 0.946 , 0.954 , 0.962 , 0.971 , 0.979 ,
                          0.987 , 1.000 , 1.004 , 1.013 , 1.021 , 1.030 , 1.039 , 1.048 , 1.057 , 1.066 , 1.075 , 1.084 , 1.093 , 1.103 , 1.112 , 1.122 , 1.131 , 1.141 , 1.151 , 1.160 ,
                          1.170 , 1.180 , 1.190 , 1.201 , 1.211 , 1.221 , 1.232 , 1.242 , 1.253 , 1.263 , 1.274 , 1.285 , 1.296 , 1.307 , 1.318 , 1.330 , 1.341 , 1.352 , 1.364 , 1.376 ,
                          1.387 , 1.399 , 1.411 , 1.423 , 1.435 , 1.448 , 1.460 , 1.473 , 1.485 , 1.498 , 1.511 , 1.523 , 1.536 , 1.550 , 1.563 , 1.576 , 1.590 , 1.603 , 1.617 , 1.631 ,
                          1.645 , 1.659 , 1.673 , 1.687 , 1.702 , 1.716 , 1.731 , 1.746 , 1.760 , 1.775 , 1.791 , 1.806 , 1.821 , 1.837 , 1.853 , 1.868 , 1.884 , 1.901 , 1.917 , 1.933 ,
                          1.950 , 1.966 , 1.983 , 2.000
                         };

const byte RESOLUTION = 25;
const byte MAX_POSITION = 158;




// data
byte currentPosition[17] = {LOW, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int basePeriod[17] = {LOW, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
bool currentStateStep[17] = {0 , LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW};
bool currentStateDir[17] = {0 , LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW};
int currentTick[] = {LOW, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
byte note2note;
byte note2octave;
float pitchShift[17];
byte currentNote[] = {LOW, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
byte lvar = 0;

//pin assignments
const byte STEPPIN[17] = {LOW, // Teensy MIDI channels are given as channels 1-16, not 0-15
                          2,    //Device 1
                          4,    //Device 2
                          6,    //Device 3
                          8,    //Device 4
                          11,   //Device 5
                          25,   //Device 6
                          27,   //Device 7
                          29,   //Device 8
                          14,     //Device 9
                          16,   //Device 10
                          18,   //Device 11
                          20,   //Device 12
                          22,   //Device 13
                          34,   //Device 14
                          36,   //Device 15
                          38,   //Device 16
                         };

const byte DIRPIN[] = { LOW,
                        3,    //Device 1
                        5,    //Device 2
                        7,    //Device 3
                        9,    //Device 4
                        10,   //Device 5
                        24,   //Device 6
                        26,   //Device 7
                        28,   //Device 8
                        15,     //Device 9
                        17,   //Device 10
                        19,   //Device 11
                        21,   //Device 12
                        23,   //Device 13
                        35,   //Device 14
                        37,   //Device 15
                        39,   //Device 16
                      };

const byte relay[] = {30, 31, 32};

void setup() {
  for (int channel = 1; channel <= 16; channel++) {
    pinMode(STEPPIN[channel], OUTPUT);
    pinMode(DIRPIN[channel], OUTPUT);
  }

  for (int setup_relays = 0; setup_relays <= 2; setup_relays++) {
    pinMode(relay[setup_relays], OUTPUT);
  }

  for (int i = 0; i<17; i++){
    pitchShift[i] = 1.000;
  }

  
  //period_lib();
  //pitchbend_Calculate();

  usbMIDI.setHandleNoteOff(OnNoteOff);
  usbMIDI.setHandleNoteOn(OnNoteOn);
  usbMIDI.setHandlePitchChange(OnPitchChange);

  Timer1.initialize(RESOLUTION);
  Timer1.attachInterrupt(processNotes);

  Serial.begin(250000);
}

void loop() {
  usbMIDI.read();
}



void OnNoteOn(byte channel, byte note, byte velocity) {
  currentNote[channel] = note;
  note2note = note % 12;
  note2octave = note / 12;
  if (note2octave < 4) { //37-48
    basePeriod[channel] = period[note2note];
  }
  else if (note2octave < 5) { //49-60
    basePeriod[channel] = period[12 + note2note];
  }
  else if (note2octave < 6) { //61-72
    basePeriod[channel] = period[24 + note2note];
  }
  else if (note2octave < 7) { //73-84
    basePeriod[channel] = period[36 + note2note];
  }
  else if (note2octave < 8) { //85-96
    basePeriod[channel] = period[48 + note2note];
  }
  else if (note2octave < 10) { //109-120
    relayFlip();
    Serial.println(note);
  }
  else if (note == 127) {
    resetDrives();
  }
  //  Serial.print(note); Serial.print("    "); Serial.println(basePeriod[channel]);
}


void OnNoteOff(byte channel, byte note, byte velocity) {
  if (currentNote[channel] == note) {
    basePeriod[channel] = 0;
  }
}


void OnPitchChange(byte channel, int pitch) {
  pitch = pitch / 100;
  pitchShift[channel] = pitchfactor[pitch+81];
  Serial.println(pitch);
}

void processNotes() {
  for (byte channel = 1; channel <= 16; channel++) {
    if (basePeriod[channel]) {                                                            // if currentPeriod of a channel is nonzero
      if (currentTick[channel] >= basePeriod[channel] / pitchShift[channel]) {                                //and if the current Period has past its period
        currentTick[channel] = 0;                                                           //reset the tick
        if (currentPosition[channel] >= MAX_POSITION) {    // check if direction needs to change due to read carriage bounds
          currentPosition[channel] = 0;
          currentStateDir[channel] = !currentStateDir[channel];
          digitalWrite(DIRPIN[channel], currentStateDir[channel]) ;
        }
        currentPosition[channel]++;
        currentStateStep[channel] = !currentStateStep[channel];                             //Step by changing the pin state
        digitalWrite(STEPPIN[channel], currentStateStep[channel]) ;

      }
      else currentTick[channel] += RESOLUTION;
    }
  }
}



void period_lib() {     // generates the periods used. Equation derived from site: http://www.phy.mtu.edu/~suits/NoteFreqCalcs.html
  float c0 = 16.35;
  float a = 1.059463094359;
  long period_data[128];

  for (int i = 0; i < 128; i++) {
    period_data[i] = long(1000000 / (c0 * pow(a, i)));
    if ((i % 12) < 1) Serial.println();
    Serial.print(period_data[i]);
    Serial.print(" , ");
  }
}

void pitchbend_Calculate() {
  for (int pitchbend = 0; pitchbend <= 163; pitchbend++) {
    pitchfactor[pitchbend] = pow(2.0, (pitchbend - 81.5) / 81.5); //calculates ratio f_new/f_old
    if ((pitchbend % 20) < 1) Serial.println();
    Serial.print(pitchfactor[pitchbend], 3);
    Serial.print(" , ");
  }
}

void resetDrives() {
  //Serial.println("resetDrives activated");
  Timer1.detachInterrupt();                                                                 //Turn Interrupt off
  for (byte channel = 1; channel <= 16; channel++) {                                        // Set all drives direction to backwards
    currentStateDir[channel] = HIGH;
    digitalWrite(DIRPIN[channel], currentStateDir[channel]);
  }
  for (byte t = 0; t < 200; t++) {                                                          // All drives go backwards 200 ticks (max is 158)
    for (byte channel = 1; channel <= 16; channel++) {
      currentStateStep[channel] = !currentStateStep[channel];
      digitalWrite(STEPPIN[channel], currentStateStep[channel]);
    }
    delayMicroseconds(7645);                                                                  // add delay
  }
  for (byte channel = 1; channel <= 16; channel++) {                                         // Set all drives direction to forwards
    currentStateDir[channel] = LOW;
    digitalWrite(DIRPIN[channel], currentStateDir[channel]);
    currentPosition[channel] = 0;
  }
  Timer1.attachInterrupt(processNotes);                                                   // Turn interrupt back on
}


void relayFlip() {
  lvar += 1;
  switch (lvar) {
    case 1:
      digitalWrite(relay[0], HIGH);
      break;
    case 2:
      digitalWrite(relay[0], LOW);
      break;
    case 3:
      digitalWrite(relay[1], HIGH);
      break;
    case 4:
      digitalWrite(relay[1], LOW);
      break;
    case 5:
      digitalWrite(relay[2], HIGH);
      break;
    case 6:
      digitalWrite(relay[2], LOW);
      lvar = 0;
      break;
    default:
      lvar = 0;
  }
}

Other stepper motor actuated instruments
Board - Teensy 3.6
USB Type - MIDI
CPU Speed - 180 MHz
Optimize - "Faster"

Code:
IntervalTimer mainTimer;
IntervalTimer modTimer[3];
elapsedMicros phaseTimer[17];

/*
   This code recieves MIDI data and plays the music on instruments

   Instruments:
   xylophone
   scanner 1
   scanner 2
   receipt printers 1 & 2 (3 channels (stepper motors each){
      1 channel for melody
      1 channel for back and forth gear chatter percussion
      1 channel for droning paper cutter sound
   }
   Stepper choir (4 channels)
   viola (1 channel)

   Total channels: (1 + 1 + 1 + 3 + 3 + 4 + 1 = 14

   It achieves this through use of the following libraries
   https://www.pjrc.com/teensy/td_midi.html
   https://www.pjrc.com/teensy/td_libs_SPI.html
   https://www.pjrc.com/teensy/td_timing_IntervalTimer.html
   https://www.pjrc.com/teensy/interrupts.html

*/

//Constants
const int RESOLUTION = 15; // Microseconds between calls to NoteProcessing; Set to difference in periods for a low period note with pitch factor at finest difference. 4050 uS * (0.504 - 0.5) = 16.2

//----------------------------------Lookup Tables----------------------------------//
const unsigned int microPeriods[] = {
  30578, 28861, 27242, 25713, 24270, 22909, 21622, 20409, 19263, 18182, 17161, 16198, //C0 - B0
  30578, 28861, 27242, 25713, 24270, 22909, 21622, 20409, 19263, 18182, 17161, 16198, //C1 - B1
  30578, 28861, 27242, 25713, 24270, 22909, 21622, 20409, 19263, 18182, 17161, 16198, //C2 - B2
  15289, 14436, 13621, 12856, 12135, 11454, 10811, 10205, 9632, 9091, 8581, 8099, //C3 - B3
  7645, 7218, 6811, 6428, 6068, 5727, 5406, 5103, 4816, 4546, 4291, 4050, //C4 - B4
  3800  , 3587  , 3386  , 3196  , 3016  , 2847  , 2687  , 2536  , 2394  , 2260  , 2133  , 2013, //C5 - B5
  1900  , 1793  , 1693  , 1598  , 1508  , 1423  , 1344  , 1268  , 1197  , 1130  , 1066  , 1007, //C6 - B6
  950, 897, 847, 799, 754, 712, 672, 634, 599, 565, 533, 504, //C7 - B7
  475, 449, 424, 400, 377, 356, 336, 317, 300,283, 267, 252,  //C8 - B8
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //C9 - B9
};
const float pitchfactor[164] = {0.500 , 0.504 , 0.509 , 0.513 , 0.517 , 0.522 , 0.526 , 0.531 , 0.535 , 0.540 , 0.544 , 0.549 , 0.554 , 0.558 , 0.563 , 0.568 , 0.573 , 0.578 , 0.583 , 0.588 ,
                                0.593 , 0.598 , 0.603 , 0.608 , 0.613 , 0.618 , 0.624 , 0.629 , 0.634 , 0.640 , 0.645 , 0.651 , 0.656 , 0.662 , 0.668 , 0.673 , 0.679 , 0.685 , 0.691 , 0.697 ,
                                0.703 , 0.709 , 0.715 , 0.721 , 0.727 , 0.733 , 0.739 , 0.746 , 0.752 , 0.759 , 0.765 , 0.772 , 0.778 , 0.785 , 0.791 , 0.798 , 0.805 , 0.812 , 0.819 , 0.826 ,
                                0.833 , 0.840 , 0.847 , 0.854 , 0.862 , 0.869 , 0.876 , 0.884 , 0.892 , 0.899 , 0.907 , 0.915 , 0.922 , 0.930 , 0.938 , 0.946 , 0.954 , 0.962 , 0.971 , 0.979 ,
                                0.987 , 1.000 , 1.000 , 1.013 , 1.021 , 1.030 , 1.039 , 1.048 , 1.057 , 1.066 , 1.075 , 1.084 , 1.093 , 1.103 , 1.112 , 1.122 , 1.131 , 1.141 , 1.151 , 1.160 ,
                                1.170 , 1.180 , 1.190 , 1.201 , 1.211 , 1.221 , 1.232 , 1.242 , 1.253 , 1.263 , 1.274 , 1.285 , 1.296 , 1.307 , 1.318 , 1.330 , 1.341 , 1.352 , 1.364 , 1.376 ,
                                1.387 , 1.399 , 1.411 , 1.423 , 1.435 , 1.448 , 1.460 , 1.473 , 1.485 , 1.498 , 1.511 , 1.523 , 1.536 , 1.550 , 1.563 , 1.576 , 1.590 , 1.603 , 1.617 , 1.631 ,
                                1.645 , 1.659 , 1.673 , 1.687 , 1.702 , 1.716 , 1.731 , 1.746 , 1.760 , 1.775 , 1.791 , 1.806 , 1.821 , 1.837 , 1.853 , 1.868 , 1.884 , 1.901 , 1.917 , 1.933 ,
                                1.950 , 1.966 , 1.983 , 2.000 ,
                               };
const float vibrato[] = {0.97, 0.98, 0.99, 1.000, 1.01, 1.02, 1.03, 1.02, 1.01, 1.00, 0.99, 0.98, 0.97};

//                          0  1   2     3  4  5  6   7     8  9  10  11  12  13  14  15  16
const int MAX_POSITION[] = {0, 0, 1000, -5, 0, 0, 0, 1000, -5, 0,  0,  0,  0,  0,  0,  0,  0};   // positive indicates back and forth stepper travel motion, negative indicates vibration back and forth, zero indicates continuous unidirectional travel

//----------------------------------Pins----------------------------------//
//For each channel, there is a stepper able to play different notes. This keeps track of that
//                                    0    1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16
const unsigned int channel2drive[] = {40, 40, 14, 16, 18, 24,  7, 34, 36, 38, 40, 40, 40, 40, 40, 40, 40};
const byte dinkChannel1 = 4; //interupt pin for dink channel 1
const byte dinkChannel2 = 9; //interupt pin for dink channel 2

// direction switches
const byte sc1sw1 = 27;
const byte sc1sw2 = 28;
const byte sc2sw1 = 10;
const byte sc2sw2 = 11;

//---------------------------------Pitch Shifting variable----------------------------//
float pitchShift [17] = {1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000}; //pull numbers from pitchfactor to here to modulate pitch

//----------------------------------Vibrato Variables---------------------------------//
int modPeriod[] = {6000, 6000, 6000};                                                                                                            //map pan control to change the mod Period of up to 3 instruments
byte vchannelElmt = 0;                                                                                                                           //element of vchannel to look at
unsigned int vibratoSpeed = 6000;                                                                                                                //
byte vchannel[3];
bool modTimerOn[3];
byte v0, v1, v2;

//----------------------------------dink variables----------------------------------//
long beat[dinkChannel2 + 1];
long phaseError[dinkChannel2 + 1];

//----------------------------------sleeping----------------------------------//
bool awake;
const byte sleepPinLength = 4;
const byte sleepPins[] = {26, 9, 13, 33};

//----------------------------------Stepper Motor Notes ----------------------------------//
int stepperNote[17] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};              // what note is this drive currently playing. (if negative, it's not playing)
unsigned int stepperPeriod[17];                                                                           // what period is this drive currently playing (if 0, it's not playing)
int Position[17];
unsigned int stepperTick[17];                                                                             //Current tick keeps track of where a note is in it's period
bool stepperState[33];
//Current tick keeps track of where a note is in it's period


void wakeywakey() {
  awake = HIGH;
  digitalWrite(26, HIGH);
  digitalWrite(9, HIGH);
  digitalWrite(13, LOW);
  digitalWrite(33, LOW);
  Serial.println("should be awake");
}

void goToSleep(){
  awake = LOW;
  digitalWrite(26, LOW);
  digitalWrite(9, LOW);
  digitalWrite(13, HIGH);
  digitalWrite(33, HIGH);
  Serial.println("should be asleep");
}

void myNoteOn(byte channel, byte note, byte velocity) {
  //Serial.println("Note On");
  if (velocity > 0) {
    if (channel > 1 && channel < 14) { // channels 2 through 13 are steppers
      if (!awake) wakeywakey();
      if (channel == dinkChannel1 || channel == dinkChannel2) {
        beat[channel] = phaseTimer[channel];
        phaseTimer[channel] = 0;
        if (stepperPeriod[channel] == 0) {
          stepperPeriod[channel] = beat[channel];
        }
        return;
      }
      else {
        stepperNote[channel] = note;
        stepperPeriod[channel] = microPeriods[note] / pitchShift[channel];
      }
    }
  }
}

void dink1() {
  phaseError[dinkChannel1] = phaseTimer[dinkChannel1];
  if (phaseError[dinkChannel1] < beat[dinkChannel1] / 2) { //if phase is closer to the preceding beat, then speed up to catch the next one.
    stepperPeriod[dinkChannel1] = (beat[dinkChannel1] - phaseError[dinkChannel1])  * (32.0 / 17.0) / 360 * 1.8; //microseconds per beat * 1 beat per 1 rev of gear 4 * gear ratio from gear 4 to gear 1 * 1 rev per 360 deg * 1.8 deg per 1 step = microseconds per step between beats
  }
  else //if phase is too close to the next beat, then speeding up may not be an option; just miss the upcoming beat and hit the next one.
    stepperPeriod[dinkChannel1] = (2 * beat[dinkChannel1] - phaseError[dinkChannel1]) * (32.0 / 17.0) / 360 * 1.8; //microseconds per beat * 1 beat per 1 rev of gear 4 * gear ratio from gear 4 to gear 1 * 1 rev per 360 deg * 1.8 deg per 1 step = microseconds per step between beats

  if (phaseTimer[dinkChannel1] > 2 * beat[dinkChannel1]) {
    stepperPeriod[dinkChannel1] = 0; //If the beat hasn't been updated, then stop playing
  }
}

void dink2() {
  phaseError[dinkChannel2] = phaseTimer[dinkChannel2];
  if (phaseError[dinkChannel2] < beat[dinkChannel2] / 2) { //if phase is closer to the preceding beat, then speed up to catch the next one.
    stepperPeriod[dinkChannel2] = (beat[dinkChannel2] - phaseError[dinkChannel2])  * (32.0 / 17.0) / 360 * 1.8; //microseconds per beat * 1 beat per 1 rev of gear 4 * gear ratio from gear 4 to gear 1 * 1 rev per 360 deg * 1.8 deg per 1 step = microseconds per step between beats
  }
  else //if phase is too close to the next beat, then speeding up may not be an option; just miss the upcoming beat and hit the next one.
    stepperPeriod[dinkChannel2] = (2 * beat[dinkChannel2] - phaseError[dinkChannel2]) * (32.0 / 17.0) / 360 * 1.8; //microseconds per beat * 1 beat per 1 rev of gear 4 * gear ratio from gear 4 to gear 1 * 1 rev per 360 deg * 1.8 deg per 1 step = microseconds per step between beats

  if (phaseTimer[dinkChannel2] > 2 * beat[dinkChannel2]) {
    stepperPeriod[dinkChannel2] = 0; //If the beat hasn't been updated, then stop playing
  }
}

void myNoteOff(byte channel, byte note, byte velocity) {
//  if (MAX_POSITION[channel] < 1) return; // don't turn off the note if it's percussion. percussion turns itself off after abs(MAX_POSITION[channel]) number of steps or after phaseError exceeds 3*beat
if (channel == 3 || channel  == 8 || channel == 4 || channel == 9) return;
if (stepperNote[channel] == note) { //don't turn off notes that are not the current note playing
    stepperPeriod[channel] =  0;
    stepperNote[channel] = -1;
    //Serial.print("ending note "); Serial.println(note);
  }
}

void myControlChange(byte channel, byte control, byte value) {
  Serial.print(channel); Serial.print("  ,  "); Serial.print(control); Serial.print("  ,  "); Serial.println(value);
  if (control == 100) { //if control = 100, then that's because the DAW has paused the playback or resumed playback. We'll put them to sleep even if it's resuming since any note on will wake them up.
    goToSleep();    
    return;
  }
  value = abs(value - 64);
  vibratoSpeed = map(value, 10, 64, 30000, 6000);
  //Serial.print("  |  ");  Serial.println(vibratoSpeed);
  if (control == 10) {
    if (value > 10) {
      //If we indicated that we want to add vibrato, then assign channel to proper timer.
      //First check to see that the channel was not already assigned to a channel
      if (vchannel[0] == channel || vchannel[1] == channel || vchannel[2] == channel) {
        for (int i = 0; i < 3; i++) {
          if (vchannel[i] == channel) {
            if (modTimerOn[i]) {
              modTimer[i].update(vibratoSpeed);
            }
            else {
              switch (i) {
                case 0:
                  modTimer[i].begin(mod0, vibratoSpeed);
                  modTimerOn[i] = HIGH;
                  break;
                case 1:
                  modTimer[i].begin(mod1, vibratoSpeed);
                  modTimerOn[i] = HIGH;
                  break;
                case 2:
                  modTimer[i].begin(mod2, vibratoSpeed);
                  modTimerOn[i] = HIGH;
              }
            }
          }
        }
      }
      //If not already assigned to a channel, then assign via a round robin style timer picker. This will boot out the channel currently playing on it.
      else {
        vchannel[vchannelElmt] = channel;   // assigns vibrato to channel specified in event
        if (modTimerOn[vchannelElmt]) {
          modTimer[vchannelElmt].update(vibratoSpeed);
        }
        else {
          switch (vchannelElmt) {
            case 0:
              modTimer[vchannelElmt].begin(mod0, vibratoSpeed);
              modTimerOn[vchannelElmt] = HIGH;
              break;
            case 1:
              modTimer[vchannelElmt].begin(mod1, vibratoSpeed);
              modTimerOn[vchannelElmt] = HIGH;
              break;
            case 2:
              modTimer[vchannelElmt].begin(mod2, vibratoSpeed);
              modTimerOn[vchannelElmt] = HIGH;
              break;
          }
        }
        // Change vchannel element for next time we run out of Interval Timers
        vchannelElmt += 1;
        if (vchannelElmt >= 3) { //sizeof gives you the number of bytes for the whole array. Divide by bytes of one element in the array.
          vchannelElmt = 0;
        }
      }
    }
    //if value is less than or equal to 10, then vibrato is unwanted. Turn off the channel-corresponding timer.
    else {
      for (int i = 0; i < 3; i++) {
        if (vchannel[i] == channel) {
          Serial.println("modTimer should be off");
          modTimer[i].end();        //Turn the Timer off
          modTimerOn[i] = LOW;      //Signal that this Timer is not running
          vchannelElmt = i;         //Next channel to use vibrato can use this one
          stepperPeriod[vchannel[i]] = microPeriods[stepperNote[vchannel[i]]] / pitchShift[vchannel[i]];
        }
      }
    }
  }
}

void myPitchChange(byte channel, int pitch) {
  pitch = pitch / 100;
  pitchShift[channel] = pitchfactor[pitch + 81];
  stepperPeriod[channel] = microPeriods[stepperNote[channel]] / pitchShift[channel];
  //Serial.print(channel); Serial.print("  ,  "); Serial.print(pitch); Serial.print("  ,  "); Serial.println(stepperPeriod[channel]);
}


//Where flags turn into actions
void NoteProcessing() {
  /*For stepper motors, execute the following
    //if the current period is greater than zero (zero being not playing)
    //stepperTick is incremented by the resolution to keep track of time between ticks
    //if the current tick has passed the microperiod threshold for ticking for that channel
    //reset the current Tick to zero
    //If the position is at a bound, then reverse the direction, and set position to zero.
    //then proceed with a tick by flipping the logic on the step pin
  */
  for (int i = 2; i < 14; i++) { //for all the stepper channels
    if (stepperPeriod[i] > 0) {
      stepperTick[i] += RESOLUTION;
      //Serial.print("The stepperTick on pin ");Serial.print(pin);Serial.print(" is ");Serial.println(stepperTick[i]);
      if (stepperTick[i] >= stepperPeriod[i]) {
        togglePin(i, channel2drive[i], channel2drive[i] + 1);
        stepperTick[i] = 0;
      }
    }
  }
}

void mod0() {
  v0 += 1;
  if (v0 > 12) {
    v0 = 0;
  }
  stepperPeriod[vchannel[0]] = (microPeriods[stepperNote[vchannel[0]]] / (pitchShift[vchannel[0]] * vibrato[v0]));
  //Serial.print(stepperPeriod[vchannel[0]]); Serial.println("  ,  This is mod0");

}
void mod1() {
  v1 += 1;
  if (v1 > 12) {
    v1 = 0;
  }
  stepperPeriod[vchannel[1]] = (microPeriods[stepperNote[vchannel[1]]] / (pitchShift[vchannel[1]] * vibrato[v1]));
  //Serial.print(stepperPeriod[vchannel[1]]); Serial.println("  ,  This is mod1");
}
void mod2() {
  v2 += 1;
  if (v2 > 12) {
    v2 = 0;
  }
  stepperPeriod[vchannel[2]] = (microPeriods[stepperNote[vchannel[2]]] / (pitchShift[vchannel[2]] * vibrato[v2]));
  //Serial.print(stepperPeriod[vchannel[2]]); Serial.println("  ,  This is mod2");
}

void togglePin(byte channel, int  pin, int direction_pin) {
  stepperState[pin] = !stepperState[pin];
  digitalWrite(pin, stepperState[pin]);
  Position[channel]++;

  //Don't switch directions if MAX_POSITION<0
  if (MAX_POSITION[channel] == 0) return;
  // If stepper percussion, run until hit abs(MAX_POSITION), and then turn the note off and change direction
  if (MAX_POSITION[channel] < 0) {
    //    Serial.println("Percussion channel found");
    //    Serial.println(abs(MAX_POSITION[channel]));
    //    Serial.println(Position[channel]);
    if (Position[channel] >= abs(MAX_POSITION[channel])) {
      stepperState[direction_pin] = !stepperState[direction_pin];
      digitalWrite(direction_pin, stepperState[direction_pin]);
      Position[channel] = 0;
      stepperPeriod[channel] =  0;
      stepperNote[channel] = -1;
    }
    return;
  }
  //Switch directions if end has been reached
  if (Position[channel] >= MAX_POSITION[channel]) {
    stepperState[direction_pin] = !stepperState[direction_pin];
    digitalWrite(direction_pin, stepperState[direction_pin]);
    Position[channel] = 0;
    //Serial.println("DIRECTION CHANGE ");
  }

}

void setup() {
  //Stepper control
  byte pin;
  for (int i = 0; i < 16; i++) {
    pin = channel2drive[i];
    pinMode(pin, OUTPUT); //Step Pins
    pinMode(pin + 1, OUTPUT); // Direction Pins
    digitalWrite(pin, LOW); digitalWrite(pin + 1, LOW);
  }

  //DIRECTION SWITCHES

  pinMode(sc1sw1, INPUT);
  pinMode(sc1sw2, INPUT);
  pinMode(sc2sw1, INPUT);
  pinMode(sc2sw2, INPUT);
  
  //Sleep Control
  for (int i = 0; i<4; i++){
    pinMode(sleepPins[i], OUTPUT);
  }
  goToSleep();

  //dink
  pinMode(30, OUTPUT);
  digitalWrite(30, HIGH);
  //attachInterrupt(digitalPinToInterrupt(channel2drive[dinkChannel1]), dink1, RISING);
//  attachInterrupt(digitalPinToInterrupt(31), dink2, RISING);

  pinMode(0, OUTPUT);      //Logic Supply voltage. 0 is located conveniently next to ground.
  digitalWrite(0, HIGH);
  pinMode(12, OUTPUT);      //Logic Supply voltage. 12 is located conveniently next to 3.3V.
  digitalWrite(12, LOW);

  mainTimer.priority(1);
  mainTimer.begin(NoteProcessing, RESOLUTION);

  usbMIDI.setHandleNoteOff(myNoteOff);
  usbMIDI.setHandleNoteOn(myNoteOn);
  usbMIDI.setHandleControlChange(myControlChange);
  usbMIDI.setHandlePitchChange(myPitchChange);

  Serial.begin(9600);
}

void loop() {
  usbMIDI.read();

  if(digitalRead(sc1sw1)){
    digitalWrite(channel2drive[5]+1, LOW);
  }
  else if(digitalRead(sc1sw2)){
    digitalWrite(channel2drive[5]+1, HIGH);
  }

  if(digitalRead(sc2sw1)){
    digitalWrite(channel2drive[6]+1, HIGH);
  }
  else if(digitalRead(sc2sw2)){
    digitalWrite(channel2drive[6]+1, LOW);
  }
}

Xylophone

Other stepper motor actuated instruments
Board - Teensy 3.2
USB Type - MIDI
CPU Speed - 96 MHz (overclocked)
Optimize - "Faster"
Teensy 3.2

Code:
#include <SPI.h>
#define LATCH_PIN 9

#define RESOLUTION 200 //Microsecond resolution for notes processing

#define MIN_MIDI_NOTE 55
#define MAX_MIDI_NOTE 79
#define NOTE_RANGE (MAX_MIDI_NOTE - MIN_MIDI_NOTE + 1)
#define OUTPUTS_PER_REG 8
#define NUM_REGS ((NOTE_RANGE+(OUTPUTS_PER_REG-1))/OUTPUTS_PER_REG)

//Device properties
boolean notesPlayable[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0
                           0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //1
                           0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //2
                           0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //3
                           0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, //4
                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //5
                           1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, //6
                           1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //7
                           0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //8
                           0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //9
                           0, 0, 0, 0, 0, 0, 0, 0
                          };            //10

boolean currentState[131];              // use to keep track of the state of a note
int currentDuration[131];
boolean pinState[131];                  // used to make square wave
byte currentRegister[NUM_REGS];         
   
int maxOn = 8000 / RESOLUTION;      // number of interrupt cycles to go until a solenoid turns back off --> time in microS/ RESOLUTION
int notesPlaying = 0;               // number of notes playing; helpful to determine if teensy should go through full noteprocessing code or not
bool noUpdate = true;

//Our timer interrupt process that is responsible for playing notes
IntervalTimer noteProcessor;


void setup() {

  pinMode(LATCH_PIN, OUTPUT);
  digitalWrite(LATCH_PIN, LOW);

  //Begin SPI
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE0);
  SPI.setClockDivider(SPI_CLOCK_DIV2);
  SPI.begin();

  //Begin Serial communication
  Serial.begin(9600);
  delay(1000);
  Serial.println("The serial works");

  //Setup our devices
  noteProcessor.end();
  usbMIDI.setHandleNoteOn(OnNoteOn);
  usbMIDI.setHandleNoteOff(OnNoteOff);
  noteProcessor.priority(255);
  noteProcessor.begin(NoteProcessing, RESOLUTION);

  writeAllBytes(currentRegister, NUM_REGS); // send the bytes to the shift registers
}

void loop() {
  usbMIDI.read();
}

void OnNoteOn(byte channel, byte note, byte velocity) {
  if(velocity == 0){
    currentState[note] = 0;
  }
  if ((notesPlayable[note]) & (currentState[note] != true)) {
    currentState[note] = true;
    notesPlaying += 1;
    noUpdate = false;
    Serial.print("Playing note "); Serial.println(note);
  }
}

void OnNoteOff(byte channel, byte note, byte velocity) {
  if (notesPlayable[note]) {
    currentState[note] =  false;
    currentDuration[note] = 0;
    notesPlaying -= 1;
    Serial.print("ending note "); Serial.println(note);
  }
}

void NoteProcessing() {
  for (int note = MIN_MIDI_NOTE; note <= MAX_MIDI_NOTE; note += 1) { // This block of code makes it so that the solenoid deploys at full 24V for maxOn duration
    if (currentState[note]){
      currentDuration[note] += 1;
      if (currentDuration[note] > maxOn) {
        pinState[note] = false;
        currentState[note] =  false;
        currentDuration[note] = 0;
      }
    }
  }

//  if (notesPlaying < 1) noUpdate = true;
  
//  if (noUpdate == true) {}
//  else {
    for (int i = 0; i < NUM_REGS; i++) {     // make up the bytes to be sent to the shift registers
      currentRegister[i] = calculateRegisterValue(i);
    }
      writeAllBytes(currentRegister, NUM_REGS); // send the bytes to the shift registers
//  }
}

byte calculateRegisterValue(int registerNum) {
  int noteStart = (registerNum * OUTPUTS_PER_REG) + MIN_MIDI_NOTE;
  byte value = 0;

  for (int i = 0; i < OUTPUTS_PER_REG; i++) {
    int note = noteStart + i;
    bitWrite(value, i, currentState[note]);
  }
  return value;
}

void latch() {
  digitalWrite(LATCH_PIN, HIGH);
  digitalWrite(LATCH_PIN, LOW);
}

void writeAllBytes(byte bytes[], int size) {
  for (int i = size - 1; i >= 0; i--) SPI.transfer(bytes[i]);
  latch();
}
 
I have zero experience with FL Studio but here are a fews suggestions to try to narrow down the source of the problem.
First, remove the usb hub from your setup. Does the problem still occur?
If so, only connect a single teensy. Does the problem still occur with every single teensy or not?
Based on the last answer we can decide on the next course of action to take.
 
I think I solved my problem! Holy cow! Thank you for the nudge in the right direction! It helped me find what I think was the problem.

So, I accidentally forgot to mention some other code that goes into these Teensy MIDI devices name.c (bottom of the page). Turns out that in my laziness when I first uploaded my code, I only named 2 out of the 3 Teensy's. Apparently that caused a problem. I had noticed for a long while now that the name of one of the named controllers was also given to the unnamed controller. Actually if I remember correctly, I think sometimes one name would even take place of both the unnamed and the other named one. For instance, my Options>MIDI Settings>Output would read like so:

Microsoft MIDI Mapper
Microsoft GS Wavetable Synth
MOPPY
MOPPY
Glockenspiel

OR

Microsoft MIDI Mapper
Microsoft GS Wavetable Synth
MOPPY
MOPPY
MOPPY

I think that this caused some issues somewhere on FL Studio's end. To me though, it seemingly worked perfectly. I think I was dumbstruck enough that my code was all working that I just ignored the sometimes spotty playback.

I have now corrected this by naming the third unnamed teensy. My Output devices now read out all the instruments as they were originally named. I have played Hotel California three times in a loop without issue, so I hesitantly say that this issue has been resolved. If I encounter the issue again, I will definitely post back.
 
I think I solved my problem! Holy cow! Thank you for the nudge in the right direction! It helped me find what I think was the problem.

So, I accidentally forgot to mention some other code that goes into these Teensy MIDI devices name.c (bottom of the page). Turns out that in my laziness when I first uploaded my code, I only named 2 out of the 3 Teensy's. Apparently that caused a problem. I had noticed for a long while now that the name of one of the named controllers was also given to the unnamed controller. Actually if I remember correctly, I think sometimes one name would even take place of both the unnamed and the other named one. For instance, my Options>MIDI Settings>Output would read like so:

Microsoft MIDI Mapper
Microsoft GS Wavetable Synth
MOPPY
MOPPY
Glockenspiel

OR

Microsoft MIDI Mapper
Microsoft GS Wavetable Synth
MOPPY
MOPPY
MOPPY

I think that this caused some issues somewhere on FL Studio's end. To me though, it seemingly worked perfectly. I think I was dumbstruck enough that my code was all working that I just ignored the sometimes spotty playback.

I have now corrected this by naming the third unnamed teensy. My Output devices now read out all the instruments as they were originally named. I have played Hotel California three times in a loop without issue, so I hesitantly say that this issue has been resolved. If I encounter the issue again, I will definitely post back.


Hello. sorry I am having this same issue please could you explain more where this code goes and how a noob like me could solve this problem. thank you
 
Until someone that understands the arcane details posts the full answer; i believe it relates to the device name string, the vender ID and product ID settings.

See "Customizing the MIDI Name" near the bottom of USB MIDI page for how to change the name
https://www.pjrc.com/teensy/td_midi.html


My guess...
FL gets confused when the names are the same but the product ID is not.... from using different Teensys.
 
Until someone that understands the arcane details posts the full answer; i believe it relates to the device name string, the vender ID and product ID settings.

See "Customizing the MIDI Name" near the bottom of USB MIDI page for how to change the name
https://www.pjrc.com/teensy/td_midi.html


My guess...
FL gets confused when the names are the same but the product ID is not.... from using different Teensys.

thank you for the reply
 
I'm using Win7 and a bunch of Midi gear and had issues with USB Midi ports disappearing randomly. After much head scratching, found a thread relating to issues with some Windows PC USB3 host hardware. In my case the motherboard USB3 host was a Via which is probably the worst offender. A known driver issue.

That same rather lengthy thread spoke of the author updating the MoBo firmware, disabling the USB3 host, reinstalling OS and updates then getting everything working on USB2 host/s and testing.

Then one by one tried a range of Pcie USB3 hosts and discovered that this was the trigger for fails - even if NO devices were physically plugged into said USB3 host. Not all USB3 host controllers work in regard to Windows and Midi EXCEPT the NEC chip variety.

Needless to say, I got one, a Sunix USB4300NS PCI Express 4 Port USB 3.0 Card featuring a Renesas (NEC) uPD720201 controller, and after more time in the Re-format - update rabbithole, the final clincher was the Motherboard Bios update as although USB3 worked reliably for Mass Storage at that point, USB Midi still fell over.

Have had no issues since.

My suggestion would be to look closely at your PC's USB3 host hardware. If its VIA, bin it. If it's not NEC, treat with suspicion. If you don't have enough USB ports, start by adding USB2 host, preferably NEC. Test and if all is ok, then think of adding an NEC based USB3 host.

Hope this is helpful.
 
Status
Not open for further replies.
Back
Top