FortySevenEffects / arduino_midi_library

MIDI for Arduino
MIT License
1.59k stars 255 forks source link

how to use MIDI.h in a class #165

Open SandroGrassia opened 4 years ago

SandroGrassia commented 4 years ago

Hi, I'm trying to use MIDI.h inside a class, and use MIDI' s functions in separated methods of this class; for example:

class Test.h:

#ifndef Test_h_
#define Test_h_
#include "Arduino.h"
#include <MIDI.h>

class Test
{
  public:
    void start_midi(void)
    {
      MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);
      MIDI.begin(MIDI_CHANNEL_OMNI);
    }
    void nostop_reading_midi(void)
    {
      do
      {
        if (MIDI.read())
        {
          switch (MIDI.getType())
          {
            case midi::NoteOn:
              {
                Serial.println();
                Serial.print("NoteOn received");
              }
              break;
            case midi::NoteOff:
              {
                Serial.println();
                Serial.print("NoteOff received");
              }
              break;
            default:
              break;
          }
        }
      }
      while (1);
    }
};
#endif

Of course this code generates errors, because MIDI was not declared in nostop_reading_midi(void)... Can you please me give me any help? Thank you! PS: I apologize ... cannot find the way to get the right visualization of indentation!!

franky47 commented 4 years ago

Indeed, calling MIDI_CREATE_INSTANCE here will not help, since the MIDI interface will be destroyed after start_midi returns, so it needs to be stored somewhere. Also, since version 5, the interface also needs a Transport object, to translate raw MIDI data into packets or serial streams. This is usually what the macro takes care of, but we'll see how it works internally and how to adapt it to your case.

There are two places you could store these objects. Either in global scope, or encapsulated in the class as members. It depends how your class object will be created and used, but by the looks of it this class has a run method that never returns, effectively encapsulating the setup / loop functions of Arduino into the class object, so either way will have the same effect.

In global scope, we'll have to use extern, so that inclusions of Test.h in several compilation units (.cpp/.ino files) don't generate multiple objects (however this is unlikely in the case of Arduino):

// Test.h
#pragma once
#include <MIDI.h>

// Define your transport
extern MIDI_NAMESPACE::SerialMIDI<HardwareSerial> serialMIDI;

// Define the MIDI interface
extern MIDI_NAMESPACE::MidiInterface<MIDI_NAMESPACE::SerialMIDI<HardwareSerial>>;

class Test
{
public:
  void start_midi()
  {
    MIDI.begin();
  }
  void nostop_reading_midi()
  {
    // Add the rest of the code around here
    MIDI.read();
  }
};

// main-sketch.ino --
#include <Test.h>

// Here we actually create the MIDI interface, that we previously declared, and bind it to Serial1.
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

To encapsulate the interface as a class member:

// Test.h
#pragma once
#include <MIDI.h>

class Test
{
private:
  // Let's define some shorthand types:
  using MidiTransport = MIDI_NAMESPACE::SerialMIDI<HardwareSerial>;
  using MidiInterface = MIDI_NAMESPACE::MidiInterface<MidiTransport>;

  MidiTransport midiTransport;
  MidiInterface midiInterface;

public:
  // In the constructor, which creates the member objects,
  // we bind the Serial1 port to the transport, and the
  // transport to the MIDI interface:
  Test()
    : midiTransport(Serial1)
    , midiInterface((MidiTransport&)midiTransport)
  {
  }

  void start_midi()
  {
    midiInterface.begin();
  }

  void nostop_reading_midi()
  {
    // Add the rest of the code around here
    midiInterface.read();
  }
};

// main-sketch.ino --
#include <Test.h>

// No need to create the MIDI objects here, 
// they will be created as part of the `test` object:
Test test;

void setup()
{
  test.start_midi();
}

void loop()
{
  test.nostop_reading_midi();
}
SandroGrassia commented 4 years ago

