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

Thread: MIDI note repeat code not working and could use some guidance

  1. #1

    MIDI note repeat code not working and could use some guidance

    Hi Everyone,

    I am trying to write a MIDI note repeat sketch and I am a bit stuck and trying to figure out why it isn't working and how to move forward. Here is a short demo of the idea using Logic Pro X MIDI note repeat effect for reference: https://youtu.be/kAuJ-MEJQCQ?t=135

    I am using a Teensy LC and I’ve built a basic DIN MIDI input output circuit. There are no issues with the circuit, it is the code that is the issue at this time as I am still very much learning.

    The idea of the sketch is that at X amount of clock tick intervals, X amount of notes are repeated.

    So for example: I want a dotted 16th note repeat to play 3 times each time a note on message is sent.

    In my sketch here, I've hard coded the note repeat tick and the note repeat amount for now, but eventually, I'd like to use a rotary encoder w/ push button to select different values...

    I am trying to use an array to keep track of notes so I can “note off” the correct notes.
    When I serial print out the repeated note ons, I see them in the serial monitor but I do not hear their output, nor are they registered in a MIDI monitor.

    In short: The initial note ons are working, but I cannot seem to connect the repeated notes to those original note on messages.

    Any guidance and support is greatly appreciated! Thank you in advance

    Code:
    #include <Arduino.h>
    
    #include <MIDI.h>
    
    /*
    
      (d):  dotted time, one and half note length
      (tr): triplet time, two thirds of duration
    
      0:  32th notes   - 3 ticks
      1:  16th (tr)    - 4 ticks
      2:  16th         - 6 ticks
      3:  8th (tr)     - 8 ticks
      4:  16th (d)     - 9 ticks
      5:  8th          - 12 ticks
      6:  quarter (tr) - 16 ticks
      7:  8th (d)      - 18 ticks
      8:  quarter      - 24 ticks (default PPQN is 24 PPQN or ticks)
      9:  half (tr)    - 32 ticks
      10: quarter (d)  - 36 ticks
      11: half         - 48 ticks
    
    */
    
    
    byte dm_type, dm_note, dm_velocity, dm_channel_received, dm_data1, dm_data2, dm_cc_num, dm_cc_val; // DIN MIDI data
    byte dm_channel_select = 0; //0 to receive any DIN MIDI channel
    
    unsigned long clock_ticks;
    unsigned long old_clock_ticks;
    unsigned long note_repeat_tick;
    
    byte note_repeat_amount;
    
    long current_time;
    long prev_time;
    
    /*
    
      Use arrays to keep track of what notes we've received and what note we've output
      so we can noteoff the correct note(s)
    
      a define is not a variable and can be used in the declaration section to set array sizes
      for example, 6 means it can track 6 notes at once. We can easily increase it but this makes it easier to see whats happening in the printout
    
    */
    
    #define note_array_len  256 // plenty of room here for notes :)
    int notes_received[note_array_len];
    int notes_sent[note_array_len];
    int note_index;
    int while_count;
    
    MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);
    
    void setup() {
    
      MIDI.begin();
      MIDI.turnThruOff(); // software Thru is enabled by default on Serial so we need to disable.
    
    }
    
    void loop() {
    
      current_time = millis();
    
      //DIN MIDI input
      if (MIDI.read()) {   // Is there a MIDI message incoming ?
    
        dm_type = MIDI.getType();
        dm_channel_received = MIDI.getChannel();
    
        if (0) { //change to 1 to print the channel
    
          Serial.print("dm channel received: ");
          Serial.print(dm_channel_received);
    
        }
    
        // only do all of this if it's the channel we want
        // "||" is "logical or", allowing us to go in when dm_channel_select is 0 aka Omni
        if (dm_channel_received == dm_channel_select || dm_channel_select == 0) {
    
          if (dm_type == midi::NoteOn) {
    
            dm_note = MIDI.getData1();
            dm_velocity = MIDI.getData2();
    
            // set these when you get the new note
            note_repeat_tick = 4; // when do we want to send the repeated note on?
            note_repeat_amount = 3; // how many times do we want to repeat the note
    
            if (dm_velocity > 0) { // is it a note on message?
    
              // while keeps doing the code inside the {} untill it's true
              // we want to skip the slots that are filled but we also need a way of exiting if all slots are filled
              while_count = 0;
              while (notes_received[note_index] != 0) {
    
                Serial.print("note_index ");
                Serial.println(note_index);
    
                note_index++;
                if (note_index > note_array_len - 1) {
    
                  note_index = 0;
                  while_count++;
    
                  if (while_count > 1) {
                    //we've gone through the whole array at least once and theres no place for the note
                    // if this happens we just need to increases note_array_len
                    Serial.print(" ! TOO MANY NOTES !");
                    break; //exit the while
    
                  }
                }
              }
    
              // access the array so we can track both the incoming notes and what notes are being sent on as note on data
              notes_received[note_index] = dm_note;
              notes_sent[note_index] = dm_note;
    
              //send the first note on
              MIDI.sendNoteOn(dm_note, dm_velocity, dm_channel_received);
    
              Serial.print("Note On:");
              Serial.print(dm_note);
              Serial.print("  velocity: ");
              Serial.println(dm_velocity);
    
              Serial.print("tick set: ");
              Serial.println(note_repeat_tick);
              Serial.print(" repeat set: ");
              Serial.print(note_repeat_amount);
              Serial.println();
    
            }
    
            if (dm_velocity == 0) { //some systems send velocity 0 as note off
    
              Serial.println("Note Off: ");
              Serial.println(dm_note);
    
            }
    
          }
    
          else if (dm_type == midi::NoteOff) { //else if can be used so we only have one outcome
    
            dm_note = MIDI.getData1();
    
            for (int i = 0; i < note_array_len; i++) { // this loop allows us to access the note array to note off the correct note(s)
    
              if (dm_note == notes_received[i]) {
    
                MIDI.sendNoteOff(notes_sent[i], dm_velocity, dm_channel_received);
                notes_received[i] = 0;
                notes_sent[i] = 0;
                break; //exit this for loop as we found what we want
    
              }
            }
    
            Serial.print("Note Off: ");
            Serial.println(dm_note);
    
            // Serial.print("  note repeat: ");
            // Serial.println(note_repeat_amount);
    
          }
    
          else if (dm_type == midi::ControlChange) {
    
            dm_cc_num = MIDI.getData1();
            dm_cc_val = MIDI.getData2();
    
            Serial.print("CC#: ");
            Serial.print(dm_cc_num);
            Serial.print("  value: ");
            Serial.println(dm_cc_val);
    
          }
    
          else if (dm_type == midi::Clock) {
    
    
            clock_ticks++; // count incoming clock ticks
    
            if (clock_ticks >= note_repeat_tick && note_repeat_amount > 0) { // condition for note repeat instance and note repeat amount
    
              MIDI.sendNoteOn(dm_note, dm_velocity, dm_channel_received); // send the note repeat data
              note_repeat_amount--; //reduce the repeat amount each time we play a note
              clock_ticks = 0; //reset this so it will take another X amount of ticks to play the next note
    
            }
    
            Serial.println();
            Serial.print("  note repeat: ");
            Serial.println(note_repeat_amount);
    
            Serial.print("note 1: ");
            Serial.println(dm_note);
            Serial.print(" note 2: ");
            Serial.println(dm_note);
            Serial.print("  note 3: ");
            Serial.println(dm_note);
            Serial.println();
    
          }
    
          else if (dm_type == midi::Start) {
    
            // clock_ticks = 0;
            Serial.println("  Starting");
    
          }
    
          else if (dm_type == midi::Continue) {
    
            // clock_ticks = old_clock_ticks;
            Serial.println("Continuing");
          }
    
          else if (dm_type == midi::Stop) {
    
            // old_clock_ticks = clock_ticks;
            Serial.println("  Stopping");
    
          }
    
          else if (dm_type == midi::ActiveSensing) {
    
            // Serial.println("ActiveSense");
    
          }
    
          //If it's not a note on or off or cc do this.
          // If there were just "ifs" being used above there could be a final else that would catch all the other message types
          // the other types are :
          /*
          midi::AfterTouchPoly
          midi::ProgramChange
          midi::AfterTouchChannel
          midi::PitchBend
          midi::SystemExclusive
          */
    
          else {
    
            dm_data1 = MIDI.getData1();
            dm_data2 = MIDI.getData2();
    
          }
        }
    
      }
    
      //DIN Send
      //There are lots of types of midi messages. Here are the most used ones. All of them are here https://www.pjrc.com/teensy/td_midi.html (just change them to MIDI instead of usb MIDI)
      /*
      MIDI.sendNoteOn(note, velocity, channel);
      MIDI.sendNoteOff(note, velocity, channel);
      MIDI.sendControlChange(control, value, channel);
      MIDI.sendAfterTouch(pressure, channel);
      MIDI.sendPitchBend(value, channel);
      */
    
    
        //   if (current_time - prev_time > 100) { // checking the note array buffer
        //
        //   prev_time = current_time;
        //
        //     Serial.print("received ");
        //
        //     for (int i = 0; i < note_array_len; i++) {
        //
        //     Serial.print(notes_received[i]);
        //     Serial.print(" ");
        //
        //     }
        //
        //     Serial.println();
        //     Serial.print("sent     ");
        //
        //     for (int i = 0; i < note_array_len; i++) {
        //
        //     Serial.print(notes_sent[i]);
        //     Serial.print(" ");
        //
        //     }
        //
        // }
    
    }

  2. #2
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,768
    Try initializing dm_channel_select to 1 (or whatever channel your keyboard uses). If your MIDI device sends Active Sense, for example, dm_channel_received will be set to zero while the code is still trying to send the repeated notes.
    My MIDI synth doesn't send a MIDI clock (AFAIK) so I tested your code by changing it to use millis() as a "clock".

    Pete

  3. #3
    Quote Originally Posted by el_supremo View Post
    Try initializing dm_channel_select to 1 (or whatever channel your keyboard uses). If your MIDI device sends Active Sense, for example, dm_channel_received will be set to zero while the code is still trying to send the repeated notes.
    My MIDI synth doesn't send a MIDI clock (AFAIK) so I tested your code by changing it to use millis() as a "clock".

    Pete
    Hi Pete,

    Many thanks for taking a look at the code here. To clarify: were you able to get the note repeats to work on your end?

    Also I realized in the above code, in the setup MIDI.begin(); will not pass any messages as a channel needs to be specified here (e.g MIDI_CHANNEL_OMNI, 1, 2,3, etc).

    I tried your suggestion of initializing dm_channel_select to the specific channel I am using and added the channel argument to MIDI.begin(); no luck: I still only hear the initial note ons and no note repeats.

    The sending device I am using is a sequencer and it does send the active sense message. My setup (which I forgot to mention): Korg SQ-1 sequencer DIN MIDI OUT > Teensy LC MIDI IN > TEENSY LC DIN MIDI OUT > USB MIDI INTERFACE
    Last edited by mmryspace; 09-17-2021 at 02:44 PM. Reason: clarity

  4. #4
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,768
    I did get note repeats working but only after I had modified the code quite a bit to use millis() as a "fudged" sort of clock source. My SY77 apparently will send a MIDI Clock signal but I haven't figured out how to make it do that yet - it's a long time since I used it routinely.
    Is your MIDI device sending a MIDI clock message? Your code won't even try to handle repeated notes if the clock isn't received. There also seems to be a problem that even if you do send the repeated note on, you don't follow that later on with a note off.
    Part of the problem of no repeats is that when you receive an Active Sense or Clock message, you set dm_channel_received to zero because there is no channel associated with Active Sense or Clock. But your code which does the repeated notes, sends the repeated notes to whatever dm_channel_received is set to, which is zero. My synth is set to send and receive on channel 1, so even if the repetition worked, the synth wouldn't respond to the note.

    Pete

  5. #5
    Quote Originally Posted by el_supremo View Post
    I did get note repeats working but only after I had modified the code quite a bit to use millis() as a "fudged" sort of clock source. My SY77 apparently will send a MIDI Clock signal but I haven't figured out how to make it do that yet - it's a long time since I used it routinely.
    Is your MIDI device sending a MIDI clock message? Your code won't even try to handle repeated notes if the clock isn't received. There also seems to be a problem that even if you do send the repeated note on, you don't follow that later on with a note off.
    Part of the problem of no repeats is that when you receive an Active Sense or Clock message, you set dm_channel_received to zero because there is no channel associated with Active Sense or Clock. But your code which does the repeated notes, sends the repeated notes to whatever dm_channel_received is set to, which is zero. My synth is set to send and receive on channel 1, so even if the repetition worked, the synth wouldn't respond to the note.

    Pete
    Thanks so much for the feedback Pete. My apologies as I've had some other projects going on - but I am definitely still working on this one. Will share the updated code as soon as I can =D

Posting Permissions

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