MIDI note repeat code not working and could use some guidance

Status
Not open for further replies.

mmryspace

Active member
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 :D

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

}
 
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
 
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:
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
 
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
 
Status
Not open for further replies.
Back
Top