Transpose / Offset incoming MIDI notes down an octave?

Status
Not open for further replies.

mmryspace

Active member
Hi, I am using the following example to experiment with transposing incoming MIDI notes with a Teensy LC, using a hardware MIDI circuit:

Code:
//Arduino for Musicians
//Listing 5.8: MIDI Library input callback

#include <MIDI.h>

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

const byte transposition = -12;  //transpose all input down 12 semitones

//Define the callback functions. Parameters and return value must match the
//values listed in the MIDI library:

void myHandleNoteOn(byte channel, byte note, byte velocity)
{
    MIDI.sendNoteOn(note + transposition, velocity, channel);
}

void myHandleNoteOff(byte channel, byte note, byte velocity)
{
    MIDI.sendNoteOff(note + transposition, velocity, channel);
}

void setup() 
{
    //Connect the callback functions to the MIDI library
    MIDI.setHandleNoteOn(myHandleNoteOn);
    MIDI.setHandleNoteOff(myHandleNoteOff);
    
    MIDI.begin(MIDI_CHANNEL_OMNI);   // Listen on all channels
}

void loop() 
{
    //Call MIDI.read(). MIDI class will automatically call callback
    //functions as needed.
    MIDI.read();
}

The above sketch works as expected where the incoming MIDI note C3 (48) plays an additional C2 (36) . So the note is effectively "doubled" C2/C3.

What I am trying to accomplish is have the sketch play only the lower octave and struggling to come up with a way to "filter" out the base incoming note and have only the lower octave play...

Would I need to do something to the effect of "zero-ing" out the velocity of the base note (C3 for example) or is there another suggested approach?

Still learning the basics here so any help or hints to a solution is greatly appreciated.
 
Using filtering to pass all but the three-byte stuff appears to pass everything else so you'd only need to handle the basic messages from the controller and in the process alter the the note values on note on/off messages.

But I think the slick way would be to alter the library itself at this switch: https://fortyseveneffects.github.io/arduino_midi_library/a00005_source.html#l01413

I'm not sure I'm up to it... you'd need set up the filter condition to trigger based on type (so it's likely simpler to resend what you don't process with similar logic)
 
@mmryspace:

Maybe you could try just sending a "NoteOff" message for each of the base notes, in addition to the "NoteOn" message that you are sending for each of the transposed notes.

Good luck & have fun !!

Mark J Culross
KD5RXT
 
https://github.com/FortySevenEffects/arduino_midi_library

I think you call turnThruOff() during setup.

But then you have to pass all the other stuff manually if you want the other MIDI to pass unaffected.

Ah yes! I forgot that thru is enabled by default - thank you!

Using filtering to pass all but the three-byte stuff appears to pass everything else so you'd only need to handle the basic messages from the controller and in the process alter the the note values on note on/off messages.

But I think the slick way would be to alter the library itself at this switch: https://fortyseveneffects.github.io/...ce.html#l01413

I'm not sure I'm up to it... you'd need set up the filter condition to trigger based on type (so it's likely simpler to resend what you don't process with similar logic)

It appears that this may actually be in the works for the lib at some point? https://github.com/FortySevenEffects/arduino_midi_library/issues/40

Thanks again for the pointers in the direction I need to head. Will have a go here and see what I can come up with and report back. Going to go with a more basic filtering approach commensurate with my skill level abilities =)
 
@mmryspace:

Maybe you could try just sending a "NoteOff" message for each of the base notes, in addition to the "NoteOn" message that you are sending for each of the transposed notes.

Good luck & have fun !!

Mark J Culross
KD5RXT

@kd5rxt-mark thanks for the tip - I hadn't thought about that method. I've got plenty to chew on and will report back when I have had a chance to muck around here. Thanks!
 
I think you call turnThruOff() during setup.

But then you have to pass all the other stuff manually if you want the other MIDI to pass unaffected.

Ok @oddson, I have a working example now where the transpose offset now works as expected. thank you!

In the process, I was experimenting and trying to add an accent pattern to the noteOn messages, where every X step gets a different velocity. I am not getting the expected results in the sketch below. I have the accent pattern set to 3 where it should be every third step but the accent begins on the 1st step of 16 steps. My novice coding is likely the culprit and could certainly use an extra pair of eyes to identify the issue...

These are steps 1-16 in sequential order:

10:15:39.481 From UM-ONE Note On 7 G3 127

10:15:39.838 From UM-ONE Note On 7 F4 100

10:15:40.195 From UM-ONE Note On 7 D3 100

10:15:40.552 From UM-ONE Note On 7 C5 127

10:15:40.909 From UM-ONE Note On 7 F4 100