Hi Francois, thank you for your fast reply; I tried both options you wrote, but none of them is correcly compiled using Teensy3.6 (sorry that I neglected to mention that I'm using this board). Worthless to say, that probably I introduced some mistakes or I misunderstood something while developing my code.... But I also would say that I tried other board-options and found that the way that you call "To encapsulate the interface as a class member" is correctly compiled using "Arduino Mega or 2560": may this fact can be important? Don't know if the reports of my trials are useful, if it's not enough I can surely post my code. Thank you!

SandroGrassia commented 4 years ago

Hi Francois, in the last days I've tried some code without success... As I wrote, I'm using a Teensy 3.6; this is my code:

#ifndef Test_h_
#define Test_h_

#pragma once
#include <MIDI.h>

// Define your transport
extern MIDI_NAMESPACE::SerialMIDI<HardwareSerial> serialMIDI;
// Define the MIDI interface
extern MIDI_NAMESPACE::MidiInterface<MIDI_NAMESPACE::SerialMIDI<HardwareSerial>>;

class Test
{
public:
    void start_midi(void)
    {
        MIDI.begin();
    }
     void read_midi(void)
    {
        MIDI.read();
    }
};
#endif

And this is the Arduino-main code:

#include <Test.h>
#include <MIDI.h>

MIDI_CREATE_DEFAULT_INSTANCE();
Test test;

void setup()
{
  test.start_midi();
}

void loop()
{
    test.read_midi();
}

Trying to compile, Arduino shows many errors, example: _C:\Users\dell\Google Drive\ARDUINO SKETCHES\libraries\TEST_classes/Test.h:10:24: warning: declaration does not declare anything [-fpermissive]

C:\Users\dell\Google Drive\ARDUINO SKETCHES\libraries\TEST_classes/Test.h: In member function 'void TestA::start_midi()': C:\Users\dell\Google Drive\ARDUINO SKETCHES\libraries\TEST_classes/Test.h:17:9: error: 'MIDI' was not declared in this scope MIDI.begin();

C:\Users\dell\Google Drive\ARDUINO SKETCHES\libraries\TEST_classes/Test.h: In member function 'void Test2A::read_midi()': C:\Users\dell\Google Drive\ARDUINO SKETCHES\libraries\TESTclasses/Test.h:22:9: error: 'MIDI' was not declared in this scope MIDI.read();

As long as I can see I used the code you proposed... Could you please tell me where is my fault?

franky47 commented 4 years ago

Yes I see, on line 10 of Test.h, the variable name is missing (my bad), it should be:

extern MIDI_NAMESPACE::MidiInterface<MIDI_NAMESPACE::SerialMIDI<HardwareSerial>> MIDI;
SandroGrassia commented 4 years ago

WOW!! Now the code is compiled without errors! Thank you so much for your superfast answer!! There was also a mistake I did, in main code: errata: MIDI_CREATE_DEFAULT_INSTANCE(); corrige: MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

Kprellz commented 3 years ago

I apologize for hijacking this thread, i'm trying to do the same thing for USB-MIDI.h however i'm getting a bunch of errors, does something need to change for this?

Kprellz commented 3 years ago

I actually ended up figuring this out. for anyone used the USB-MIDI library you can do, so far no issues.

#include <USB-MIDI.h>
extern USBMIDI_NAMESPACE::usbMidiTransport usbMIDI;
extern MIDI_NAMESPACE::MidiInterface<USBMIDI_NAMESPACE::usbMidiTransport> MIDI;
SunboX commented 4 weeks ago

Is there any way to do this with Adafruit_USBD_MIDI ?

lathoub commented 4 weeks ago

Is there any way to do this with Adafruit_USBD_MIDI ?

Assuming you mean Adafruit TinyUSB: yes, the MIDI stack in TinyUSB uses this library

SunboX commented 4 weeks ago

yes, but how do I define these with using Tiny USB instead of Serial?

// Define your transport
extern MIDI_NAMESPACE::SerialMIDI<HardwareSerial> serialMIDI;
// Define the MIDI interface
extern MIDI_NAMESPACE::MidiInterface<MIDI_NAMESPACE::SerialMIDI<HardwareSerial>>;
lathoub commented 4 weeks ago

Not sure if I understand, but maybe this helps:

https://github.com/adafruit/Adafruit_TinyUSB_Arduino/blob/05686e7f97de299487fc2291864591d0a18d9bb6/examples/MIDI/midi_test/midi_test.ino#L19-L27

SunboX commented 3 weeks ago

No, not really. I'm getting it working in the main.ino file. But how do I get it working in an external C++ class. Like the thread starter was asking. I'm looking for the equal syntax like here for Serial:

// Define your transport
extern MIDI_NAMESPACE::SerialMIDI<HardwareSerial> serialMIDI;
// Define the MIDI interface
extern MIDI_NAMESPACE::MidiInterface<MIDI_NAMESPACE::SerialMIDI<HardwareSerial>>;

Is this MIDI interface definition correct?

extern MIDI_NAMESPACE::MidiInterface<Adafruit_USBD_MIDI> MIDI;

How should the the transport definition look like?

SunboX commented 3 weeks ago

hm, I think I found an example I can use for lookup: https://github.com/Pirate-MIDI/Pico-Mod/blob/16ce4980aaf7b726754004dd82ff718b90286684/Firmware/include/picomod.h#L189

lathoub commented 3 weeks ago

Check, now I got it (when the problem is solved - hihi). This is a good topic for the Wiki - do you want to describe what you learned/howto?