1 /*
2  * Copyright (C) 2019-2020 Rerrah
3  *
4  * Permission is hereby granted, free of charge, to any person
5  * obtaining a copy of this software and associated documentation
6  * files (the "Software"), to deal in the Software without
7  * restriction, including without limitation the rights to use,
8  * copy, modify, merge, publish, distribute, sublicense, and/or sell
9  * copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following
11  * conditions:
12  *
13  * The above copyright notice and this permission notice shall be
14  * included in all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23  * OTHER DEALINGS IN THE SOFTWARE.
24  */
25 
26 #include "midi.hpp"
27 #include "midi_def.h"
28 #include <stdio.h>
29 
30 std::unique_ptr<MidiInterface> MidiInterface::instance_;
31 
instance()32 MidiInterface &MidiInterface::instance()
33 {
34 	if (instance_)
35 		return *instance_;
36 
37 	MidiInterface *out = new MidiInterface;
38 	instance_.reset(out);
39 	return *out;
40 }
41 
MidiInterface()42 MidiInterface::MidiInterface()
43 	  : hasOpenInputPort_(false)
44 {
45 	switchApi(RtMidi::RTMIDI_DUMMY);
46 }
47 
~MidiInterface()48 MidiInterface::~MidiInterface()
49 {
50 }
51 
currentApi() const52 RtMidi::Api MidiInterface::currentApi() const
53 {
54 	return inputClient_->getCurrentApi();
55 }
56 
currentApiName() const57 std::string MidiInterface::currentApiName() const
58 {
59 	return RtMidi::getApiDisplayName(currentApi());
60 }
61 
getAvailableApi() const62 std::vector<std::string> MidiInterface::getAvailableApi() const
63 {
64 	std::vector<RtMidi::Api> apis;
65 	RtMidi::getCompiledApi(apis);
66 	std::vector<std::string> list;
67 	for (const auto& apiAvailable : apis)
68 		list.push_back(RtMidi::getApiDisplayName(apiAvailable));
69 	return list;
70 }
71 
switchApi(std::string api,std::string * errDetail)72 bool MidiInterface::switchApi(std::string api, std::string* errDetail)
73 {
74 	std::vector<RtMidi::Api> apis;
75 	RtMidi::getCompiledApi(apis);
76 
77 	for (const auto& apiAvailable : apis) {
78 		if (api == RtMidi::getApiDisplayName(apiAvailable)) {
79 			return switchApi(apiAvailable, errDetail);
80 		}
81 	}
82 	return switchApi(RtMidi::RTMIDI_DUMMY, errDetail);
83 }
84 
switchApi(RtMidi::Api api,std::string * errDetail)85 bool MidiInterface::switchApi(RtMidi::Api api, std::string* errDetail)
86 {
87 	if (inputClient_ && api == inputClient_->getCurrentApi())
88 		return true;
89 
90 	RtMidiIn *inputClient = nullptr;
91 	try {
92 		inputClient = new RtMidiIn(api, MIDI_INP_CLIENT_NAME, MidiBufferSize);
93 		if (errDetail) *errDetail = "";
94 	}
95 	catch (RtMidiError &error) {
96 		error.printMessage();
97 		if (errDetail) *errDetail = error.getMessage();
98 	}
99 	bool res = (inputClient != nullptr);
100 	if (!inputClient)
101 		inputClient = new RtMidiIn(RtMidi::RTMIDI_DUMMY, MIDI_INP_CLIENT_NAME, MidiBufferSize);
102 
103 	inputClient->ignoreTypes(MIDI_INP_IGNORE_SYSEX, MIDI_INP_IGNORE_TIME, MIDI_INP_IGNORE_SENSE);
104 	inputClient_.reset(inputClient);
105 	inputClient->setCallback(&onMidiInput, this);
106 	hasOpenInputPort_ = false;
107 
108 	return res;
109 }
110 
supportsVirtualPort() const111 bool MidiInterface::supportsVirtualPort() const
112 {
113 	switch (currentApi()) {
114 	case RtMidi::MACOSX_CORE: case RtMidi::LINUX_ALSA: case RtMidi::UNIX_JACK:
115 		return true;
116 	default:
117 		return false;
118 	}
119 }
120 
supportsVirtualPort(std::string api) const121 bool MidiInterface::supportsVirtualPort(std::string api) const
122 {
123 	std::vector<RtMidi::Api> apis;
124 	RtMidi::getCompiledApi(apis);
125 	RtMidi::Api apiType = RtMidi::RTMIDI_DUMMY;
126 	for (const auto& apiAvailable : apis) {
127 		if (api == RtMidi::getApiDisplayName(apiAvailable)) {
128 			apiType = apiAvailable;
129 			break;
130 		}
131 	}
132 
133 	switch (apiType) {
134 	case RtMidi::MACOSX_CORE: case RtMidi::LINUX_ALSA: case RtMidi::UNIX_JACK:
135 		return true;
136 	default:
137 		return false;
138 	}
139 }
140 
getRealInputPorts()141 std::vector<std::string> MidiInterface::getRealInputPorts()
142 {
143 	RtMidiIn &client = *inputClient_;
144 	unsigned count = client.getPortCount();
145 
146 	std::vector<std::string> ports;
147 	ports.reserve(count);
148 
149 	for (unsigned i = 0; i < count; ++i)
150 		ports.push_back(client.getPortName(i));
151 	return ports;
152 }
153 
getRealInputPorts(const std::string & api)154 std::vector<std::string> MidiInterface::getRealInputPorts(const std::string& api)
155 {
156 	std::vector<RtMidi::Api> apis;
157 	RtMidi::getCompiledApi(apis);
158 
159 	RtMidi::Api apiType = RtMidi::RTMIDI_DUMMY;
160 	for (const auto& apiAvailable : apis) {
161 		if (api == RtMidi::getApiDisplayName(apiAvailable)) {
162 			apiType = apiAvailable;
163 			break;
164 		}
165 	}
166 
167 	std::vector<std::string> ports;
168 	try {
169 		auto client = std::make_unique<RtMidiIn>(apiType);
170 		unsigned count = client->getPortCount();
171 		ports.reserve(count);
172 		for (unsigned i = 0; i < count; ++i)
173 			ports.push_back(client->getPortName(i));
174 	}
175 	catch (RtMidiError& error) {
176 		error.printMessage();
177 	}
178 	return ports;
179 }
180 
closeInputPort()181 void MidiInterface::closeInputPort()
182 {
183 	RtMidiIn &client = *inputClient_;
184 	if (hasOpenInputPort_) {
185 		client.closePort();
186 		hasOpenInputPort_ = false;
187 	}
188 }
189 
openInputPort(unsigned port,std::string * errDetail)190 bool MidiInterface::openInputPort(unsigned port, std::string* errDetail)
191 {
192 	try {
193 	RtMidiIn &client = *inputClient_;
194 	closeInputPort();
195 
196 	std::string name = MIDI_INP_PORT_NAME;
197 	if (port == ~0u) {
198 		client.openVirtualPort(name);
199 		hasOpenInputPort_ = true;
200 	}
201 	else {
202 		client.openPort(port, name);
203 		hasOpenInputPort_ = client.isPortOpen();
204 	}
205 	if (errDetail) *errDetail = "";
206 	return true;
207 	}
208 	catch (RtMidiError& error) {
209 		if (errDetail) *errDetail = error.getMessage();
210 		hasOpenInputPort_ = false;
211 		return false;
212 	}
213 }
214 
openInputPortByName(const std::string & portName,std::string * errDetail)215 bool MidiInterface::openInputPortByName(const std::string &portName, std::string* errDetail)
216 {
217 	std::vector<std::string> ports = getRealInputPorts();
218 
219 	for (unsigned i = 0, n = ports.size(); i < n; ++i) {
220 		if (ports[i] == portName) {
221 			return openInputPort(i, errDetail);;
222 		}
223 	}
224 
225 	if (errDetail) *errDetail = "There is no port such the name.";
226 	return false;
227 }
228 
installInputHandler(InputHandler * handler,void * user_data)229 void MidiInterface::installInputHandler(InputHandler *handler, void *user_data)
230 {
231 	std::lock_guard<std::mutex> lock(inputHandlersMutex_);
232 	inputHandlers_.push_back(std::make_pair(handler, user_data));
233 }
234 
uninstallInputHandler(InputHandler * handler,void * user_data)235 void MidiInterface::uninstallInputHandler(InputHandler *handler, void *user_data)
236 {
237 	std::lock_guard<std::mutex> lock(inputHandlersMutex_);
238 	for (size_t i = 0, n = inputHandlers_.size(); i < n; ++i) {
239 		bool match = inputHandlers_[i].first == handler &&
240 					 inputHandlers_[i].second == user_data;
241 		if (match) {
242 			inputHandlers_.erase(inputHandlers_.begin() + i);
243 			return;
244 		}
245 	}
246 }
247 
onMidiInput(double timestamp,std::vector<unsigned char> * message,void * user_data)248 void MidiInterface::onMidiInput(double timestamp, std::vector<unsigned char> *message, void *user_data)
249 {
250 	MidiInterface *self = reinterpret_cast<MidiInterface *>(user_data);
251 
252 	std::unique_lock<std::mutex> lock(self->inputHandlersMutex_, std::try_to_lock);
253 	if (!lock.owns_lock())
254 		return;
255 
256 	const uint8_t *msg = message->data();
257 	size_t len = message->size();
258 
259 	for (size_t i = 0, n = self->inputHandlers_.size(); i < n; ++i)
260 		self->inputHandlers_[i].first(timestamp, msg, len, self->inputHandlers_[i].second);
261 }
262