diff --git a/include/choc/choc_SmallVector.h b/include/choc/choc_SmallVector.h new file mode 100644 index 0000000..b197bc9 --- /dev/null +++ b/include/choc/choc_SmallVector.h @@ -0,0 +1,528 @@ +// +// ██████ ██  ██  ██████  ██████ +// ██      ██  ██ ██    ██ ██       ** Clean Header-Only Classes ** +// ██  ███████ ██  ██ ██ +// ██  ██   ██ ██  ██ ██ https://github.com/Tracktion/choc +//  ██████ ██  ██  ██████   ██████ +// +// CHOC is (C)2021 Tracktion Corporation, and is offered under the terms of the ISC license: +// +// Permission to use, copy, modify, and/or distribute this software for any purpose with or +// without fee is hereby granted, provided that the above copyright notice and this permission +// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR +// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +#ifndef CHOC_SMALLVECTOR_HEADER_INCLUDED +#define CHOC_SMALLVECTOR_HEADER_INCLUDED + +#include +#include "choc_Span.h" + +namespace choc +{ + +/** + A std::vector-style container class, which uses some pre-allocated storage + to avoid heap allocation when the number of elements is small. + + Inspired by LLVM's SmallVector, I've found this to be handy in many situations + where you know there's only likely to be a small or fixed number of elements, + and where performance is important. + + It retains most of the same basic methods as std::vector, but without some of + the more exotic tricks that the std library uses, just to avoid things getting + too complicated. +*/ +template +struct SmallVector +{ + using value_type = ElementType; + using reference = ElementType&; + using const_reference = const ElementType&; + using iterator = ElementType*; + using const_iterator = const ElementType*; + using size_type = size_t; + + SmallVector() noexcept; + ~SmallVector() noexcept; + + SmallVector (SmallVector&&) noexcept; + SmallVector (const SmallVector&); + SmallVector& operator= (SmallVector&&) noexcept; + SmallVector& operator= (const SmallVector&); + + /// Creates a SmallVector as a copy of some kind of iterable container. + template + SmallVector (const VectorType& initialContent); + + /// Replaces the contents of this vector with a copy of some kind of iterable container. + template + SmallVector& operator= (const VectorType&); + + reference operator[] (size_type index); + const_reference operator[] (size_type index) const; + + value_type* data() const noexcept; + + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + iterator begin() noexcept; + iterator end() noexcept; + + const_reference front() const; + reference front(); + const_reference back() const; + reference back(); + + bool empty() const noexcept; + size_type size() const noexcept; + size_type length() const noexcept; + size_type capacity() const noexcept; + + bool contains (const ElementType&) const; + + void clear() noexcept; + void resize (size_type newSize); + void reserve (size_type requiredNumElements); + + void push_back (const value_type&); + void push_back (value_type&&); + + /// Handy method to add multiple elements with a single push_back call. + template + void push_back (const value_type& first, Others&&... others); + + template + void emplace_back (ConstructorArgs&&... args); + + void pop_back(); + + void insert (iterator insertPosition, const value_type& valueToInsert); + void insert (iterator insertPosition, value_type&& valueToInsert); + + void erase (iterator startPosition); + void erase (iterator startPosition, iterator endPosition); + + bool operator== (span) const; + bool operator!= (span) const; + +private: + value_type* elements; + size_type numElements = 0, numAllocated = numPreallocatedElements; + uint64_t internalStorage[(numPreallocatedElements * sizeof (value_type) + sizeof (uint64_t) - 1) / sizeof (uint64_t)]; + + void shrink (size_type); + value_type* getInternalStorage() noexcept { return reinterpret_cast (internalStorage); } + bool isUsingInternalStorage() const noexcept { return numAllocated <= numPreallocatedElements; } + void resetToInternalStorage() noexcept; + void freeHeapAndResetToInternalStorage() noexcept; + + static inline ElementType& _nullValue() noexcept { static ElementType e = {}; return e; } +}; + + + +//============================================================================== +// _ _ _ _ +// __| | ___ | |_ __ _ (_)| | ___ +// / _` | / _ \| __| / _` || || |/ __| +// | (_| || __/| |_ | (_| || || |\__ \ _ _ _ +// \__,_| \___| \__| \__,_||_||_||___/(_)(_)(_) +// +// Code beyond this point is implementation detail... +// +//============================================================================== + +template +SmallVector::SmallVector() noexcept : elements (getInternalStorage()) +{ +} + +template +SmallVector::~SmallVector() noexcept +{ + clear(); +} + +template +SmallVector::SmallVector (const SmallVector& other) : SmallVector() +{ + operator= (other); +} + +template +template +SmallVector::SmallVector (const VectorType& initialContent) : SmallVector() +{ + reserve (initialContent.size()); + + for (auto& i : initialContent) + emplace_back (i); +} + +template +SmallVector::SmallVector (SmallVector&& other) noexcept +{ + if (other.isUsingInternalStorage()) + { + elements = getInternalStorage(); + numElements = other.numElements; + + for (size_type i = 0; i < numElements; ++i) + new (elements + i) value_type (std::move (other.elements[i])); + } + else + { + elements = other.elements; + numElements = other.numElements; + numAllocated = other.numAllocated; + other.resetToInternalStorage(); + other.numElements = 0; + } +} + +template +SmallVector& SmallVector::operator= (SmallVector&& other) noexcept +{ + clear(); + + if (other.isUsingInternalStorage()) + { + numElements = other.numElements; + + for (size_type i = 0; i < numElements; ++i) + new (elements + i) value_type (std::move (other.elements[i])); + } + else + { + elements = other.elements; + numElements = other.numElements; + numAllocated = other.numAllocated; + other.resetToInternalStorage(); + other.numElements = 0; + } + + return *this; +} + +template +SmallVector& SmallVector::operator= (const SmallVector& other) +{ + if (other.size() > numElements) + { + reserve (other.size()); + + for (size_type i = 0; i < numElements; ++i) + elements[i] = other.elements[i]; + + for (size_type i = numElements; i < other.size(); ++i) + new (elements + i) value_type (other.elements[i]); + + numElements = other.size(); + } + else + { + shrink (other.size()); + + for (size_type i = 0; i < numElements; ++i) + elements[i] = other.elements[i]; + } + + return *this; +} + +template +template +SmallVector& SmallVector::operator= (const VectorType& other) +{ + if (other.size() > numElements) + { + reserve (other.size()); + + for (size_type i = 0; i < numElements; ++i) + elements[i] = other[i]; + + for (size_type i = numElements; i < other.size(); ++i) + new (elements + i) value_type (other[i]); + + numElements = other.size(); + } + else + { + shrink (other.size()); + + for (size_type i = 0; i < numElements; ++i) + elements[i] = other[i]; + } + + return *this; +} + +template +void SmallVector::resetToInternalStorage() noexcept +{ + elements = getInternalStorage(); + numAllocated = preSize; +} + +template +void SmallVector::freeHeapAndResetToInternalStorage() noexcept +{ + if (! isUsingInternalStorage()) + { + delete[] reinterpret_cast (elements); + resetToInternalStorage(); + } +} + +template +typename SmallVector::reference SmallVector::operator[] (size_type index) +{ + DISTRHO_SAFE_ASSERT_RETURN (index < numElements, _nullValue()); + return elements[index]; +} + +template +typename SmallVector::const_reference SmallVector::operator[] (size_type index) const +{ + DISTRHO_SAFE_ASSERT_RETURN (index < numElements, _nullValue()); + return elements[index]; +} + +template +typename SmallVector::value_type* SmallVector::data() const noexcept { return elements; } +template +typename SmallVector::const_iterator SmallVector::begin() const noexcept { return elements; } +template +typename SmallVector::const_iterator SmallVector::end() const noexcept { return elements + numElements; } +template +typename SmallVector::const_iterator SmallVector::cbegin() const noexcept { return elements; } +template +typename SmallVector::const_iterator SmallVector::cend() const noexcept { return elements + numElements; } +template +typename SmallVector::iterator SmallVector::begin() noexcept { return elements; } +template +typename SmallVector::iterator SmallVector::end() noexcept { return elements + numElements; } + +template +typename SmallVector::reference SmallVector::front() +{ + DISTRHO_SAFE_ASSERT_RETURN (! empty(), _nullValue()); + return elements[0]; +} + +template +typename SmallVector::const_reference SmallVector::front() const +{ + DISTRHO_SAFE_ASSERT_RETURN (! empty(), _nullValue()); + return elements[0]; +} + +template +typename SmallVector::reference SmallVector::back() +{ + DISTRHO_SAFE_ASSERT_RETURN (! empty(), _nullValue()); + return elements[numElements - 1]; +} + +template +typename SmallVector::const_reference SmallVector::back() const +{ + DISTRHO_SAFE_ASSERT_RETURN (! empty(), _nullValue()); + return elements[numElements - 1]; +} + +template +typename SmallVector::size_type SmallVector::size() const noexcept { return numElements; } + +template +typename SmallVector::size_type SmallVector::length() const noexcept { return numElements; } + +template +typename SmallVector::size_type SmallVector::capacity() const noexcept { return numAllocated; } + +template +bool SmallVector::empty() const noexcept { return numElements == 0; } + +template +bool SmallVector::contains (const ElementType& target) const +{ + for (size_t i = 0; i < numElements; ++i) + if (elements[i] == target) + return true; + + return false; +} + +template +bool SmallVector::operator== (span other) const { return span (*this) == other; } + +template +bool SmallVector::operator!= (span other) const { return span (*this) != other; } + +template +void SmallVector::push_back (const value_type& item) +{ + reserve (numElements + 1); + new (elements + numElements) value_type (item); + ++numElements; +} + +template +void SmallVector::push_back (value_type&& item) +{ + reserve (numElements + 1); + new (elements + numElements) value_type (std::move (item)); + ++numElements; +} + +template +template +void SmallVector::push_back (const value_type& first, Others&&... others) +{ + reserve (numElements + 1 + sizeof... (others)); + push_back (first); + push_back (std::forward (others)...); +} + +template +template +void SmallVector::emplace_back (ConstructorArgs&&... args) +{ + reserve (numElements + 1); + new (elements + numElements) value_type (std::forward (args)...); + ++numElements; +} + +template +void SmallVector::insert (iterator insertPos, const value_type& item) +{ + DISTRHO_SAFE_ASSERT_RETURN (insertPos != nullptr && insertPos >= begin() && insertPos <= end(),); + auto index = insertPos - begin(); + push_back (item); + std::rotate (begin() + index, end() - 1, end()); +} + +template +void SmallVector::insert (iterator insertPos, value_type&& item) +{ + DISTRHO_SAFE_ASSERT_RETURN (insertPos != nullptr && insertPos >= begin() && insertPos <= end(),); + auto index = insertPos - begin(); + push_back (std::move (item)); + std::rotate (begin() + index, end() - 1, end()); +} + +template +void SmallVector::pop_back() +{ + if (numElements == 1) + { + clear(); + } + else + { + DISTRHO_SAFE_ASSERT_RETURN (numElements > 0,); + elements[--numElements].~value_type(); + } +} + +template +void SmallVector::clear() noexcept +{ + for (size_type i = 0; i < numElements; ++i) + elements[i].~value_type(); + + numElements = 0; + freeHeapAndResetToInternalStorage(); +} + +template +void SmallVector::resize (size_type newSize) +{ + if (newSize > numElements) + { + reserve (newSize); + + while (numElements < newSize) + new (elements + numElements++) value_type (value_type()); + } + else + { + shrink (newSize); + } +} + +template +void SmallVector::shrink (size_type newSize) +{ + if (newSize == 0) + return clear(); + + DISTRHO_SAFE_ASSERT_RETURN (newSize <= numElements,); + + while (newSize < numElements && numElements > 0) + elements[--numElements].~value_type(); +} + +template +void SmallVector::reserve (size_type requiredNumElements) +{ + if (requiredNumElements > numAllocated) + { + requiredNumElements = static_cast ((requiredNumElements + 15u) & ~(size_type) 15u); + + if (requiredNumElements > preSize) + { + auto* newBuffer = reinterpret_cast (new char[requiredNumElements * sizeof (value_type)]); + + for (size_type i = 0; i < numElements; ++i) + { + new (newBuffer + i) value_type (std::move (elements[i])); + elements[i].~value_type(); + } + + freeHeapAndResetToInternalStorage(); + elements = newBuffer; + } + + numAllocated = requiredNumElements; + } +} + +template +void SmallVector::erase (iterator startElement) +{ + erase (startElement, startElement + 1); +} + +template +void SmallVector::erase (iterator startElement, iterator endElement) +{ + DISTRHO_SAFE_ASSERT_RETURN (startElement != nullptr && startElement >= begin() && startElement <= end(),); + DISTRHO_SAFE_ASSERT_RETURN (endElement != nullptr && endElement >= begin() && endElement <= end(),); + + if (startElement != endElement) + { + DISTRHO_SAFE_ASSERT_RETURN (startElement < endElement,); + + if (endElement == end()) + return shrink (static_cast (startElement - begin())); + + auto dest = startElement; + + for (auto src = endElement; src < end(); ++dest, ++src) + *dest = std::move (*src); + + shrink (size() - static_cast (endElement - startElement)); + } +} + +} + +#endif // CHOC_SMALLVECTOR_HEADER_INCLUDED diff --git a/include/choc/choc_Span.h b/include/choc/choc_Span.h new file mode 100644 index 0000000..2efa7fb --- /dev/null +++ b/include/choc/choc_Span.h @@ -0,0 +1,127 @@ +// +// ██████ ██  ██  ██████  ██████ +// ██      ██  ██ ██    ██ ██       ** Clean Header-Only Classes ** +// ██  ███████ ██  ██ ██ +// ██  ██   ██ ██  ██ ██ https://github.com/Tracktion/choc +//  ██████ ██  ██  ██████   ██████ +// +// CHOC is (C)2021 Tracktion Corporation, and is offered under the terms of the ISC license: +// +// Permission to use, copy, modify, and/or distribute this software for any purpose with or +// without fee is hereby granted, provided that the above copyright notice and this permission +// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR +// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +#ifndef CHOC_SPAN_HEADER_INCLUDED +#define CHOC_SPAN_HEADER_INCLUDED + +#include +#include "DistrhoUtils.hpp" + +namespace choc +{ + +//============================================================================== +/** This is a temporary stunt-double for std::span, with the intention of it being + deprecated when there's more widespread compiler support for the real std::span. + + This class has fewer bells-and-whistles than a real std::span, but it does have + the advantage of calling CHOC_ASSERT when mistakes are made like out-of-range + accesses, which can be useful for getting clean error handling rather than UB. +*/ +template +struct span +{ + span() = default; + span (const span&) = default; + span (span&&) = default; + span& operator= (span&&) = default; + span& operator= (const span&) = default; + + /// Construct from some raw start and end pointers. For an empty span, these + /// can both be nullptr, but if one is a real pointer then the caller must ensure + /// that start <= end. + span (Item* start, Item* end) noexcept : s (start), e (end) {} + + /// Constructs a span from a pointer and length. + /// The pointer must not be nullptr unless the length is 0. + span (const Item* start, size_t length) noexcept : span (const_cast (start), const_cast (start) + length) {} + + /// Constructor taking a raw C++ array. + template + span (Item (&array)[length]) : span (array, length) {} + + /// Constructor which takes some kind of class like std::vector or std::array. + /// Any class that provides data() and size() methods can be passed in. + template + span (const VectorOrArray& v) : span (v.data(), v.size()) {} + + /// Returns true if the span is empty. + bool empty() const { return s == e; } + + /// Returns the number of elements. + /// The length() and size() methods are equivalent. + size_t size() const { return static_cast (e - s); } + + /// Returns the number of elements. + /// The length() and size() methods are equivalent. + size_t length() const { return static_cast (e - s); } + + /// Returns a raw pointer to the start of the data. + Item* data() const noexcept { return s; } + + const Item& front() const { DISTRHO_SAFE_ASSERT_RETURN (! empty(), _nullValue()); return *s; } + const Item& back() const { DISTRHO_SAFE_ASSERT_RETURN (! empty(), _nullValue()); return *(e - 1); } + Item& front() { DISTRHO_SAFE_ASSERT_RETURN (! empty(), _nullValue()); return *s; } + Item& back() { DISTRHO_SAFE_ASSERT_RETURN (! empty(), _nullValue()); return *(e - 1); } + + const Item& operator[] (size_t index) const { DISTRHO_SAFE_ASSERT_RETURN (index < length(), _nullValue()); return s[index]; } + Item& operator[] (size_t index) { DISTRHO_SAFE_ASSERT_RETURN (index < length(), _nullValue()); return s[index]; } + + /// A handy bonus function for getting a (non-empty) span's tail elements + span tail() const { DISTRHO_SAFE_ASSERT_RETURN (! empty(), _nullValue()); return { s + 1, e }; } + + const Item* begin() const noexcept { return s; } + const Item* end() const noexcept { return e; } + Item* begin() noexcept { return s; } + Item* end() noexcept { return e; } + + /// Helper function to return a std::vector copy of the span's elements. + std::vector::type> createVector() const + { + return std::vector::type> (s, e); + } + + /// Two spans are considered identical if their elements are all comparable + template + bool operator== (const OtherSpan& other) const + { + auto sz = size(); + + if (sz != other.size()) + return false; + + for (decltype (sz) i = 0; i < sz; ++i) + if (s[i] != other.s[i]) + return false; + + return true; + } + + template + bool operator!= (const OtherSpan& other) const { return ! operator== (other); } + +private: + Item* s = {}; + Item* e = {}; + + static inline Item& _nullValue() noexcept { static Item e = {}; return e; } +}; + +} // namespace choc + +#endif // CHOC_SPAN_HEADER_INCLUDED diff --git a/include/midi.hpp b/include/midi.hpp new file mode 100644 index 0000000..dd89ac6 --- /dev/null +++ b/include/midi.hpp @@ -0,0 +1,328 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021-2022 Filipe Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For a full copy of the GNU General Public License see the LICENSE file. + */ + +/** + * This file is an edited version of VCVRack's midi.hpp + * Copyright (C) 2016-2021 VCV. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + */ + +#pragma once +#include +#include + +#include + +#include +#include + +#include "choc/choc_SmallVector.h" + +namespace rack { +/** Abstraction for all MIDI drivers in Rack */ +namespace midi { + + +struct Message { + /** Initialized to 3 empty bytes. */ + choc::SmallVector bytes; + /** The Engine frame timestamp of the Message. + For output messages, the frame when the message was generated. + For input messages, the frame when it is intended to be processed. + -1 for undefined, to be sent or processed immediately. + */ + int64_t frame = -1; + + Message() {} + + int getSize() const { + return bytes.size(); + } + void setSize(int size) { + bytes.resize(size); + } + + uint8_t getChannel() const { + if (bytes.size() < 1) + return 0; + return bytes[0] & 0xf; + } + void setChannel(uint8_t channel) { + if (bytes.size() < 1) + return; + bytes[0] = (bytes[0] & 0xf0) | (channel & 0xf); + } + + uint8_t getStatus() const { + if (bytes.size() < 1) + return 0; + return bytes[0] >> 4; + } + void setStatus(uint8_t status) { + if (bytes.size() < 1) + return; + bytes[0] = (bytes[0] & 0xf) | (status << 4); + } + + uint8_t getNote() const { + if (bytes.size() < 2) + return 0; + return bytes[1]; + } + void setNote(uint8_t note) { + if (bytes.size() < 2) + return; + bytes[1] = note & 0x7f; + } + + uint8_t getValue() const { + if (bytes.size() < 3) + return 0; + return bytes[2]; + } + void setValue(uint8_t value) { + if (bytes.size() < 3) + return; + bytes[2] = value & 0x7f; + } + + std::string toString() const; + + int64_t getFrame() const { + return frame; + } + + void setFrame(int64_t frame) { + this->frame = frame; + } +}; + +//////////////////// +// Driver +//////////////////// + +struct InputDevice; +struct Input; +struct OutputDevice; +struct Output; + +/** Wraps a MIDI driver API containing any number of MIDI devices. +*/ +struct Driver { + virtual ~Driver() {} + /** Returns the name of the driver. E.g. "ALSA". */ + virtual std::string getName() { + return ""; + } + /** Returns a list of all input device IDs that can be subscribed to. */ + virtual std::vector getInputDeviceIds() { + return {}; + } + /** Returns the default device to use when the driver is selected, or -1 for none. */ + virtual int getDefaultInputDeviceId() { + return -1; + } + /** Returns the name of an input device without obtaining it. */ + virtual std::string getInputDeviceName(int deviceId) { + return ""; + } + /** Adds the given port as a reference holder of a device and returns the it. + Creates the Device if no ports are subscribed before calling. + */ + virtual InputDevice* subscribeInput(int deviceId, Input* input) { + return NULL; + } + /** Removes the give port as a reference holder of a device. + Deletes the Device if no ports are subscribed after calling. + */ + virtual void unsubscribeInput(int deviceId, Input* input) {} + + // The following behave identically as the above methods except for outputs. + + virtual std::vector getOutputDeviceIds() { + return {}; + } + virtual int getDefaultOutputDeviceId() { + return -1; + } + virtual std::string getOutputDeviceName(int deviceId) { + return ""; + } + virtual OutputDevice* subscribeOutput(int deviceId, Output* output) { + return NULL; + } + virtual void unsubscribeOutput(int deviceId, Output* output) {} +}; + +//////////////////// +// Device +//////////////////// + +/** A single MIDI device of a driver API. + +Modules and the UI should not interact with this API directly. Use Port instead. + +Methods throw `rack::Exception` if the driver API has an exception. +*/ +struct Device { + virtual ~Device() {} + virtual std::string getName() { + return ""; + } +}; + +struct InputDevice : Device { + std::set subscribed; + /** Not public. Use Driver::subscribeInput(). */ + void subscribe(Input* input); + /** Not public. Use Driver::unsubscribeInput(). */ + void unsubscribe(Input* input); + /** Called when a MIDI message is received from the device. */ + void onMessage(const Message& message); +}; + +struct OutputDevice : Device { + std::set subscribed; + /** Not public. Use Driver::subscribeOutput(). */ + void subscribe(Output* output); + /** Not public. Use Driver::unsubscribeOutput(). */ + void unsubscribe(Output* output); + /** Sends a MIDI message to the device. */ + virtual void sendMessage(const Message& message) {} +}; + +//////////////////// +// Port +//////////////////// + +/** A handle to a Device, typically owned by modules to have shared access to a single Device. + +All Port methods safely wrap Drivers methods. +That is, if the active Device throws a `rack::Exception`, it is caught and logged inside all Port methods, so they do not throw exceptions. + +Use Input or Output subclasses in your module, not Port directly. +*/ +struct Port { + /** For MIDI output, the channel to automatically set outbound messages. + If -1, the channel is not overwritten and must be set by MIDI generator. + + For MIDI input, messages will be filtered by the channel. + If -1, all MIDI channels pass through. + */ + int channel = -1; + + // private + int driverId = -1; + int deviceId = -1; + /** Not owned */ + Driver* driver = NULL; + Device* device = NULL; + Context* context; + + Port(); + virtual ~Port(); + + Driver* getDriver(); + int getDriverId(); + void setDriverId(int driverId); + + Device* getDevice(); + virtual std::vector getDeviceIds() = 0; + virtual int getDefaultDeviceId() = 0; + int getDeviceId(); + virtual void setDeviceId(int deviceId) = 0; + virtual std::string getDeviceName(int deviceId) = 0; + + virtual std::vector getChannels() = 0; + int getChannel(); + void setChannel(int channel); + std::string getChannelName(int channel); + + json_t* toJson(); + void fromJson(json_t* rootJ); +}; + + +struct Input : Port { + /** Not owned */ + InputDevice* inputDevice = NULL; + + Input(); + ~Input(); + void reset(); + + std::vector getDeviceIds() override; + int getDefaultDeviceId() override; + void setDeviceId(int deviceId) override; + std::string getDeviceName(int deviceId) override; + + std::vector getChannels() override; + + virtual void onMessage(const Message& message) {} +}; + + +/** An Input port that stores incoming MIDI messages and releases them when ready according to their frame timestamp. +*/ +struct InputQueue : Input { + struct Internal; + Internal* internal; + + InputQueue(); + ~InputQueue(); + void onMessage(const Message& message) override; + /** Pops and returns the next message (by setting `messageOut`) if its frame timestamp is `maxFrame` or earlier. + Returns whether a message was returned. + */ + bool tryPop(Message* messageOut, int64_t maxFrame); + size_t size(); +}; + + +struct Output : Port { + /** Not owned */ + OutputDevice* outputDevice = NULL; + + Output(); + ~Output(); + void reset(); + + std::vector getDeviceIds() override; + int getDefaultDeviceId() override; + void setDeviceId(int deviceId) override; + std::string getDeviceName(int deviceId) override; + + std::vector getChannels() override; + + void sendMessage(const Message& message); +}; + + +PRIVATE void init(); +PRIVATE void destroy(); +/** Registers a new MIDI driver. Takes pointer ownership. */ +void addDriver(int driverId, Driver* driver); +std::vector getDriverIds(); +Driver* getDriver(int driverId); + + +} // namespace midi +} // namespace rack