Go Down

Topic: Chord memory function with basspedal and midi-shield (Read 928 times) previous topic - next topic

TheRebell

Hello,

I have an arduino mega with the sparkfun midi-shield and an organ basspedal. And a Led Backpanel 16x8, also a lcd screen

On the digital pins are the 13 keys connected. Now i want assign different chords on different keys, then go out ont the output of midi-shield!

p.ex. i tap on the G key of the pedalboard and go out with a GMinor, Major or seven.

When that' work, i want that the chord will be shown in the ledpanel  !

I have tried with the gizmo.ino from eclab but without success.

Please help me!!!
Thanks
Greetings from Belgium
Marco

Here is the code for normal key assignment wich works fine:

#define DEBOUNCE 1500

struct key
{
    int pin;
    int midiKey;
    int debounce;
    int keySent;
};

struct key keys[] =
{
  { 22, 25, 0, 0 },  // Db red
  { 24, 26, 0, 0 },  // D  red
  { 26, 27, 0, 0 },  // Eb orange
  { 28, 28, 0, 0 },  // E  orange
  { 30, 29, 0, 0 },  // F  yellow
  { 32, 30, 0, 0 },  // Gb green
  { 34, 31, 0, 0 },  // G  green
  { 36, 32, 0, 0 },  // Ab blue
  { 38, 33, 0, 0 },  // A  blue
  { 40, 34, 0, 0 },  // Bb violet
  { 42, 35, 0, 0 },  // B  violet
  { 44, 36, 0, 0 },  // C  brown
  { 48, 24, 0, 0 },  // C  brown
  { 0, 0, 0, 0 }     // end of list marker
};

int keyOffset = 0;
int keyVelocity = 100;

void setup() {
  // put your setup code here, to run once:
  for(int i = 0; keys.pin != 0; ++i)
  {
    pinMode(keys.pin, INPUT_PULLUP);
  }
  //start serial with midi baudrate 31250
  Serial.begin(31250);    
}

void Midi_Send(byte cmd, byte data1, byte data2)
{
  Serial.write(cmd);
  Serial.write(data1);
  Serial.write(data2);
}

void noteOn(int midiKey)
{
  Midi_Send(0x90, midiKey, keyVelocity);
}

void noteOff(int midiKey)
{
  Midi_Send(0x80, midiKey, keyVelocity);
}

void loop() {
  // put your main code here, to run repeatedly:
  byte byte1;
  byte byte2;
  byte byte3;
  int value;

  //*************** MIDI THRU ******************//
  if(Serial.available() > 0)
  {
    byte1 = Serial.read();
    byte2 = Serial.read();
    byte3 = Serial.read();

    Midi_Send(byte1, byte2, byte3);
  }

  // Look for bass pedal key events
  for(int i = 0; keys.pin != 0; ++i)
  {
    value = digitalRead(keys.pin);
    if(keys.debounce == 0) // Key has been off
    {
      if(value == LOW)        // Key is now on
      {
        noteOn(keys.midiKey + keyOffset);      // Send the MIDI note on message
        keys.keySent = keys.midiKey + keyOffset;
        keys.debounce = DEBOUNCE;  // Set the note off debounce counter
      }
    }
    else                      // Key has been on
    {
      if(value == HIGH)       // Key has gone off
      {
        if(--keys.debounce == 0) // If Key has remained off for DEBOUNCE scans,
          noteOff(keys.keySent); // In case the offset has changed, send MIDI off for the right note
      }
      else                    // Key has not gone off
        keys.debounce = DEBOUNCE;  // Reset debounce counter in case we got
                                      // a small number of key off scans
    }
  }
}

slipstick

How do you expect it to know whether to send Gmaj, Gm or G7?

But if you really want a pedal press to send a chord instead of a single note then basically you need to replace the noteOn() and noteOff functions with more complex versions that send several MIDI commands not just one. Since the notes in a chord are well structured it's not difficult to send e.g. for a minor chord key.midiKey, midiKey+3 and midiKey+7 etc.

You can sort out the display when you have that working.

Steve

TheRebell

#2
Aug 21, 2018, 11:01 pm Last Edit: Aug 22, 2018, 01:16 pm by TheRebell
Hi thanks for the quick reply!

p.ex I assign the c-key an C Major-chord, the a-key a A Minor, anotherone a 7-chord
That's only an example!
For a song different chords on different keys

That' it!

And on the midi-shield i can switch bank up and down for different songs (chord structures)


TheRebell

I want combine the Basspedal1.ino with
 This extract from Sean Luke' Gizmo Sketch:

Thru.cpp

Code: [Select]
#include "All.h"

#ifdef INCLUDE_THRU

void resetDistributionNotes()
    {
    memset(local.thru.distributionNotes, NO_NOTE, NUM_MIDI_CHANNELS);
    local.thru.currentDistributionChannelIndex = 0;
    }


void performThruNoteOff(uint8_t note, uint8_t velocity, uint8_t channel)
    {
    // NOTE DISTRIBUTION OVER MULTIPLE CHANNELS
    if (options.thruNumDistributionChannels > 0)
        {
        // turn off ALL instances of this note on ALL channels
        for(uint8_t i = 0; i <= options.thruNumDistributionChannels; i++)
            {
            if (local.thru.distributionNotes[i] == note)
                {
                uint8_t newchannel = (options.channelOut + i - 1) % NUM_MIDI_CHANNELS + 1;
                for(uint8_t i = 0; i <= options.thruExtraNotes; i++)            // do at least once
                    {
                    sendNoteOff(note, velocity, newchannel);
                    }
                local.thru.distributionNotes[i] = NO_NOTE;
                }
            }
        }
    else
        {
        // NOTE REPLICATION
        for(uint8_t i = 0; i <= options.thruExtraNotes; i++)            // do at least once
            {
            sendNoteOff(note, velocity, channel);
            }
        }
                
    // CHORD MEMORY
    for(uint8_t i = 1; i < options.thruChordMemorySize; i++)  // Yes, I see the *1*.  We aren't playing the bottom note a second time
        {
        uint8_t chordNote = options.thruChordMemory[i] - options.thruChordMemory[0] + note;  // can't overflow, it'll only go to 254 (127 + 127).
        if (chordNote <= 127)
            {
            sendNoteOff(chordNote, velocity, channel);
            }
        }
    }

void performThruNoteOn(uint8_t note, uint8_t velocity, uint8_t channel)
    {
    // NOTE DISTRIBUTION OVER MULTIPLE CHANNELS
    if (options.thruNumDistributionChannels > 0)
        {
        // revise the channel
        channel = (options.channelOut + local.thru.currentDistributionChannelIndex - 1) % NUM_MIDI_CHANNELS + 1;
                                                        
        // do I need to turn off a note?
        if (local.thru.distributionNotes[local.thru.currentDistributionChannelIndex] != NO_NOTE)
            {
            for(uint8_t i = 0; i <= options.thruExtraNotes; i++)            // do at least once
                {
                sendNoteOff(local.thru.distributionNotes[local.thru.currentDistributionChannelIndex], 127, channel);
                }
            }
        
        // store the note and update
        local.thru.distributionNotes[local.thru.currentDistributionChannelIndex] = note;
        local.thru.currentDistributionChannelIndex++;
        if (local.thru.currentDistributionChannelIndex > options.thruNumDistributionChannels )  // yes, it's > not >= because options.thruNumDistributionChannels starts at *1*
            local.thru.currentDistributionChannelIndex = 0;
        }

    // NOTE REPLICATION
    for(uint8_t i = 0; i <= options.thruExtraNotes; i++)            // do at least once
        {
        sendNoteOn(note, itemValue, channel);
        }
        
    // CHORD MEMORY
    for(uint8_t i = 1; i < options.thruChordMemorySize; i++)  // Yes, I see the *1*.  We aren't playing the bottom note a second time
        {
        uint8_t chordNote = options.thruChordMemory[i] - options.thruChordMemory[0] + note;  // can't overflow, it'll only go to 254 (127 + 127).
        if (chordNote <= 127)
            {
            sendNoteOn(chordNote, itemValue, channel);
            }
        }
    }
        
