aboutsummaryrefslogtreecommitdiff
path: root/Src/external_dependencies/openmpt-trunk/mptrack/plugins/MidiInOut.h
blob: cf8aa86c7d54bc9dead251adcb4a931415e422ee (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
/*
 * 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 <rtmidi/RtMidi.h>
#include <array>
#include <deque>


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("<none>")
	{ }

	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<unsigned char, 32> 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<Message> m_outQueue;              // Latency-compensated output
	std::vector<unsigned char> 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<MidiDevice::ID>(value * static_cast<float>(kMaxDevices)) - 1;
	}

	// Translate a RtMidi device ID to a VST parameter
	static float DeviceIDToParameter(MidiDevice::ID index)
	{
		return static_cast<float>(index + 1) / static_cast<float>(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<unsigned char> *message, void *userData) { static_cast<MidiInOut *>(userData)->InputCallback(deltatime, *message); }
	void InputCallback(double deltatime, std::vector<unsigned char> &message);

	// Calculate the current output timestamp
	double GetOutputTimestamp() const;
};


OPENMPT_NAMESPACE_END