Skip to content

Latest commit

 

History

History
583 lines (406 loc) · 17.3 KB

system-api.md

File metadata and controls

583 lines (406 loc) · 17.3 KB

System API

This document describes various functions available to query the state of the system or make simple requests to the system.

Audio Settings

See audio/settings.hh

Namespace: MetaModule::Audio

Audio::get_block_size()

uint32_t get_block_size();

Returns the audio block size. Possible values are 16, 32, 64, 128, 256, or 512.

Example usage:

#include "audio/settings.hh"
using namespace MetaModule;

auto size = Audio::get_block_size();

Notifications

See gui/notification.hh

Namespace: MetaModule::Gui

Gui::notify_user()

void notify_user(std::string_view message, int duration_ms);

Triggers a notification to be displayed to the user at the top of the screen. The Notification will automatically hide after the given number of milliseconds. The message should be 255 characters or less (it will be truncated if longer). Typical durations are 1000-4000 (which is 1 - 4 seconds).

Safe to be called from audio context (but as always, do not make the mistake of creating or appending to a std::string while in the audio context).

Example usage:

#include "gui/notification.hh"
using namespace MetaModule;

if (file_format_unknown) {
    Gui::notify_user("The file format is not recognized, please choose a different file", 3000);
}

Patch Files

See patch/patch_file.hh

Namespace: MetaModule::Patch

Patch::mark_patch_modified()

void mark_patch_modified();

Tells the MetaModule system that the current patch file has unsaved changes and needs to be saved. Calling this will put a red dot over the File menu icon at the top of the patch. It also will prevent the patch file from being reloaded automatically if the user updates the patch via Wi-Fi or on the SD Card or USB drive.

The main use case for this option is when your module changes something that needs to be saved in the module's state (save_state() for native modules, or dataToJson() for VCV ports).

A common example is the file path for a sample or wavetable file that the user selects with the File Browser. When a user picks a file to be used with your module, you should call mark_patch_modified() to indicate to the user that they need to save the patch file. Otherwise the user might not realized they have to save the patch in order to have that file loaded the next time they open the patch.

Be careful not to over-use this. It will prevent the user from syncing their patch to VCV Rack with the Wi-Fi expander, which disrupts a common workflow. Only call this function if the user explicitly made a change that must be saved into the patch file.

Example usage:

#include "patch/patch_file.hh"
using namespace MetaModule;

if (user_loaded_a_file) {
    Patch::mark_patch_modified();
}

Patch::get_volume()

StaticString<7> get_volume();

Returns the volume that the currently playing patch is on, as a null-terminated string.

Will be one of the following:

  • "usb:/": USB drive
  • "sdc:/": SD card
  • "nor:/": Internal storage
  • "ram:/": Patch is not saved, exists only in RAM

Future variations of the MetaModule might support more volumes, so keep that in mind.

Example usage:

#include "patch/patch_file.hh"
using namespace MetaModule;

// You can test the path like this (safe to do in any context):
if (Patch::patch_volume().is_equal("ram:/")) {
    //...
}

// You can use the volume to create a path (only safe to use std::string in the
// constructor or an AsyncThread) 

std::string tmp_path = std::string(Patch::patch_volume()) + "tmp.json";

Note: StaticString is a simple string class that stores an array of characters without dynamic memory. In this case, StaticString<7> is an array of 8 chars (maxmimum of 7 chars in the string plus 1 null-termination). It easily converts to/from std::string_view. You can see how StaticString works by looking at the class in [cpputil/util/static_string.hh]

Patch::get_path()

std::string get_path();

Returns the full path to the patch file as dynamically allocated string. Example: "usb:/live-set-01/random-drums.yml" Do not use in audio context (since it will allocate).

Example usage:

#include "patch/patch_file.hh"
using namespace MetaModule;

#include <filesystem>

// Open a .dat file in the same directory as the patch file (with the same stem, e.g. patch.yml => patch.dat)
std::string patch_path = Patch::get_path();
std::string data_path = std::filesystem::path(patch_path).stem().string() + ".dat";
auto fil = fopen(data_path.c_str(), "w+");

Memory

See system/memory.hh

Namespace: MetaModule::System

System::free_memory()

uint32_t free_memory();

Returns the free memory available for allocation (malloc, new, etc.) This memory is not necessarily contigious, so there is no guarentee that you can allocate a block the size of the value returned. However, if the value is smaller than what you want to allocate, then you know for sure that your allocation would fail. Another use for this is to make an allocation that's a percentage of the available memory (e.g. allocate 25% of the free memory).

Example usage:

#include "system/memory.hh"
using namespace MetaModule;

std::vector<float> buffer;

auto free_mem = System::free_memory();
buffer.resize(free_mem / 4); // limit our usage to 25% of available memory

Note that starting in SDK v2.3, failed allocations will throw an exception, so you should pattern large allocations like this:

std::vector<float> sample_data;

