-
Hi Everyone, I am trying to create a MIDI note repeater sketch similar to how the MID note repeat effect is implemented in in Logic Pro X here. The basic idea is at X amount of clock tick intervals, X amount of notes are repeated. Every time there is a note on message, a clock interval amount and note repeat amount are set. For example: I want a dotted 16th note repeat to play 3 times each time a note on message is sent The initial note ons are working, but I cannot seem to connect the repeated notes to those original note on messages. I am probably overlooking something really obvious here so any advice on how to proceed / debug would be greatly appreciated! #include <Arduino.h>
#include <MIDI.h>
const int led_pin2 = 22; // encoder led green
const int led_pin1 = 23; // encoder led red
byte dm_type, dm_note, dm_velocity, dm_channel_received, dm_data1, dm_data2, dm_cc_num, dm_cc_val; // DIN MIDI data (if we were using USB midi then replace dm with um for example...)
unsigned long clock_ticks, old_clock_ticks; // clock counter
unsigned long note_repeat_tick; // what clock interval do we want to have the note repeat ?
byte note_repeat_amount; // how many notes do we want to repeat ?
byte dm_repeat_note; // repeat note send data
byte dm_repeat_velocity; // repeat note send velocity data
/*
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
*/
// a define is not a variable and can be used in the declaration section to set array sizes
// 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 6 // plenty of room here for notes :)
int notes_received[note_array_len];
int notes_sent[note_array_len];
int note_index;
int while_count;
long current_time;
long previous_time;
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);
void setup() {
pinMode(led_pin2, OUTPUT);
pinMode(led_pin1, OUTPUT);
MIDI.begin(MIDI_CHANNEL_OMNI);
MIDI.turnThruOff();
}
void loop() {
current_time = millis();
//DIN MIDI input
if (MIDI.read()) { // Is there an incoming MIDI message ?
dm_type = MIDI.getType();
dm_channel_received = MIDI.getChannel();
dm_note = MIDI.getData1(); // MIDI note data to be sent with note on
dm_velocity = MIDI.getData2(); // MIDI note velocity data to be sent with note on
if (dm_type == midi::NoteOn) {
// digitalWrite(led_pin1, HIGH);
/*
(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
*/
// set these variables when we get a new note on
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 ?
// 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.println();
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.println();
Serial.print("NoteOn: ");
Serial.print(dm_note);
Serial.print(" | Velocity: ");
Serial.print(dm_velocity);
Serial.println();
//
// Serial.print("tick set: ");
// Serial.print(note_repeat_tick);
// Serial.print(" repeat set: ");
// Serial.print(note_repeat_amount);
// Serial.println();
}
if (dm_type == midi::NoteOff) {
// digitalWrite(led_pin1, LOW);
// digitalWrite(led_pin2, LOW);
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("NoteOff: ");
// Serial.print(dm_note);
// Serial.println();
}
if (dm_type == midi::Clock) {
clock_ticks++; // count incoming clock ticks
if (clock_ticks >= note_repeat_tick && note_repeat_amount > 0) { // test our note repeat tick interval & note repeat amount set in note on function
// digitalWrite(led_pin2, HIGH);
// 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.println();
Serial.print("repeated_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;
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
if (note_repeat_amount == 0) {
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 INERVAL RESET! ");
// Serial.println(clock_ticks);
}
Serial.println();
Serial.print("Repeated NoteOn: ");
Serial.print(dm_note);
Serial.print(" | Repeated NoteOn Velocity: ");
Serial.print(dm_velocity);
Serial.println();
// Serial.println();
// Serial.print(" NOTE REPEAT COUNTER ");
// Serial.println(note_repeat_amount);
}
// Serial.println();
// Serial.print("Tick Tock... ");
// Serial.println(clock_ticks);
// Serial.println();
}
}
// if (current_time - previous_time > 100) { // checking the note array buffer
//
// previous_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(" ");
//
// }
//
// }
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 11 replies
-
It looks like you have an extra behaviour compared to Logic's implementation, you want the notes to repeat a given amount of times, instead of indefinitely (until the corresponding NoteOff is received). This is how I'd go about implementing this:
You define a maximum supported number of notes by creating an array of Repeaters.
Example implementation (compiles, but untested): #include <MIDI.h>
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);
static const int sMaxNotes = 16;
class Repeater
{
public:
// This is the constructor: it initialises the member variables (see below) before `setup` runs.
inline Repeater()
: active(false)
, noteNumber(0)
, velocity(0)
, channel(1)
, numTicks(24)
, tickCount(0)
, numRepeats(3)
, repeatCount(0)
{
}
public:
// This is the function that is called when a NoteOn is received.
// it enables the Repeater for this note.
inline void setup(byte inNoteNumber,
byte inVelocity,
byte inChannel,
byte inNumTicks = 24,
byte inNumRepeats = 3)
{
noteNumber = inNoteNumber;
velocity = inVelocity;
channel = inChannel;
numTicks = inNumTicks;
numRepeats = inNumRepeats;
tickCount = 0;
repeatCount = 0;
active = true;
}
inline void disable()
{
active = false;
tickCount = 0;
repeatCount = 0;
}
public:
// We need to query a few things about those objects:
// (`const` means it's a read-only function)
inline bool isActive() const
{
return active;
}
inline bool hasNote(byte inNoteNumber) const
{
return noteNumber == inNoteNumber;
}
public:
// This is called when we receive Clock messages.
// It does the counting, logic and emitting of MIDI data.
inline void tick()
{
if (!active)
{
return;
}
tickCount++;
if (tickCount == numTicks)
{
tickCount = 0;
repeatCount++;
MIDI.sendNoteOff(noteNumber, velocity, channel); // Kill previous note
MIDI.sendNoteOn(noteNumber, velocity, channel); // And start it again
}
if (repeatCount == numRepeats)
{
disable();
}
}
// We store each Repeater's data (member variables) here:
private:
bool active;
// Note data
byte noteNumber;
byte velocity;
byte channel;
// Counters
byte numTicks; // How many ticks to count between notes
byte tickCount; // Where we're at
byte numRepeats; // How many times to repeat the note
byte repeatCount; // Where we're at
};
// Create N=sMaxNotes repeaters
Repeater repeaters[sMaxNotes];
// MIDI Callbacks, see https://github.com/FortySevenEffects/arduino_midi_library/wiki/Using-Callbacks
void handleNoteOn(byte channel, byte noteNumber, byte velocity)
{
for (Repeater& repeater : repeaters) // iterate over the repeaters
{
if (!repeater.isActive())
{
repeater.setup(noteNumber, velocity, channel);
break;
}
}
}
void handleNoteOff(byte channel, byte noteNumber, byte velocity)
{
for (Repeater& repeater : repeaters)
{
if (repeater.isActive() && repeater.hasNote(noteNumber))
{
repeater.disable();
MIDI.sendNoteOff(noteNumber, velocity, channel);
}
}
}
void handleClock()
{
for (Repeater& repeater : repeaters)
{
repeater.tick();
}
}
// --
void setup()
{
MIDI.begin();
MIDI.turnThruOff();
// Register the callbacks
MIDI.setHandleNoteOn(handleNoteOn);
MIDI.setHandleNoteOff(handleNoteOff);
MIDI.setHandleClock(handleClock);
}
void loop()
{
MIDI.read();
} Note: the above implementation has fixed time intervals and number of repetitions, but it could be connected to hardware controls or Control Change messages. It also does not deal with a variable "gate length" (duration of each note), which could be implemented with another counter (how many ticks before sending a NoteOff after the NoteOn has been emitted). |
Beta Was this translation helpful? Give feedback.
It looks like you have an extra behaviour compared to Logic's implementation, you want the notes to repeat a given amount of times, instead of indefinitely (until the corresponding NoteOff is received).
This is how I'd go about implementing this:
active
stateYou define a maximum supported number of notes by creating an array of Repeaters.
All Repeaters start in an inactive state.
When a NoteOn is received, f…