Pages: [1] 2   Go Down
Author Topic: Why is splitting projects into multiple files so hard?  (Read 3749 times)
0 Members and 1 Guest are viewing this topic.
Southern Ontario
Offline Offline
Sr. Member
****
Karma: 2
Posts: 279
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The topic says it all... I cannot for the life of me figure out how to split my code into multiple .h/.c files...

For the most part my code is all self contained only relying on the Midi.h class.  In order to make use of midi input, you have to subclass the Midi class.  This works perfectly when all in one file with the appropriate #include <Midi.h>

However, when I try to move my subclass into its own two files mymidi.c/h I am getting various odd errors when I try to compile..  Such as:

/mymidi.h:13: undefined reference to `vtable for MyMidi'  (note: this happens only when the class in mymidi.c is COMMENTED OUT, as in no actual code, just the header)

The following error happens when the class in mymidi.c IS NOT commented out...  There's a few pages of these types of errors.

Code:
C:\Program Files\Arduino\hardware\arduino\cores\arduino/WString.h:28: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'String'
C:\Program Files\Arduino\hardware\arduino\cores\arduino/WString.h:99: error: expected '=', ',', ';', 'asm' or '__attribute__' before ':' token
C:\Program Files\Arduino\hardware\arduino\cores\arduino/WString.h:106: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'operator'

How can it be this awkward?  Granted I come from a PHP background, where it is as simple as an include() and bang! the included file is inserted inline...  Don't get me started about the IDE's seriously weird handling of multiple .pde files... Concatenate them alphabetically?!?  That just doesn't make sense to me whatsoever...

Any help would be wonderful smiley
Logged

Southern Ontario
Offline Offline
Sr. Member
****
Karma: 2
Posts: 279
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Since you'll likely want code:
This code works perfectly when in the main pde file.

mymidi.h
Code:
#ifndef MYMIDI_H
#define MYMIDI_H
#include <Midi.h>
class MyMidi : public Midi {
  public:

  MyMidi(HardwareSerial &s) : Midi(s) {}
  
  void handleNoteOn(unsigned int channel, unsigned int note, unsigned int velocity){};
  void handleNoteOff(unsigned int channel, unsigned int note, unsigned int velocity){};
  void handleControlChange(unsigned int channel, unsigned int controller, unsigned int value){};
};
#endif

mymidi.c
Code:
#include "mymidi.h"

class MyMidi : public Midi {
  public:

  MyMidi(HardwareSerial &s) : Midi(s) {}
  
  void handleNoteOn(unsigned int channel, unsigned int note, unsigned int velocity) {
    sendNoteOn(channel, note, velocity); // midi thru
    if (note == CH1_BEAT_NOTE && velocity == 127) digitalWrite(LED_CH1_BEAT, LOW); // thump!
    if (note == CH2_BEAT_NOTE && velocity == 127) digitalWrite(LED_CH2_BEAT, LOW); // thump!
  }
  void handleNoteOff(unsigned int channel, unsigned int note, unsigned int velocity){
    sendNoteOff(channel, note, velocity); // midi thru
    if (note == CH1_BEAT_NOTE && velocity == 0) digitalWrite(LED_CH1_BEAT, HIGH); // no thump!
    if (note == CH2_BEAT_NOTE && velocity == 0) digitalWrite(LED_CH2_BEAT, HIGH); // no thump!
  }

  void handleControlChange(unsigned int channel, unsigned int controller, unsigned int value) {
    sendControlChange(channel, controller, value); // midi thru
    if (channel != 4) return; // we only are about channel 4 atm
    if (controller == XFADER_CC) {
      unsigned int graphCh1 = 0;
      bitSet(graphCh1,map(value,0,127,0,NUM_LEDS_XF-1));
      digitalWrite(LATCH_XFADER, LOW);
      shiftOut(DATA_XFADER, CLOCK_XFADER, MSBFIRST, highByte(graphCh1));
      shiftOut(DATA_XFADER, CLOCK_XFADER, MSBFIRST, lowByte(graphCh1));
      digitalWrite(LATCH_XFADER, HIGH);
    }
  }
};
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 640
Posts: 50335
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
However, when I try to move my subclass into its own two files mymidi.c/h I am getting various odd errors when I try to compile.
Perhaps you should be sub-classing mymidi in a .cpp file, instead of a .c file. Different compilers get invoked. The .cpp compiler knows about classes. The .c compiler does not.
Logged

Southern Ontario
Offline Offline
Sr. Member
****
Karma: 2
Posts: 279
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Ok, good point.. learn something new every day... however when i renamed and compiled, I get

Code:
mymidi.cpp:2: error: redefinition of 'class MyMidi'
mymidi.h:3: error: previous definition of 'class MyMidi'
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 640
Posts: 50335
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The definition of the class goes in the .h file. The implementation of the class goes in the .cpp file.

Remove all the { and } from the header file.
Remove the class statement and public keyword from the source file.

Header:
Code:
#ifndef MYMIDI_H
#define MYMIDI_H
#include <Midi.h>
class MyMidi : public Midi {
  public:

  MyMidi(HardwareSerial &s);
 
  void handleNoteOn(unsigned int channel, unsigned int note, unsigned int velocity);
  void handleNoteOff(unsigned int channel, unsigned int note, unsigned int velocity);
  void handleControlChange(unsigned int channel, unsigned int controller, unsigned int value);
};
#endif

Source:
Code:
MyMidi(HardwareSerial &s) : Midi(s) {}
 
  void handleNoteOn(unsigned int channel, unsigned int note, unsigned int velocity) {
    sendNoteOn(channel, note, velocity); // midi thru
    if (note == CH1_BEAT_NOTE && velocity == 127) digitalWrite(LED_CH1_BEAT, LOW); // thump!
    if (note == CH2_BEAT_NOTE && velocity == 127) digitalWrite(LED_CH2_BEAT, LOW); // thump!
  }
  void handleNoteOff(unsigned int channel, unsigned int note, unsigned int velocity){
    sendNoteOff(channel, note, velocity); // midi thru
    if (note == CH1_BEAT_NOTE && velocity == 0) digitalWrite(LED_CH1_BEAT, HIGH); // no thump!
    if (note == CH2_BEAT_NOTE && velocity == 0) digitalWrite(LED_CH2_BEAT, HIGH); // no thump!
  }

  void handleControlChange(unsigned int channel, unsigned int controller, unsigned int value) {
    sendControlChange(channel, controller, value); // midi thru
    if (channel != 4) return; // we only are about channel 4 atm
    if (controller == XFADER_CC) {
      unsigned int graphCh1 = 0;
      bitSet(graphCh1,map(value,0,127,0,NUM_LEDS_XF-1));
      digitalWrite(LATCH_XFADER, LOW);
      shiftOut(DATA_XFADER, CLOCK_XFADER, MSBFIRST, highByte(graphCh1));
      shiftOut(DATA_XFADER, CLOCK_XFADER, MSBFIRST, lowByte(graphCh1));
      digitalWrite(LATCH_XFADER, HIGH);
    }
  }
}
Logged

Southern Ontario
Offline Offline
Sr. Member
****
Karma: 2
Posts: 279
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Okay, did that...

Code:
mymidi.cpp:2: error: expected `)' before '&' token
mymidi.cpp: In function 'void handleNoteOn(unsigned int, unsigned int, unsigned int)':
mymidi.cpp:5: error: 'sendNoteOn' was not declared in this scope
mymidi.cpp:6: error: 'CH1_BEAT_NOTE' was not declared in this scope
mymidi.cpp:6: error: 'LED_CH1_BEAT' was not declared in this scope
mymidi.cpp:6: error: 'LOW' was not declared in this scope
mymidi.cpp:6: error: 'digitalWrite' was not declared in this scope
mymidi.cpp:7: error: 'CH2_BEAT_NOTE' was not declared in this scope
mymidi.cpp:7: error: 'LED_CH2_BEAT' was not declared in this scope
mymidi.cpp:7: error: 'LOW' was not declared in this scope
mymidi.cpp:7: error: 'digitalWrite' was not declared in this scope
mymidi.cpp: In function 'void handleNoteOff(unsigned int, unsigned int, unsigned int)':
mymidi.cpp:10: error: 'sendNoteOff' was not declared in this scope
mymidi.cpp:11: error: 'CH1_BEAT_NOTE' was not declared in this scope
mymidi.cpp:11: error: 'LED_CH1_BEAT' was not declared in this scope
mymidi.cpp:11: error: 'HIGH' was not declared in this scope
mymidi.cpp:11: error: 'digitalWrite' was not declared in this scope
mymidi.cpp:12: error: 'CH2_BEAT_NOTE' was not declared in this scope
mymidi.cpp:12: error: 'LED_CH2_BEAT' was not declared in this scope
mymidi.cpp:12: error: 'HIGH' was not declared in this scope
mymidi.cpp:12: error: 'digitalWrite' was not declared in this scope
mymidi.cpp: In function 'void handleControlChange(unsigned int, unsigned int, unsigned int)':
mymidi.cpp:16: error: 'sendControlChange' was not declared in this scope
mymidi.cpp:18: error: 'XFADER_CC' was not declared in this scope
mymidi.cpp:20: error: 'NUM_LEDS_XF' was not declared in this scope
mymidi.cpp:20: error: 'map' was not declared in this scope
mymidi.cpp:20: error: 'bitSet' was not declared in this scope
mymidi.cpp:21: error: 'LATCH_XFADER' was not declared in this scope
mymidi.cpp:21: error: 'LOW' was not declared in this scope
mymidi.cpp:21: error: 'digitalWrite' was not declared in this scope
mymidi.cpp:22: error: 'DATA_XFADER' was not declared in this scope
mymidi.cpp:22: error: 'CLOCK_XFADER' was not declared in this scope
mymidi.cpp:22: error: 'MSBFIRST' was not declared in this scope
mymidi.cpp:22: error: 'highByte' was not declared in this scope
mymidi.cpp:22: error: 'shiftOut' was not declared in this scope
mymidi.cpp:23: error: 'lowByte' was not declared in this scope
mymidi.cpp:24: error: 'HIGH' was not declared in this scope
mymidi.cpp: At global scope:
mymidi.cpp:27: error: expected declaration before '}' token
Logged