try {
    sample_data.resize(sample_size); 
    sample_loaded = true;
} catch (std::bad_alloc &) {
    Gui::notify_user("Could not allocate memory for the sample. Choose a smaller file.", 2000);
    sample_loaded = false;
}

System::total_memory()

uint32_t total_memory();

Returns the total memory available to all modules. This value is subject to change from firmware to firmware version. On the latest firmware at time of writing (v2.0.12), this returns 304087040 (290MB). Typically the only usage for this would be to compare this to the result from free_memory() to get a sense if memory is nearly full or not.

Example usage:

#include "system/memory.hh"
using namespace MetaModule;

auto total_mem = System::total_memory();

Random

See system/random.hh

Namespace: MetaModule::System

System::hardware_random()

uint32_t hardware_random();

hardware_random returns a true random number generated by hardware with an analog entropy source. On average, it's about 2-3x slower than calling random().

The hardware takes time to generate new values, so you must call hardware_random_ready() first and make sure it returns true or else you will not get a unique random value.

Example usage:

#include "system/memory.hh"
using namespace MetaModule;


if (System::hardware_random_ready())
    val = System::hardware_random();

System::hardware_random_ready()

bool hardware_random_ready();

Returns true when the random number hardware is ready to return a new random value. If this returns false, then calling hardware_random() will return the previous value.

Example usage: See hardware_random()

System::random()

uint32_t random();

random returns a pseudo-random number generated by an algorithm. This is a simple wrapper for std::rand().

Example usage:

#include "system/memory.hh"
using namespace MetaModule;


val = System::random();

Time

See system/time.hh

Namespace: MetaModule::System

System::get_ticks()

uint32_t get_ticks();

Returns the number of milliseconds since power-on.

Example usage:

#include "system/time.hh"
using namespace MetaModule;

if (System::get_ticks() - last_time > 1000) {
    last_time = System::get_ticks();
    do_something_every_second();
}

System::delay_ms()

void delay_ms(uint32_t ms);

Pauses execution for the specified number of milliseconds. This is almost never a good idea to use in the audio context as it will likely cause an audio glitch or underrun.

Example usage:

#include "system/time.hh"
using namespace MetaModule;

System::delay_ms(1);

USB

See system/usb.hh

Namespace: MetaModule::System

These functions report what (if anything) is connected to the USB-C port. The MetaModule can act as a USB host (when a device such as a MIDI controller or a drive is plugged into it) or as a USB device (typically when the MetaModule is plugged into a computer). The connection type tells you which role is active.

A complete example is in tests/usb-info-test, which dumps everything described here to the console.

System::get_usb_connection_status()

UsbConnectionStatus get_usb_connection_status();

Returns a snapshot of the current USB connection:

struct UsbConnectionStatus {
    UsbConnectionType connection = UsbConnectionType::None;

    uint8_t num_midi_rx_cables = 0; // MIDI streams the device sends to us
    uint8_t num_midi_tx_cables = 0; // MIDI streams we can send to it

    uint8_t num_midi_in_jacks = 0;  // MIDI IN jacks declared by the device
    uint8_t num_midi_out_jacks = 0;

    uint16_t vid = 0;   // attached device's idVendor (host mode)
    uint16_t pid = 0;   // attached device's idProduct (host mode)
};

This struct holds only small scalars, so polling it is cheap. The device's name strings are reported separately by get_usb_device_name() below, so a module that only needs the cable counts doesn't pay to copy them.

The connection field is one of:

UsbConnectionType Meaning
None Nothing attached
HostSearching Host: port powered, no device class active yet
HostMidiDevice Host: a USB MIDI device is attached
HostUsbDrive Host: a USB mass-storage drive is attached
DeviceWaiting Device: not yet enumerated by a host
DeviceMidiHost Device: enumerated as a USB-MIDI device by a host
DeviceVideoHost Device: enumerated as a UVC video device by a host
DeviceConsoleHost Device: enumerated as a CDC serial console by a host
DeviceModePeripheralIgnored Forced to device role, but a peripheral was sensed on the port: unusable until USB Mode is set to Auto or Host

The device fields (vid, pid and the jack/cable counts), like the name strings from get_usb_device_name(), are populated only in host mode, when there is an attached peripheral to describe. In device mode there is no peripheral descriptor to report, so those fields are zero/empty and only connection is meaningful. (When the MetaModule is plugged into a computer it presents a single MIDI port, so every message has cable number 0.)

Example usage:

#include "system/usb.hh"
using namespace MetaModule;

auto status = System::get_usb_connection_status();

if (status.connection == System::UsbConnectionType::HostMidiDevice) {
    // Detect a particular VID/PID and do something special with it
    if (status.vid == MyDeviceVID && status.pid == MyDevicePID) {
        communicate_with_my_device();
    }
}

System::get_usb_device_name()

UsbDeviceName get_usb_device_name();

The attached device's name strings, reported separately from the connection status so that polling the status stays cheap:

struct UsbDeviceName {
    StaticString<63> manufacturer;  // iManufacturer string (may be empty)
    StaticString<63> product;       // iProduct string (may be empty)
};