void performThruPolyAftertouch(uint8_t note, uint8_t velocity, uint8_t channel)
    {
    // We note here that although a NOTE ON can be filtered out,
    // its corresponding NOTE OFF will not, nor will any POLYPHONIC AFTERTOUCH.
    // I had considered storing the note on that was being filtered so
    // as to also filter out the later note off, but this has downsides,
    // namely if you have a bounce that gets filtered, but the note off hasn't
    // happened yet, then you get ANOTHER BOUNCE, it can't get filtered :-(

    // NOTE DISTRIBUTION OVER MULTIPLE CHANNELS
    if (options.thruNumDistributionChannels > 0)
        {
        // change ALL instances of this note on ALL channels
        for(uint8_t i = 0; i <= options.thruNumDistributionChannels; i++)
            {
            if (local.thru.distributionNotes[i] == note)
                {
                channel = (options.channelOut + i - 1) % NUM_MIDI_CHANNELS + 1;
                // We do NOT send extra notes with poly aftertouch because it consumes
                // so much buffer space that we will block on output and then start missing
                // incoming messages
                //for(uint8_t i = 0; i <= options.thruExtraNotes; i++)            // do at least once
                    {
                    sendPolyPressure(note, itemValue, channel);
                    }
                }
            }
        }
    else
        {
        // NOTE REPLICATION
        // We do NOT send extra notes with poly aftertouch because it consumes
        // so much buffer space that we will block on output and then start missing
        // incoming messages
        //for(uint8_t i = 0; i <= options.thruExtraNotes; i++)            // do at least once
            {
            sendPolyPressure(note, itemValue, channel);
            }
        }

    // CHORD MEMORY
    // We do NOT send extra notes with poly aftertouch because it consumes
    // so much buffer space that we will block on output and then start missing
    // incoming messages
    /*
      for(uint8_t i = 1; i < options.thruChordMemorySize; i++)  // Yes, I see the *1*.  We aren't playing the bottom note a second time
      {
      uint8_t chordNote = options.thruChordMemory[i] - options.thruChordMemory[0] + note;  // can't overflow, it'll only go to 254 (127 + 127).
      if (chordNote <= 127)
      sendPolyPressure(chordNote, itemValue, channel);
      }
    */
    }


...

TheRebell

Code: [Select]
...
void playThru()
{
    // here we check if it's time to submit a NOTE OFF
    if (local.thru.debounceState == DEBOUNCE_STATE_FIRST_NOTE_UP_IGNORED &&
        local.thru.debounceTime + ((uint32_t)options.thruDebounceMilliseconds) * 1000 <= currentTime)
        {
        performThruNoteOff(local.thru.debounceNote, 127, options.channelOut);
        local.thru.debounceState = DEBOUNCE_STATE_OFF;
        }
       
    if (!bypass && newItem && options.channelOut != CHANNEL_OFF &&
        (itemChannel == options.channelIn || options.channelIn == CHANNEL_OMNI || itemChannel == options.thruMergeChannelIn))
        {
        uint8_t channel = options.channelOut;
               
        if (itemType == MIDI_NOTE_ON)
            {
            if (options.thruDebounceMilliseconds == 0)          // if debounce is off, don't do any debounce stuff!
                {
                performThruNoteOn(itemNumber, itemValue, channel);
                }
            else if (itemNumber != local.thru.debounceNote ||
                local.thru.debounceState == DEBOUNCE_STATE_OFF ||
                    ((local.thru.debounceState == DEBOUNCE_STATE_FIRST_NOTE_UP_IGNORED) &&
                    local.thru.debounceTime + ((uint32_t)options.thruDebounceMilliseconds) * 1000 <= currentTime))
                {
                if (itemNumber != local.thru.debounceNote && local.thru.debounceState == DEBOUNCE_STATE_FIRST_NOTE_UP_IGNORED)  // new note, kill the old one
                    {
                    performThruNoteOff(local.thru.debounceNote, itemValue, channel);
                    }
                       
                if (options.thruDebounceMilliseconds != 0)
                    {
                    // set up state machine
                    local.thru.debounceState = DEBOUNCE_STATE_FIRST_NOTE_DOWN;
                    local.thru.debounceTime = currentTime;
                    local.thru.debounceNote = itemNumber;
                    }

                performThruNoteOn(itemNumber, itemValue, channel);
                }
            else
                {
                // Filter out, but we're pressing again, so:
                local.thru.debounceState = DEBOUNCE_STATE_FIRST_NOTE_DOWN;
                }
            }
        else if (itemType == MIDI_NOTE_OFF)
            {
            // If the note is too short, and it's what we're holding down, hold off and wait
            if (options.thruDebounceMilliseconds > 0 &&
                local.thru.debounceState == DEBOUNCE_STATE_FIRST_NOTE_DOWN &&
                local.thru.debounceNote == itemNumber)
                {
                if (local.thru.debounceTime + ((uint32_t)options.thruDebounceMilliseconds) * 1000 >= currentTime)                               
                    {
                    local.thru.debounceState = DEBOUNCE_STATE_FIRST_NOTE_UP_IGNORED;
                    local.thru.debounceTime = currentTime;
                    }
                else
                    {
                    performThruNoteOff(itemNumber, itemValue, channel);
                    local.thru.debounceState = DEBOUNCE_STATE_OFF;
                    }
                }
            else            // we don't care about this one
                {
                performThruNoteOff(itemNumber, itemValue, channel);
                }
            }
        else if (itemType == MIDI_AFTERTOUCH_POLY)
            {
            performThruPolyAftertouch(itemNumber, itemValue, channel);
            }
        else if ((itemType == MIDI_AFTERTOUCH) ||
            (itemType >= MIDI_PROGRAM_CHANGE && itemType <= MIDI_RPN_DECREMENT))
            {
            // Yes, I realize this is largely a copy of Control.sendControllerCommand()
            // but I can't modify that function without going over the Uno's memory limit,
            // so I'm unable to use it because I can't pass a channel in.  Maybe later if
            // we jettison the Uno.
                       
            // this includes raw CC, note
                       
/*
// CC->NRPN Mapping.  We just retag CC as if it was NRPN here.
if (options.thruCCToNRPN && (itemType == MIDI_CC_7_BIT || itemType == MIDI_CC_14_BIT))
{
itemType = MIDI_NRPN_14_BIT;
}
*/
                       
            for(uint8_t i = 0; i <= options.thruNumDistributionChannels; i++)
                {
                channel = (options.channelOut + i - 1) % NUM_MIDI_CHANNELS + 1;
                switch(itemType)
                    {
                    case MIDI_AFTERTOUCH:
                        {
                        MIDI.sendAfterTouch(itemValue, channel);
                        }
                    break;
                    case MIDI_PROGRAM_CHANGE:
                        {
                        MIDI.sendProgramChange(itemNumber, channel);
                        }
                    break;
                    case MIDI_PITCH_BEND:
                        {
                        MIDI.sendPitchBend((int)itemValue, channel);
                        }
                    break;
                    case MIDI_CC_7_BIT:
                        {
                        MIDI.sendControlChange(itemNumber, itemValue, channel);
                        }
                    break;
                    case MIDI_CC_14_BIT:
                        {
                        sendControllerCommand(CONTROL_TYPE_CC, itemNumber, itemValue, channel);
                        }
                    break;
                    case MIDI_NRPN_14_BIT:
                        {
                        sendControllerCommand(CONTROL_TYPE_NRPN, itemNumber, itemValue, channel);
                        }
                    break;
                    case MIDI_RPN_14_BIT:
                        {
                        sendControllerCommand(CONTROL_TYPE_RPN, itemNumber, itemValue, channel);
                        }
                    break;
                    case MIDI_NRPN_INCREMENT:
                        {
                        sendControllerCommand(CONTROL_TYPE_NRPN, itemNumber, CONTROL_VALUE_INCREMENT << 7, channel);
                        }
                    break;
                    case MIDI_RPN_INCREMENT:
                        {
                        sendControllerCommand(CONTROL_TYPE_RPN, itemNumber, CONTROL_VALUE_INCREMENT << 7, channel);
                        }
                    break;
                    case MIDI_NRPN_DECREMENT:
                        {
                        sendControllerCommand(CONTROL_TYPE_NRPN, itemNumber, CONTROL_VALUE_DECREMENT << 7, channel);
                        }
                    break;
                    case MIDI_RPN_DECREMENT:
                        {
                        sendControllerCommand(CONTROL_TYPE_RPN, itemNumber, CONTROL_VALUE_DECREMENT << 7, channel);
                        }
                    break;
                    }
                }
            TOGGLE_OUT_LED();
            }
        }
    }
   


PieterP

Is this close to what you want?

MIDI-Chord-Buttons.ino:
#include "MIDIButtonChord.hpp"
#include "Notes.hpp"

constexpr uint8_t channel = 1; // MIDI channel to send on

MIDIButtonChord buttons[] = {
  {Chords::Major,          2,  note(C, 4),  channel}, // Major C chord in the 4th octave, button between pin 2 and ground, MIDI channel 1
  {Chords::Minor,          3,  note(D, 4),  channel},
  {Chords::Minor,          4,  note(E, 4),  channel},
  {Chords::MajorFirstInv,  5,  note(F, 4),  channel},
  {Chords::MajorSecondInv, 6,  note(G, 4),  channel},
  {Chords::MinorSecondInv, 7,  note(A, 4),  channel},
  {Chords::Diminished,     8,  note(B, 4),  channel},
};

/*
* You can change the chords afterwards using
*    buttons[0].setChord(Chords::MajorSeventh);
* for example.
*/

void setup() {
  MIDI.begin();
  for (MIDIButtonChord &button : buttons)
    button.begin();
}

void loop() {
  for (MIDIButtonChord &button : buttons)
    button.update();
}


Full code attached.

Pieter

