Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 3 of 3

Thread: "MIDI devices have been changed...refreshing"

  1. #1
    Junior Member
    Join Date
    Sep 2019
    Posts
    2

    "MIDI devices have been changed...refreshing"

    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();
    }

  2. #2
    Senior Member
    Join Date
    Nov 2017
    Location
    Belgium
    Posts
    104
    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.

  3. #3
    Junior Member
    Join Date
    Sep 2019
    Posts
    2
    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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •