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