TheRebell

Hello, perhaps it's the right thing!

What is to do? Combine ?


Like this or is something to change?

#include <Button.h>
#include <MIDI.h>

#define DEBOUNCE 1500

struct key
{
    int pin;
    int midiKey;
    int debounce;
};

struct key keys[] =
{
  { 22, 25, 0 },  // Db red
  { 24, 26, 0 },  // D  red
  { 26, 27, 0 },  // Eb orange
  { 28, 28, 0 },  // E  orange
  { 30, 29, 0 },  // F  yellow
  { 32, 30, 0 },  // Gb green
  { 34, 31, 0 },  // G  green
  { 36, 32, 0 },  // Ab blue
  { 38, 33, 0 },  // A  blue
  { 40, 34, 0 },  // Bb violet
  { 42, 35, 0 },  // B  violet
  { 44, 36, 0 },  // C  brown
  { 48, 24, 0 },  // C  brown
  { 0, 0, 0 }     // end of list marker
};

int keyOffset = 0;

void setup() {
  // put your setup code here, to run once:
  for(int i = 0; keys.pin != 0; ++i)
  {
    pinMode(keys.pin, INPUT_PULLUP);
  }
  Serial.begin(9600);           // set up Serial library at 9600 bps
  Serial.println("Setup complete");
}

void noteOn(int midiKey)
{
  Serial.print("Note On: ");
  Serial.println(midiKey);
}

void noteOff(int midiKey)
{
  Serial.print("Note Off: ");
  Serial.println(midiKey);
}

void loop() {
  // put your main code here, to run repeatedly:
  int value;
  for(int i = 0; keys.pin != 0; ++i)
  {
    value = digitalRead(keys.pin);
    if(keys.debounce == 0) // Key has been off
    {
      if(value == LOW)        // Key is now on
      {
        noteOn(keys.midiKey);      // Send the MIDI note on message
        keys.debounce = DEBOUNCE;  // Set the note off debounce counter
      }
    }
    else                      // Key has been on
    {
      if(value == HIGH)       // Key has gone off
      {
        if(--keys.debounce == 0) // If Key has remained off for DEBOUNCE scans,
          noteOff(keys.midiKey);
      }
      else                    // Key has not gone off
        keys.debounce = DEBOUNCE;  // Reset debounce counter in case we got
                                      // a small number of key off scans
    }
  }
}


PieterP

The code I posted plays a C major chord when you hit the first button, a D minor chord when you hit the second button, etc. You can customize all notes, pins, and chords.

I think that an object-oriented approach is much easier than your code where you're doing all the logic in your loop function. If you encapsulate all of that in classes, it's much easier to keep an overview of what's going on.

If you look at the code I posted, you'll see that the MIDIButtonChords class doesn't care about debouncing, it just uses button.getState(). The Button class handles debouncing behind the scenes, so once that code is written, you don't have to worry about it anymore.

Please use [code][/code] tags when posting code.

TheRebell

sorry, for my late reply, had alot to do the last week. Can you give me an example for the sketch in my case!?

My input note from the pedal knob for a low C is digital pin 48  on the arduino! What is to change in your
mid-chord-button.ino?


What and where do I have to add something for the remaining 12 buttons?


Please help me
Thanks
Greetings...
and many many thanks to you for the patience

Marco

PieterP

Just change the pin numbers on lines 7-14. You can add more chords to that list. Just specify the chord, the pin number, the note number and the channel for each entry.

TheRebell

#10
Sep 03, 2018, 02:22 pm Last Edit: Sep 03, 2018, 02:24 pm by TheRebell
Great!!!  :)  :)  :)  it works!

Thats for simple 3-4 notes chords but
Now, when i have more octaves, p ex 2 hands playing, what can i do?


Thanks, you're great, absolutely what i'm searching for!!!


PieterP

#11
Sep 03, 2018, 07:17 pm Last Edit: Sep 04, 2018, 08:59 am by PieterP
The chords are defined in Chords.hpp. Just add some more.

For example:
Code: [Select]
const Chord<4> MajorAndBass = {{M3, P5, -P8, -2*P8}};
This is a major chord (major third, perfect fifth), and two bass notes (one octave lower, and two octaves lower).

TheRebell

Thanks a lot PieterP,
now I want add more sequences of chords on different channels or banks

p.ex. channel 1:  C=Cmaj, C#=C#m, D=Dm7,...

channel/bank 2:  C=Cm, C#=C#m, D=Dmaj,...

with the buttons of the midi-shield to change

Is it possible?

Greetings
and thanks

Go Up