Learn Sending and Receiving Messages 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 Messages C++

Any inport, outport, in, or out objects in a RNBO patcher will create tagged ports that can send and receive messages. To send a message to an object defined like [inport pitch], use sendMessage.

// Send a bang to pitch
rnboObject.sendMessage(TAG("pitch"), TAG(""));

The first argument of sendMessage is the tag of the inport that should receive the message. Use the RNBO function TAG to convert from a string to the internal RNBO tag format. The second argument is mostly reserved for RNBO internal use, and refers to the tag of a specific object id. In most cases, simply use the empty tag by writing TAG(""). The optional third argument lets you schedule the message to be sent in the future by specifying a specific millisecond time. The default value is equivalent to passing RNBO::RNBOTimeNow, which schedules the message to be sent immediately.

Internally there's not much different between in and inport, where in simply have a default tag like "in1" for the first message inlet, "in2" for the second message inlet, etc. So to send a number to the second inlet, simply send a message as follows:

// Send a number to the second inlet
rnboObject.sendMessage(TAG("in2"), 74, TAG(""));

Finally, to send a list of numerical values, we must first create a unique pointer to a RNBO list. The easiest way to accomplish this is to use the RNBO::make_unique function.

// Send a list to the third inlet
auto mylist = RNBO::make_unique<RNBO::list>();
mylist->push(1), mylist->push(2), mylist->push(3);
rnboObject.sendMessage(TAG("in3"), std::move(mylist), TAG(""));

Receiving Message Events

Similar to receiving parameter events, you can receive message events by creating a concrete subclass of RNBO::EventHandler. See the previous section on Getting and Setting Parameters for a more in depth discussion on the threading considerations involved with receiving events. The basic idea is the same: override and implement handleMessageEvent instead of handleParameterEvent.

class LoggingMessageHandler : EventHandler {

  void eventsAvailable() override {
    drainEvents();
  }

  void handleMessageEvent(const MessageEvent& event) override {
    if (event.getType() == MessageEvent::Bang) {
      std::cout << "Received bang with tag " << event.getTag() << "\n";
    } else if (event.getType() == MessageEvent::Number) {
      std::cout << "Received value " << event.getNumValue() << " with tag " << event.getTag() << "\n";
    } else if (event.getType() == MessageEvent::List) {
      std::cout << "Received list value ";
      auto list = event.getListValue().get();
      for (int i = 0; i < list->length; i++) {
        if (i != 0) std::cout << ", ";
        std::cout << list->operator[](i);
      }
      std::cout << " with tag " << event.getTag() << "\n";
    }
  }
};

One consideration here is that event.getTag() will return an internal RNBO tag, which might not be very useful. We could pass the RNBO core object to the message handler class, giving us access to the resolveTag method of the RNBO core object.

using namespace RNBO;

class LoggingMessageHandler : EventHandler {
public:
  LoggingMessageHandler(CoreObject *obj) : m_obj(obj) {}

private:

  void eventsAvailable() override {
    drainEvents();
  }

  void handleMessageEvent(const MessageEvent& event) override {
    if (event.getType() == MessageEvent::Bang) {
      std::cout << "Received bang with tag " << m_obj->resolveTag(event.getTag()) << "\n";
    } else if (event.getType() == MessageEvent::Number) {
      std::cout << "Received value " << event.getNumValue() << " with tag " << m_obj->resolveTag(event.getTag()) << "\n";
    } else if (event.getType() == MessageEvent::List) {
      std::cout << "Received list value ";
      auto list = event.getListValue().get();
      for (int i = 0; i < list->length; i++) {
        if (i != 0) std::cout << ", ";
        std::cout << list->operator[](i);
      }
      std::cout << " with tag " << m_obj->resolveTag(event.getTag()) << "\n";
    }
  }

  CoreObject *m_obj;
};

Or for extra credit, we could simply pass a lambda to resolve the tag.

using namespace RNBO;

class LoggingMessageHandler : EventHandler {
public:
  LoggingMessageHandler(std::function<RNBO::MessageTagInfo(RNBO::MessageTag)> tagResolver) : tagResolver(tagResolver) {}

private:

  void eventsAvailable() override {
    drainEvents();
  }

  void handleMessageEvent(const MessageEvent& event) override {
    if (event.getType() == MessageEvent::Bang) {
      std::cout << "Received bang with tag " << tagResolver(event.getTag()) << "\n";
    } else if (event.getType() == MessageEvent::Number) {
      std::cout << "Received value " << event.getNumValue() << " with tag " << tagResolver(event.getTag()) << "\n";
    } else if (event.getType() == MessageEvent::List) {
      std::cout << "Received list value ";
      auto list = event.getListValue().get();
      for (int i = 0; i < list->length; i++) {
        if (i != 0) std::cout << ", ";
        std::cout << list->operator[](i);
      }
      std::cout << " with tag " << tagResolver(event.getTag()) << "\n";
    }
  }

  std::function<RNBO::MessageTagInfo(RNBO::MessageTag)> tagResolver;
};

CoreObject rnboObject;
auto tagResolver = [&rnboObject](RNBO::MessageTag tag) { return rnboObject.resolveTag(tag); };
LoggingMessageHandler handler(tagResolver);

Putting it all together into a complete example might look something like this:

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

using namespace RNBO;

class LoggingMessageHandler : EventHandler {
public:
  LoggingMessageHandler(std::function<RNBO::MessageTagInfo(RNBO::MessageTag)> tagResolver) : tagResolver(tagResolver) {}

private:

  void eventsAvailable() override {
    drainEvents();
  }

  void handleMessageEvent(const MessageEvent& event) override {
    if (event.getType() == MessageEvent::Bang) {
      std::cout << "Received bang with tag " << tagResolver(event.getTag()) << "\n";
    } else if (event.getType() == MessageEvent::Number) {
      std::cout << "Received value " << event.getNumValue() << " with tag " << tagResolver(event.getTag()) << "\n";
    } else if (event.getType() == MessageEvent::List) {
      std::cout << "Received list value ";
      auto list = event.getListValue().get();
      for (int i = 0; i < list->length; i++) {
        if (i != 0) std::cout << ", ";
        std::cout << list->operator[](i);
      }
      std::cout << " with tag " << tagResolver(event.getTag()) << "\n";
    }
  }

  std::function<RNBO::MessageTagInfo(RNBO::MessageTag)> tagResolver;
};

int main(int argc, const char * argv[]) {
  CoreObject rnboObject;
  auto tagResolver = [&rnboObject](RNBO::MessageTag tag) { return rnboObject.resolveTag(tag); };
  LoggingMessageHandler handler(tagResolver);

  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) {
    rnboObject.process(inputs, 0, outputs, 1, 64);
    
    // Send a bang to in1
    rnboObject.sendMessage(TAG("in1"), TAG(""));

    // Send a number to in2
    rnboObject.sendMessage(TAG("in2"), 74, TAG(""));

    // Send a list to in3
    auto l = make_unique<RNBO::list>();
    l->push(1), l->push(2), l->push(3);
    rnboObject.sendMessage(TAG("in3"), std::move(l), TAG(""));
  }

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

  return 0;
}