Learn Sending and Receiving MIDI in C++

Getting Started

Welcome to RNBO

Quickstart

RNBO Basics

Key Differences

Why We Made RNBO

Fundamentals

Audio IO

Messages to rnbo~

Using Parameters

MIDI in RNBO

Messages and Ports

Polyphony and Voice Control

Audio Files in RNBO

Using Buffers

Using the FFT

Export Targets

Export Targets Overview

VST/AudioUnit
Max External Target
Raspberry Pi Target
The Web Export Target
The C++ Source Code Target

Code Export

Working with JavaScript
Working with C++

How to Include RNBO in Your C++ Project

RNBO Helper Types

Getting and Setting Parameters in C++

Sending and Receiving Messages in C++

Sending and Receiving MIDI in C++

Multiple RNBO Devices in one C++ Project

Working with Presets in C++

Loading File Dependencies

Sending and Receiving MIDI C++

Recall from Getting and Setting Parameters C++ that we use the setParameterValue method of a RNBO core object to update parameters. To send a message, we use the sendMessage method. Both of these functions are convenience wrappers around a more generic function scheduleEvent, which can send any kind of RNBO event to be executed at a given time. This is the function used to send MIDI events to a RNBO core object.

#include "RNBO.h"
using namespace RNBO;

const uint8_t noteOn = 0x90;
const uint8_t midiChannel = 0;

const uint8_t noteOnLeadByte = noteOn | midiChannel;
const uint8_t pitch = 60;
const uint8_t velocity = 100;

const uint8_t midiBytes[3] = { noteOnLeadByte, pitch, velocity };
rnboObject.scheduleEvent(MidiEvent(RNBOTimeNow, 0, midiBytes, 3));

Using the same approach, it's possible to schedule MIDI Control Change, Pitch Bend, and other events. See https://ccrma.stanford.edu/~craig/articles/linuxmidi/misc/essenmidi.html for a more thorough overview of MIDI essentials.

Receiving MIDI Events

We can receive MIDI events by creating a new class that is a subclass of RNBO::EventHandler and implementing the handleMidiEvent method. A simple example might look something like this:

#include <iostream>
#include "RNBO.h"

using namespace RNBO;

class LoggingMidiHandler : EventHandler {

  void eventsAvailable() override {
    drainEvents();
  }

  void handleMidiEvent(const MidiEvent& event) override {
    const uint8_t *data = event.getData();
    int length = event.getLength();

    if ((data[0] >> 4) == 0x09) { // note on
      std::cout << "Received note on event with pitch: " << ((int) data[1]) << " velocity: " << ((int) data[2]) << "\n";
    } else if ((data[0] >> 4) == 0x08) { // note off
      std::cout << "Received note off event with pitch: " << ((int) data[1]) << "\n";
    } else {
      std::cout << "Received midi event with data: ";
      for (int i = 0; i < length; i++) {
        if (i != 0) std::cout << ", ";
        std::cout << ((int) data[i]);
      }
      std::cout << "\n";
    }
  }
};

int main(int argc, const char * argv[]) {
  CoreObject rnboObject;
  LoggingMidiHandler handler;

  auto interface = 
        rnboObject.createParameterInterface(ParameterEventInterface::MultiProducer, (EventHandler *) &handler);

  rnboObject.prepareToProcess(44100, 64);

  SampleValue** inputs = nullptr;
  SampleValue** outputs = new SampleValue*[1];
  outputs[0] = new double[64];

  while (true) {
    const uint8_t noteOn = 0x90;
    const uint8_t noteOff = 0x80;
    const uint8_t midiChannel = 0;

    const uint8_t noteOnLeadByte = noteOn | midiChannel;
    const uint8_t pitch = 60;
    const uint8_t velocity = 100;

    uint8_t midiBytes[3] = { noteOnLeadByte, pitch, velocity };

    // send note on
    rnboObject.scheduleEvent(MidiEvent(RNBOTimeNow, 0, midiBytes, 3));

    midiBytes[0] = noteOff | midiChannel;
    midiBytes[2] = 0;
    
    // send note off
    rnboObject.scheduleEvent(MidiEvent(RNBOTimeNow, 0, midiBytes, 3));

    rnboObject.process(inputs, 0, outputs, 1, 64);
  }

  delete [] outputs[0];
  delete [] outputs;

  return 0;
}