10:15:41.266 From UM-ONE Note On 7 C3 100

10:15:41.623 From UM-ONE Note On 7 A♯2 127

10:15:41.980 From UM-ONE Note On 7 G3 100

10:15:42.337 From UM-ONE Note On 7 F5 100

10:15:42.694 From UM-ONE Note On 7 A♯3 127

10:15:43.051 From UM-ONE Note On 7 C5 100

10:15:43.408 From UM-ONE Note On 7 A4 100

10:15:43.765 From UM-ONE Note On 7 C4 127

10:15:44.122 From UM-ONE Note On 7 D♯2 100

10:15:44.479 From UM-ONE Note On 7 A♯3 100

10:15:44.836 From UM-ONE Note On 7 C3 127

Code:
#include <Arduino.h>
#include <MIDI.h>

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

byte velocityAdjust = 100; // min (0), default (100) max(127)
  
byte accentVelocity = 127;  // velocity value for accented steps
byte setAccentPattern = 3; // add an accent step every x step (there are a total of 16 steps we're working with).

byte accentPattern = 0; // NOTE:  on start message, we'll need accentPattern = 0; in order to reset accent pattern when there is a start message...

byte transposition = -12; // transpose all input down 12 semitones, where incoming MIDI notes have an offset

// MIDI library functions
// because MIDI Thru is turned off, only the data associated with these functions shall pass when these functions are called

void handleClock()
{

}

void handleStart()
{
    accentPattern = 0; // reset accent pattern
}

void handleStop()
{
}

void handleNoteOn(byte channel, byte note, byte velocity)
{
    velocity = velocityAdjust;

    // this isn't quite working as expected yet.
    if (setAccentPattern > 0)
    { //if > 0 we want accents applied
        if (accentPattern == 0)
            velocity = accentVelocity; // we want it on this step
        accentPattern++;
        accentPattern = accentPattern % setAccentPattern; // calculate alternating steps
    }

    MIDI.sendNoteOn(note + transposition, velocity, channel);
}

void handleNoteOff(byte channel, byte note, byte velocity)
{
    MIDI.sendNoteOff(note + transposition, velocity, channel);
}

// initialize
void setup()
{
    //Connect the callback functions to the MIDI library
    MIDI.setHandleNoteOn(handleNoteOn);
    MIDI.setHandleNoteOff(handleNoteOff);
    MIDI.setHandleClock(handleClock);
    MIDI.setHandleStart(handleStart);
    MIDI.setHandleStop(handleStop);

    MIDI.begin(MIDI_CHANNEL_OMNI); // Listen on all channels
    MIDI.turnThruOff();            // software Thru is enabled by default on Serial so we need to disable.
}

// the main loop
void loop()
{
    //Call MIDI.read(). MIDI class will automatically call callback
    //functions as needed.
    MIDI.read();
}
 
Last edited:
Not sure I understand the problem... you want the accent on the third pulse?

Code:
        if (accentPattern == 0)
            velocity = accentVelocity; // we want it on this step
        accentPattern++;
But your test is looking for the 1st (index zero!) ... If you want that third then (accentPattern == 2) would tell it to bump the velocity on the third step.

Am I missing something?
 
Not sure I understand the problem... you want the accent on the third pulse?

Code:
        if (accentPattern == 0)
            velocity = accentVelocity; // we want it on this step
        accentPattern++;
But your test is looking for the 1st (index zero!) ... If you want that third then (accentPattern == 2) would tell it to bump the velocity on the third step.

Am I missing something?

Hey and thanks for the reply @oddson and no you're not missing anything - absolutely correct on that portion. However, my goal is to have the

Code:
byte setAccentPattern = 3; // add an accent step every x step (there are a total of 16 steps we're working with).

be what determines the step so I can set it to step 2, 5, 7 etc. The test I was attempting to set up was in relation to when a start message is received:

Code:
void handleStart()
{
    accentPattern = 0; // reset accent pattern
}

So any time there is a start message sent, it resets the accent pattern in the 16 steps and then every x step the accent happens.

This is most likely my novice attempt working against me..
 
I think you are missing the setHandleStart() in setup so that handleStart is called when an start message is recieved.
https://fortyseveneffects.github.io...a00039.html#a6aa08e1f81d063d78ecea8995252e858

This is covered better on the USB MIDI page under "Receiving Messages with Read & Callback Functions"
https://www.pjrc.com/teensy/td_midi.html

I believe the same list of handlers is available.

Hmmmn... ok, I was thinking I wasn't using the proper call back and handler but unless I am mistaken, the correct start handler is there in set up?


Code:
#include <Arduino.h>
#include <MIDI.h>

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

byte velocityAdjust = 100; // min (0), default (100) max(127)
  
byte accentVelocity = 127;  // velocity value for accented steps
byte setAccentPattern = 3; // add an accent step every x step (there are a total of 16 steps we're working with).

byte accentPattern = 0; // NOTE:  on start message, we'll need accentPattern = 0; in order to reset accent pattern when there is a start message...

byte transposition = -12; // transpose all input down 12 semitones, where incoming MIDI notes have an offset

// MIDI library functions
// because MIDI Thru is turned off, only the data associated with these functions shall pass when these functions are called

void handleClock()
{

}

void handleStart()
{
    accentPattern = 0; // reset accent pattern
}

void handleStop()
{
}

void handleNoteOn(byte channel, byte note, byte velocity)
{
    velocity = velocityAdjust;

    // this isn't quite working as expected yet.
    if (setAccentPattern > 0)
    { //if > 0 we want accents applied
        if (accentPattern == 0)
            velocity = accentVelocity; // we want it on this step
        accentPattern++;
        accentPattern = accentPattern % setAccentPattern; // calculate alternating steps
    }

    MIDI.sendNoteOn(note + transposition, velocity, channel);
}

void handleNoteOff(byte channel, byte note, byte velocity)
{
    MIDI.sendNoteOff(note + transposition, velocity, channel);
}

// initialize
void setup()
{
    //Connect the callback functions to the MIDI library
    MIDI.setHandleNoteOn(handleNoteOn);
    MIDI.setHandleNoteOff(handleNoteOff);

    MIDI.setHandleClock(handleClock);

    MIDI.setHandleStart(handleStart);

    MIDI.setHandleStop(handleStop);

    MIDI.begin(MIDI_CHANNEL_OMNI); // Listen on all channels

    MIDI.turnThruOff();            // software Thru is enabled by default on Serial so we need to disable.
}

// the main loop
void loop()
{
    //Call MIDI.read(). MIDI class will automatically call callback
    //functions as needed.
    MIDI.read();
}
 
Could it be your MIDI source (I guess it is a keyboard), plays it's own note as well as the incoming note? If you want to get rid of the played note and sound only the transposed note, the keyboard must be set to something like "key send MIDI only", or the sound source to something like "MIDI in only". The manual gives information on how to do this. Some keyboard sound module always listens to the keybed, so this connection can not be cut by a setting on these.
 
Could it be your MIDI source (I guess it is a keyboard), plays it's own note as well as the incoming note? If you want to get rid of the played note and sound only the transposed note, the keyboard must be set to something like "key send MIDI only", or the sound source to something like "MIDI in only". The manual gives information on how to do this. Some keyboard sound module always listens to the keybed, so this connection can not be cut by a setting on these.

Are you referring to the note transposition? If so I've got that sorted in #7 Thanks!

Moved on to another issue related to creating an accent pattern, also in #7 above.
 
Hmmmn... ok, I was thinking I wasn't using the proper call back and handler but unless I am mistaken, the correct start handler is there in set up?
My bad... lots wrong with the theory beside being incorrect. It would not compile, and it would not explain the result....

doesn't this really imply it's resetting because it's getting a start message that is stopping it from continuing uninterrupted?

If you add a print message there you'll be able to confirm.

Or am I still not getting what you're saying.
 
You can make the code handle any value of setAccentPattern by changing this:
Code:
        if (accentPattern == 0)
            velocity = accentVelocity; // we want it on this step
        accentPattern++;
        accentPattern = accentPattern % setAccentPattern; // calculate alternating steps
to this:
Code:
        accentPattern++;
        if (accentPattern == setAccentPattern) {
            velocity = accentVelocity; // we want it on this step
            accentPattern = 0;
        }

Pete
 
My bad... lots wrong with the theory beside being incorrect. It would not compile, and it would not explain the result....

doesn't this really imply it's resetting because it's getting a start message that is stopping it from continuing uninterrupted?

If you add a print message there you'll be able to confirm.

Or am I still not getting what you're saying.

No worries @oddson, I appreciate you supporting my mis-steps in trying to understand what is going on here. When I print message, it does appear to be firing but I dont think I need to have the setAccentPattern value attached to a start message as @el_supremo has offered the solution in #16

You can make the code handle any value of setAccentPattern by changing this:

Code:
if (accentPattern == 0)
            velocity = accentVelocity; // we want it on this step
        accentPattern++;
        accentPattern = accentPattern % setAccentPattern; // calculate alternating steps

to this:

Code:
accentPattern++;
        if (accentPattern == setAccentPattern) {
            velocity = accentVelocity; // we want it on this step
            accentPattern = 0;
        }

Pete
 