Southern Ontario
Offline Offline
Sr. Member
****
Karma: 2
Posts: 279
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I added my constants header, so all the defines went away, I also added WProgram.h, and some of the others went away, but I'm still left with:

Code:
mymidi.cpp:4: error: expected `)' before '&' token
mymidi.cpp: In function 'void handleNoteOn(unsigned int, unsigned int, unsigned int)':
mymidi.cpp:7: error: 'sendNoteOn' was not declared in this scope
mymidi.cpp: In function 'void handleNoteOff(unsigned int, unsigned int, unsigned int)':
mymidi.cpp:12: error: 'sendNoteOff' was not declared in this scope
mymidi.cpp: In function 'void handleControlChange(unsigned int, unsigned int, unsigned int)':
mymidi.cpp:18: error: 'sendControlChange' was not declared in this scope
mymidi.cpp: At global scope:
mymidi.cpp:29: error: expected declaration before '}' token

I don't understand why it cant find them, all those functions are in the parent class...   Unless I am SERIOUSLY misunderstanding how OOP works in C++
Logged

New Hampshire
Offline Offline
God Member
*****
Karma: 17
Posts: 781
There are 10 kinds of people, those who know binary, and those who don't.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

The proper format for defining a class method is:

return_type ClassName::methodName(params){
  code for method
}

Rinse and repeat for all methods of the class.
Logged


Southern Ontario
Offline Offline
Sr. Member
****
Karma: 2
Posts: 279
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Ok awesome!

Thanks to you guys, I now have my code split into logical files..  I have one final issue though, I have one function that calls my midi sub-class:

Code:
void midi_stream_note(const uint8_t pitch, const bool onoff) {
#ifdef DEBUG
  uint8_t command = ((onoff)? 0x90 : 0x80);
  Serial.print("cmd:");Serial.print(command >> 4,HEX);   // command type 0..15
  Serial.print(" ch:");Serial.print((g_midi_channel & 0x0f),DEC);  // channel 0..15
  Serial.print(" nt:");Serial.print(pitch & 0x7f,DEC);   // note 0..127
  Serial.print(" d3:");Serial.println(g_midi_velocity & 0x7f,HEX); // velocity 0..127
#else
  if (onoff) {
    midi.sendNoteOn(MIDI_CHANNEL, pitch & 0x7f, 0x7f);
  } else {
    midi.sendNoteOff(MIDI_CHANNEL, pitch & 0x7f, 0x7f);
  }
#endif
}

I have the midi class instantiated at the top of the main pde as follows:

Code:
MyMidi midi(Serial);

It compiles with the midi_stream_note in the main sketch, but when I try to move it to the midi.cpp file, it fails to compile complaining that 'midi' is not declared in this scope.  How would I get this working?  Where do I instantiate the class?   I also have my main task function that is staying in the main sketch file that also calls midi... so I need to have access to it in midi.cpp and in the main sketch file...
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 640
Posts: 50335
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
It compiles with the midi_stream_note in the main sketch, but when I try to move it to the midi.cpp file, it fails to compile complaining that 'midi' is not declared in this scope.  How would I get this working?
In the cpp file, the instance that the method is being called for is known. So, you shouldn't be using midi.sendNoteOn() in the midi_stream_note() function. It should be just sendNoteOn().
Logged

Southern Ontario
Offline Offline
Sr. Member
****
Karma: 2
Posts: 279
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Nadda....

Same error.  midi_stream_note is not part of the class... so (based on my php oop knowledge how would it know where to find sendNoteOn unless I specify which class?

In php (yeah, i know... php), you always have to reference the class whose method you are calling, no matter where you are calling it from (even inside the class, you use $this->)
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 640
Posts: 50335
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

midi_stream_note() is not defined in the header file, or implemented in the source file.

In PHP, the reference to the instance is using the $this-> mechanism. In C++, it would be done using this->, but, this-> is implied if omitted, so, usually, it is omitted.
Logged

Southern Ontario
Offline Offline
Sr. Member
****
Karma: 2
Posts: 279
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

It is though...

midi.h
Code:
#ifndef MYMIDI_H
#define MYMIDI_H
#include <Midi.h>
class MyMidi : public Midi {
  public:

  MyMidi(HardwareSerial &s);
 
  void handleNoteOn(unsigned int channel, unsigned int note, unsigned int velocity);
  void handleNoteOff(unsigned int channel, unsigned int note, unsigned int velocity);
  void handleControlChange(unsigned int channel, unsigned int controller, unsigned int value);
};

extern uint8_t g_midi_channel;
extern uint8_t g_midi_velocity;
extern uint8_t kNoteMap[32];

void midi_stream_note(const uint8_t pitch, const bool onoff);
uint8_t midi_fourbanks_key_to_note(const uint8_t keynum);
#endif

midi.cpp
Code:
#include <WProgram.h>
#include "constants.h"
#include "midi.h"
#include "key.h"

uint8_t g_midi_channel = 3;      // MIDI channel to listen and send on (0..15)
uint8_t g_midi_velocity = 127;     // Default velocity for NoteOn (0..127)
uint8_t kNoteMap[32] = {
  12, 13, 14, 15,
   8,  9, 10, 11,
   4,  5,  6,  7,
   0,  1,  2,  3,
  16, 17, 18, 19,
  20, 21, 22, 23,
  24, 25, 26, 27,
  28, 29, 30, 31,
};

MyMidi::MyMidi(HardwareSerial &s) : Midi(s) {}
 
void MyMidi::handleNoteOn(unsigned int channel, unsigned int note, unsigned int velocity) {
  sendNoteOn(channel, note, velocity); // midi thru
  if (note == CH1_BEAT_NOTE && velocity == 127) digitalWrite(LED_CH1_BEAT, LOW); // thump!
  if (note == CH2_BEAT_NOTE && velocity == 127) digitalWrite(LED_CH2_BEAT, LOW); // thump!
}
void MyMidi::handleNoteOff(unsigned int channel, unsigned int note, unsigned int velocity){
  sendNoteOff(channel, note, velocity); // midi thru
  if (note == CH1_BEAT_NOTE && velocity == 0) digitalWrite(LED_CH1_BEAT, HIGH); // no thump!
  if (note == CH2_BEAT_NOTE && velocity == 0) digitalWrite(LED_CH2_BEAT, HIGH); // no thump!
}

void MyMidi::handleControlChange(unsigned int channel, unsigned int controller, unsigned int value) {
  sendControlChange(channel, controller, value); // midi thru
  if (channel != 4) return; // we only are about channel 4 atm
  if (controller == XFADER_CC) {
    unsigned int graphCh1 = 0;
    bitSet(graphCh1,map(value,0,127,0,NUM_LEDS_XF-1));
    digitalWrite(LATCH_XFADER, LOW);
    shiftOut(DATA_XFADER, CLOCK_XFADER, MSBFIRST, highByte(graphCh1));
    shiftOut(DATA_XFADER, CLOCK_XFADER, MSBFIRST, lowByte(graphCh1));
    digitalWrite(LATCH_XFADER, HIGH);
  }
}

void midi_stream_note(const uint8_t pitch, const bool onoff) {
#ifdef DEBUG
  uint8_t command = ((onoff)? 0x90 : 0x80);
  Serial.print("cmd:");Serial.print(command >> 4,HEX);   // command type 0..15
  Serial.print(" ch:");Serial.print((g_midi_channel & 0x0f),DEC);  // channel 0..15
  Serial.print(" nt:");Serial.print(pitch & 0x7f,DEC);   // note 0..127
  Serial.print(" d3:");Serial.println(g_midi_velocity & 0x7f,HEX); // velocity 0..127
#else
  if (onoff) {
    sendNoteOn(MIDI_CHANNEL, pitch & 0x7f, 0x7f);
  } else {
    sendNoteOff(MIDI_CHANNEL, pitch & 0x7f, 0x7f);
  }
#endif
}

uint8_t midi_fourbanks_key_to_note(const uint8_t keynum) {
  uint8_t banksize = 0;
  banksize = 12;
  uint8_t offset = MIDI_BASE_NOTE + g_key_bank_selected * banksize;
  return kNoteMap[keynum] + offset;
}
Logged

Southern Ontario
Offline Offline
Sr. Member
****
Karma: 2
Posts: 279
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Ok, so I did get it to compile by moving the midiStreamNote into the midi class itself... To be honest, it probably belongs in there anyway...

However I would still like to know why it was happening and what I would need to do to avoid it.  This is the beginnings of a much much larger project, so I will certainly encounter situations like this again.

Are there any decent resources online about including external files in c++?
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 640
Posts: 50335
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
It is though...
No, it was declared in the header file, as a global function. It was implemented in the source file, but not as a member of the class, so it can't call class members. The function was being called as a member of a class, when that was not how it was defined.
Logged

Pages: [1] 2   Go Up
Jump to: