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

Thread: Transpose / Offset incoming MIDI notes down an octave?

  1. #1

    Transpose / Offset incoming MIDI notes down an octave?

    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.

  2. #2
    Senior Member oddson's Avatar
    Join Date
    Feb 2013
    Location
    Isle in the Salish Sea
    Posts
    1,391
    Differences between Serial & other transports
    Software Thru is enabled by default on Serial, but not on other transports.
    https://github.com/FortySevenEffects...o_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.
    Last edited by oddson; 12-05-2020 at 07:00 PM.

  3. #3
    Senior Member oddson's Avatar
    Join Date
    Feb 2013
    Location
    Isle in the Salish Sea
    Posts
    1,391
    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)

  4. #4
    Senior Member
    Join Date
    Apr 2020
    Location
    DFW area in Texas
    Posts
    196
    @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

  5. #5
    Quote Originally Posted by oddson View Post
    https://github.com/FortySevenEffects...o_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...rary/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 =)

  6. #6
    Quote Originally Posted by kd5rxt-mark View Post
    @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!

  7. #7
    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 by mmryspace; 12-08-2020 at 03:23 PM. Reason: more detail

  8. #8
    Senior Member oddson's Avatar
    Join Date
    Feb 2013
    Location
    Isle in the Salish Sea
    Posts
    1,391
    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?

  9. #9
    Quote Originally Posted by oddson View Post
    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..

  10. #10
    Senior Member oddson's Avatar
    Join Date
    Feb 2013
    Location
    Isle in the Salish Sea
    Posts
    1,391
    I think you are missing the setHandleStart() in setup so that handleStart is called when an start message is recieved.
    https://fortyseveneffects.github.io/...cea8995252e858

    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.

  11. #11
    Quote Originally Posted by oddson View Post
    I think you are missing the setHandleStart() in setup so that handleStart is called when an start message is recieved.
    https://fortyseveneffects.github.io/...cea8995252e858

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

  12. #12
    Deleted User
    Guest
    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.

  13. #13
    Quote Originally Posted by Deleted User View Post
    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.

  14. #14
    Deleted User
    Guest
    Ah, sorry. Thank you for pointing that out.

  15. #15
    Senior Member oddson's Avatar
    Join Date
    Feb 2013
    Location
    Isle in the Salish Sea
    Posts
    1,391
    Quote Originally Posted by mmryspace View Post
    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.

  16. #16
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,546
    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

  17. #17
    Quote Originally Posted by oddson View Post
    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

  18. #18
    Quote Originally Posted by el_supremo View Post
    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 by mmryspace; 12-11-2020 at 07:10 PM. Reason: more detail

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

  20. #20
    Senior Member oddson's Avatar
    Join Date
    Feb 2013
    Location
    Isle in the Salish Sea
    Posts
    1,391
    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

  21. #21
    Quote Originally Posted by oddson View Post
    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

Posting Permissions

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