/* * MidiInOut.h * ----------- * Purpose: A plugin for sending and receiving MIDI data. * Notes : (currently none) * Authors: Johannes Schultz (OpenMPT Devs) * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #pragma once #include "openmpt/all/BuildSettings.hpp" #include "mpt/mutex/mutex.hpp" #include "../../common/mptTime.h" #include "../../soundlib/plugins/PlugInterface.h" #include #include #include OPENMPT_NAMESPACE_BEGIN class MidiDevice { public: using ID = decltype(RtMidiIn().getPortCount()); static constexpr ID NO_MIDI_DEVICE = ID(-1); RtMidi &stream; std::string name; // Charset::UTF8 ID index = NO_MIDI_DEVICE; public: MidiDevice(RtMidi &stream) : stream(stream) , name("") { } std::string GetPortName(ID port); // Charset::UTF8 }; class MidiInOut final : public IMidiPlugin { friend class MidiInOutEditor; protected: enum { kInputParameter = 0, kOutputParameter = 1, kNumPrograms = 1, kNumParams = 2, kNoDevice = MidiDevice::NO_MIDI_DEVICE, kMaxDevices = 65536, // Should be a power of 2 to avoid rounding errors. }; // MIDI queue entry with small storage optimiziation. // This optimiziation is going to be used for all messages that OpenMPT can send internally, // but SysEx messages received from other plugins may be longer. class Message { public: double m_time; size_t m_size; unsigned char *m_message = nullptr; protected: std::array m_msgSmall; public: Message(double time, const void *data, size_t size) : m_time(time) , m_size(size) { if(size > m_msgSmall.size()) m_message = new unsigned char[size]; else m_message = m_msgSmall.data(); std::memcpy(m_message, data, size); } Message(const Message &) = delete; Message & operator=(const Message &) = delete; Message(double time, unsigned char msg) noexcept : Message(time, &msg, 1) { } Message(Message &&other) noexcept : m_time(other.m_time) , m_size(other.m_size) , m_message(other.m_message) , m_msgSmall(other.m_msgSmall) { other.m_message = nullptr; if(m_size <= m_msgSmall.size()) m_message = m_msgSmall.data(); } ~Message() { if(m_size > m_msgSmall.size()) delete[] m_message; } Message& operator= (Message &&other) noexcept { m_time = other.m_time; m_size = other.m_size; m_message = (m_size <= m_msgSmall.size()) ? m_msgSmall.data() : other.m_message; m_msgSmall = other.m_msgSmall; other.m_message = nullptr; return *this; } }; std::string m_chunkData; // Storage for GetChunk std::deque m_outQueue; // Latency-compensated output std::vector m_bufferedInput; // For receiving long SysEx messages mpt::mutex m_mutex; double m_nextClock = 0.0; // Remaining samples until next MIDI clock tick should be sent double m_latency = 0.0; // User-adjusted latency in seconds // I/O device settings Util::MultimediaClock m_clock; RtMidiIn m_midiIn; RtMidiOut m_midiOut; MidiDevice m_inputDevice; MidiDevice m_outputDevice; bool m_sendTimingInfo = true; #ifdef MODPLUG_TRACKER CString m_programName; #endif public: static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct); MidiInOut(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct); ~MidiInOut(); // Translate a VST parameter to an RtMidi device ID static MidiDevice::ID ParameterToDeviceID(float value) { return static_cast(value * static_cast(kMaxDevices)) - 1; } // Translate a RtMidi device ID to a VST parameter static float DeviceIDToParameter(MidiDevice::ID index) { return static_cast(index + 1) / static_cast(kMaxDevices); } ///////////////////////////////////////////////// // Destroy the plugin void Release() final { delete this; } int32 GetUID() const final { return 'MMID'; } int32 GetVersion() const final { return 2; } void Idle() final { } uint32 GetLatency() const final; int32 GetNumPrograms() const final { return kNumPrograms; } int32 GetCurrentProgram() final { return 0; } void SetCurrentProgram(int32) final { } PlugParamIndex GetNumParameters() const final { return kNumParams; } void SetParameter(PlugParamIndex paramindex, PlugParamValue paramvalue) final; PlugParamValue GetParameter(PlugParamIndex nIndex) final; // Save parameters for storing them in a module file void SaveAllParameters() final; // Restore parameters from module file void RestoreAllParameters(int32 program) final; void Process(float *pOutL, float *pOutR, uint32 numFrames) final; // Render silence and return the highest resulting output level float RenderSilence(uint32) final{ return 0; } bool MidiSend(uint32 midiCode) final; bool MidiSysexSend(mpt::const_byte_span sysex) final; void HardAllNotesOff() final; // Modify parameter by given amount. Only needs to be re-implemented if plugin architecture allows this to be performed atomically. void Resume() final; void Suspend() final; // Tell the plugin that there is a discontinuity between the previous and next render call (e.g. aftert jumping around in the module) void PositionChanged() final; void Bypass(bool bypass = true) final; bool IsInstrument() const final { return true; } bool CanRecieveMidiEvents() final { return true; } // If false is returned, mixing this plugin can be skipped if its input are currently completely silent. bool ShouldProcessSilence() final { return true; } #ifdef MODPLUG_TRACKER CString GetDefaultEffectName() final { return _T("MIDI Input / Output"); } CString GetParamName(PlugParamIndex param) final; CString GetParamLabel(PlugParamIndex) final{ return CString(); } CString GetParamDisplay(PlugParamIndex param) final; CString GetCurrentProgramName() final { return m_programName; } void SetCurrentProgramName(const CString &name) final { m_programName = name; } CString GetProgramName(int32) final { return m_programName; } virtual CString GetPluginVendor() { return _T("OpenMPT Project"); } bool HasEditor() const final { return true; } protected: CAbstractVstEditor *OpenEditor() final; #endif public: int GetNumInputChannels() const final { return 0; } int GetNumOutputChannels() const final { return 0; } bool ProgramsAreChunks() const final { return true; } ChunkData GetChunk(bool isBank) final; void SetChunk(const ChunkData &chunk, bool isBank) final; protected: // Open a device for input or output. void OpenDevice(MidiDevice::ID newDevice, bool asInputDevice); // Close an active device. void CloseDevice(MidiDevice &device); static void InputCallback(double deltatime, std::vector *message, void *userData) { static_cast(userData)->InputCallback(deltatime, *message); } void InputCallback(double deltatime, std::vector &message); // Calculate the current output timestamp double GetOutputTimestamp() const; }; OPENMPT_NAMESPACE_END