You can make the code handle any value of setAccentPattern by changing this:
Code:
        if (accentPattern == 0)
            velocity = accentVelocity; // we want it on this step
        accentPattern++;
        accentPattern = accentPattern % setAccentPattern; // calculate alternating steps
to this:
Code:
        accentPattern++;
        if (accentPattern == setAccentPattern) {
            velocity = accentVelocity; // we want it on this step
            accentPattern = 0;
        }

Pete

Thank you Pete, that resolved the issue I was having! Very much appreciated. I erroneously thought I would need to have a MIDI start message to handle the setAccentPattern value. Unless you or anyone here can see where I'd run into problems, start message or not, the accent pattern is only activated when there is a note On message so a start message condition seems unnecessary...

UPDATE, however... now that I am thinking about it. I do need that "reset" of the accent pattern with a start message. Lets say I am advancing the sequence manually by pressing each individual step to test out or adjust a note on a step. I could be anywhere in the accent pattern cycle when I press the start button and send a start message.

@oddson I am not sure my print message test was effective. In the serial monitor, it prints "start" when handleStart is called. However, in my MIDI monitor app on my computer, no start byte data is registering at all. When I re-enable Thru, the start, stop, and clock bytes are registered...
 
Last edited:
Ok here's what I came up with using @el_supremo code that looks to be working (resets the accent pattern with a start message):

Code:
#include <Arduino.h>
#include <MIDI.h>

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

byte velocityAdjust = 100; // increases velocity level: min (0), default (100) max(127)

/*
 * ACCENTS & ACCENT PATTERNS - In order to have the accPattern every time the sequence starts,
 * we need to handle clock, start, stop messages. e.g. onStart: accPat=0; // reset accent pattern
*/

#define MIDI_START 0xFA

byte accentVelocity = 127;      // velocity value for accented steps: min (0), default (127), max (127)
byte setAccentPattern = 8; // add an accent step every x step: min (0), default (0), max (16)
byte accentPattern = 0; // on start messages, we'll need accentPattern = 0; in order to reset accent pattern when there is a start message...

byte transposition = -12; // transpose all input down 12 semitones, where incoming MIDI notes have an offset


//Define the callback functions. Parameters and return value must match the
//values listed in the MIDI library:
//we can access note data via MIDI.getData1, i.e. data byte 1 --> note = MIDI.getData1();
//we can access velocity data via MIDI.getData1, i.e. data byte 2 --> velocity = MIDI.getData2();

// MIDI library functions
// because MIDI Thru is turned off, only the data associated with these functions shall pass when these functions are called

void handleStart()
{
  if (MIDI_START)
  {
    accentPattern = 0; // reset accent pattern
 
  }
}

void handleNoteOn(byte channel, byte note, byte velocity)
{
    velocity = velocityAdjust;

    if (setAccentPattern > 0)
    { //if > 0 we want accents applied
        
        accentPattern++;
        if (accentPattern == setAccentPattern) {
            velocity = accentVelocity; // we want it on this step
            accentPattern = 0;
        }
    }

    MIDI.sendNoteOn(note + transposition, velocity, channel);
}

void handleNoteOff(byte channel, byte note, byte velocity)
{
    MIDI.sendNoteOff(note + transposition, velocity, channel);
}

// initialize
void setup()
{
    //Connect the callback functions to the MIDI library
    MIDI.setHandleNoteOn(handleNoteOn);
    MIDI.setHandleNoteOff(handleNoteOff);
    
    MIDI.setHandleStart(handleStart);
    

    MIDI.begin(MIDI_CHANNEL_OMNI); // Listen on all channels
    MIDI.turnThruOff();            // software Thru is enabled by default on Serial so we need to disable.
}

// the main loop
void loop()
{
    //Call MIDI.read(). MIDI class will automatically call callback
    //functions as needed.
    MIDI.read();
}
 
I get why Pete's code it preferable but it looks functionally equivalent to me... looked to me like your modulo math should do the same thing in resetting on reaching the inter-accent count (ie -- every three steps)

I don't think it matters if you have what you need but I don't think I ever really understood what was going on here :S
 
I get why Pete's code it preferable but it looks functionally equivalent to me... looked to me like your modulo math should do the same thing in resetting on reaching the inter-accent count (ie -- every three steps)

I don't think it matters if you have what you need but I don't think I ever really understood what was going on here :S

No worries - I really appreciate the help! I barely understand what I am doing here as I go along and I have the physical hardware in front of me so you are totally fine! I'm not sure why the modulo version won't work either. I just tried it again and still same output as shown in MIDI monitor in #7 even with the added start condition as above in #19
 
Status
Not open for further replies.
Back
Top