Either string may be empty: not every device provides them, and in device mode there is no attached peripheral to name.

auto name = System::get_usb_device_name();
printf("%s %s\n", name.manufacturer.c_str(), name.product.c_str());

System::get_usb_midi_rx_cable() / get_usb_midi_tx_cable()

UsbMidiCableInfo get_usb_midi_rx_cable(unsigned cable_num);
UsbMidiCableInfo get_usb_midi_tx_cable(unsigned cable_num);

A multi-port USB-MIDI device carries each of its ports on a separate "cable", and every message it sends is tagged with the cable number it came from (MidiMessage::usb_hdr.cable_num). These two functions tell you what each cable is called, so you can present the user with a list of port names and/or filter received messages on a cable number, and/or transmit to particular cables.

rx and tx are relative to the MetaModule:

  • rx cables carry messages from the other device to the MetaModule. These are the ones to match against messages you get from MidiInput::pop_message().
  • tx cables are the ones the MetaModule can send messages to with MidiOutput::push_message.

Valid cable numbers run from 0 to num_midi_rx_cables - 1 (or num_midi_tx_cables - 1) as reported by get_usb_connection_status(). Out of range returns an entry whose valid is false. A device can have at most MetaModule::System::MaxMidiCables (16) cables per direction, which is the USB-MIDI limit.

Example usage:

#include "midi/midi_in.hh"
#include "system/usb.hh"
using namespace MetaModule;

// Find the cable whose name contains "DAW" and listen to only that one.

auto status = System::get_usb_connection_status();
int daw_cable = -1;

for (unsigned c = 0; c < status.num_midi_rx_cables; c++) {
    auto cable = System::get_usb_midi_rx_cable(c);
    if (cable.valid && cable.name.contains("DAW")) {
        daw_cable = c;
        break;
    }
}

// Now we can filter our incoming messages

if (auto msg = midi.pop_message()) {
    if (msg->usb_hdr.cable_num == daw_cable) {
        process_DAW_message(*msg);
    }
}

Both functions return a UsbMidiCableInfo, which carries just what you need to match a cable by name and then filter on its number:

struct UsbMidiCableInfo {
    StaticString<31> name;  // the cable's port name (may be empty)
    uint8_t cable_num = 0;  // matches MidiMessage::usb_hdr.cable_num
    bool valid = false;     // false if there is no such cable
};

If valid is false, then the other fields are meaningless. Otherwise, cable_num is just the number you passed in. The name is the device's own name for that port, which is the same string a computer would show in its MIDI port list. Many devices leave the name empty, so be prepared to fall back on a generic label like "Port 1", or the Manufacturer/Product name if you intend to display the cable name to the user.

System::get_usb_midi_in_jack_info() / get_usb_midi_out_jack_info()

UsbMidiJackInfo get_usb_midi_in_jack_info(unsigned num);
UsbMidiJackInfo get_usb_midi_out_jack_info(unsigned num);

This is the lower-level view: the raw jack descriptors the device declares. Most modules will only need to use get_usb_midi_rx_cable() above instead. Only use the jack accessors if you need to see the device's full internal topology, for instance to tell an Embedded jack from an External one.

The struct returned from get_usb_connection_status() tells you how many MIDI in and out jacks the device has. You can then pass a jack number to get_usb_midi_in/out_jack_info() to get the information in the descriptor:

struct UsbMidiJackInfo {
    StaticString<31> name;    // the jack's iJack string descriptor (may be empty)
    uint8_t jack_id = 0;      // bJackID: the device's own ID for this jack
    uint8_t cable_num = 0;    // USB-MIDI cable number; only meaningful if has_cable
    bool has_cable = false;   // true if this jack is addressable by a cable number
    bool is_embedded = false; // true: Embedded jack (a real port); false: External
    bool valid = false;       // false if `num` was out of range / no such jack
};

Always check valid before using the other fields: it is false if num is out of range (that is, num >= num_midi_in_jacks for the IN direction, or >= num_midi_out_jacks for OUT). At most MetaModule::System::MaxMidiJacks (16) jacks per direction are reported, no matter how many the device declares.

Note that jack numbers are just indices into the snapshot: jack_id is a separate value assigned by the device itself, and the two are not usually equal.

Two things about jacks are easy to get backwards:

  • Only Embedded jacks have cable numbers. External jacks are the device's physical sockets. They carry MIDI between the device and the outside world, not over USB, so has_cable is false for them. It can also be false for an Embedded jack the device declared but didn't associate with an endpoint.
  • A device's MIDI OUT jacks are what send messages to us. So the MIDI OUT jacks are the ones with rx cable numbers, and the MIDI IN jacks have the tx cable numbers. This inversion is why get_usb_midi_rx_cable() exists: it is named from the MetaModule's point of view and can't be misread.

If an Embedded jack has no name of its own, the name of the External jack it is wired to is used instead (that's often where a device puts the useful label, e.g. "MIDI OUT 2").