You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

402 lines
9.9KB

  1. #include <vector>
  2. #include <map>
  3. #include <queue>
  4. #include <thread>
  5. #include <mutex>
  6. #include <condition_variable>
  7. #pragma GCC diagnostic push
  8. #ifndef __clang__
  9. #pragma GCC diagnostic ignored "-Wsuggest-override"
  10. #endif
  11. #include <rtmidi/RtMidi.h>
  12. #pragma GCC diagnostic pop
  13. #include <rtmidi.hpp>
  14. #include <midi.hpp>
  15. #include <string.hpp>
  16. #include <system.hpp>
  17. #include <context.hpp>
  18. #include <engine/Engine.hpp>
  19. namespace rack {
  20. static void rtMidiErrorCallback(RtMidiError::Type type, const std::string& errorText, void* userData) {
  21. // Do nothing
  22. }
  23. struct RtMidiInputDevice : midi::InputDevice {
  24. RtMidiIn* rtMidiIn;
  25. std::string name;
  26. RtMidiInputDevice(int driverId, int deviceId) {
  27. try {
  28. rtMidiIn = new RtMidiIn((RtMidi::Api) driverId, "VCV Rack");
  29. }
  30. catch (RtMidiError& e) {
  31. throw Exception("Failed to create RtMidi input driver %d: %s", driverId, e.what());
  32. }
  33. rtMidiIn->setErrorCallback(rtMidiErrorCallback);
  34. rtMidiIn->ignoreTypes(false, false, false);
  35. rtMidiIn->setCallback(midiInputCallback, this);
  36. try {
  37. name = rtMidiIn->getPortName(deviceId);
  38. }
  39. catch (RtMidiError& e) {
  40. throw Exception("Failed to get RtMidi input device name: %s", e.what());
  41. }
  42. try {
  43. rtMidiIn->openPort(deviceId, "VCV Rack input");
  44. }
  45. catch (RtMidiError& e) {
  46. throw Exception("Failed to open RtMidi input device: %s", e.what());
  47. }
  48. }
  49. ~RtMidiInputDevice() {
  50. // This does not throw for any driver API
  51. rtMidiIn->closePort();
  52. delete rtMidiIn;
  53. }
  54. std::string getName() override {
  55. return name;
  56. }
  57. static void midiInputCallback(double timeStamp, std::vector<unsigned char>* message, void* userData) {
  58. if (!message)
  59. return;
  60. if (!userData)
  61. return;
  62. RtMidiInputDevice* midiInputDevice = (RtMidiInputDevice*) userData;
  63. if (!midiInputDevice)
  64. return;
  65. midi::Message msg;
  66. msg.bytes = std::vector<uint8_t>(message->begin(), message->end());
  67. // Don't set msg.frame from timeStamp here, because it's set in onMessage().
  68. midiInputDevice->onMessage(msg);
  69. }
  70. };
  71. struct RtMidiOutputDevice : midi::OutputDevice {
  72. RtMidiOut* rtMidiOut;
  73. std::string name;
  74. struct MessageSchedule {
  75. midi::Message message;
  76. double timestamp;
  77. bool operator<(const MessageSchedule& other) const {
  78. return timestamp > other.timestamp;
  79. }
  80. };
  81. std::priority_queue<MessageSchedule, std::vector<MessageSchedule>> messageQueue;
  82. std::thread thread;
  83. std::mutex mutex;
  84. std::condition_variable cv;
  85. bool stopped = false;
  86. RtMidiOutputDevice(int driverId, int deviceId) {
  87. try {
  88. rtMidiOut = new RtMidiOut((RtMidi::Api) driverId, "VCV Rack");
  89. }
  90. catch (RtMidiError& e) {
  91. throw Exception("Failed to create RtMidi output driver %d: %s", driverId, e.what());
  92. }
  93. rtMidiOut->setErrorCallback(rtMidiErrorCallback);
  94. try {
  95. name = rtMidiOut->getPortName(deviceId);
  96. }
  97. catch (RtMidiError& e) {
  98. throw Exception("Failed to get RtMidi output device name: %s", e.what());
  99. }
  100. try {
  101. rtMidiOut->openPort(deviceId, "VCV Rack output");
  102. }
  103. catch (RtMidiError& e) {
  104. throw Exception("Failed to get RtMidi output device name: %s", e.what());
  105. }
  106. startThread();
  107. }
  108. ~RtMidiOutputDevice() {
  109. stopThread();
  110. // This does not throw for any driver API
  111. rtMidiOut->closePort();
  112. delete rtMidiOut;
  113. }
  114. std::string getName() override {
  115. return name;
  116. }
  117. void sendMessage(const midi::Message& message) override {
  118. // If frame is undefined, send message immediately
  119. if (message.frame < 0) {
  120. sendMessageNow(message);
  121. return;
  122. }
  123. // Schedule message to be sent by worker thread
  124. MessageSchedule ms;
  125. ms.message = message;
  126. // Compute time in next Engine block to send message
  127. double deltaTime = (message.frame - APP->engine->getBlockFrame()) * APP->engine->getSampleTime();
  128. ms.timestamp = APP->engine->getBlockTime() + deltaTime;
  129. std::lock_guard<decltype(mutex)> lock(mutex);
  130. messageQueue.push(ms);
  131. cv.notify_one();
  132. }
  133. // Consumer thread methods
  134. void startThread() {
  135. thread = std::thread(&RtMidiOutputDevice::runThread, this);
  136. }
  137. void runThread() {
  138. std::unique_lock<decltype(mutex)> lock(mutex);
  139. while (!stopped) {
  140. if (messageQueue.empty()) {
  141. // No messages. Wait on the CV to be notified.
  142. cv.wait(lock);
  143. }
  144. else {
  145. // Get earliest message
  146. const MessageSchedule& ms = messageQueue.top();
  147. double duration = ms.timestamp - system::getTime();
  148. // If we need to wait, release the lock and wait for the timeout, or if the CV is notified.
  149. // This correctly handles MIDI messages with no timestamp, because duration will be NAN.
  150. if (duration > 0) {
  151. if (cv.wait_for(lock, std::chrono::duration<double>(duration)) != std::cv_status::timeout)
  152. continue;
  153. }
  154. // Send and remove from queue
  155. sendMessageNow(ms.message);
  156. messageQueue.pop();
  157. }
  158. }
  159. }
  160. void sendMessageNow(const midi::Message& message) {
  161. try {
  162. rtMidiOut->sendMessage(message.bytes.data(), message.bytes.size());
  163. }
  164. catch (RtMidiError& e) {
  165. // Ignore error
  166. }
  167. }
  168. void stopThread() {
  169. {
  170. std::lock_guard<decltype(mutex)> lock(mutex);
  171. stopped = true;
  172. cv.notify_one();
  173. }
  174. thread.join();
  175. }
  176. };
  177. struct RtMidiDriver : midi::Driver {
  178. int driverId;
  179. /** Just for querying MIDI driver information */
  180. RtMidiIn* rtMidiIn;
  181. RtMidiOut* rtMidiOut;
  182. std::map<int, RtMidiInputDevice*> inputDevices;
  183. std::map<int, RtMidiOutputDevice*> outputDevices;
  184. RtMidiDriver(int driverId) {
  185. this->driverId = driverId;
  186. try {
  187. rtMidiIn = new RtMidiIn((RtMidi::Api) driverId);
  188. }
  189. catch (RtMidiError& e) {
  190. throw Exception("Failed to create RtMidi input driver %d: %s", driverId, e.what());
  191. }
  192. rtMidiIn->setErrorCallback(rtMidiErrorCallback);
  193. try {
  194. rtMidiOut = new RtMidiOut((RtMidi::Api) driverId);
  195. }
  196. catch (RtMidiError& e) {
  197. throw Exception("Failed to create RtMidi output driver %d: %s", driverId, e.what());
  198. }
  199. rtMidiOut->setErrorCallback(rtMidiErrorCallback);
  200. }
  201. ~RtMidiDriver() {
  202. assert(inputDevices.empty());
  203. assert(outputDevices.empty());
  204. // This does not throw for any driver API
  205. delete rtMidiIn;
  206. delete rtMidiOut;
  207. }
  208. std::string getName() override {
  209. switch (driverId) {
  210. case RtMidi::UNSPECIFIED: return "Unspecified";
  211. case RtMidi::MACOSX_CORE: return "Core MIDI";
  212. case RtMidi::LINUX_ALSA: return "ALSA";
  213. case RtMidi::UNIX_JACK: return "JACK";
  214. case RtMidi::WINDOWS_MM: return "Windows MIDI";
  215. case RtMidi::RTMIDI_DUMMY: return "Dummy MIDI";
  216. default: return "";
  217. }
  218. }
  219. std::vector<int> getInputDeviceIds() override {
  220. // TODO The IDs unfortunately jump around in RtMidi. Is there a way to keep them constant when a MIDI device is added/removed?
  221. int count;
  222. try {
  223. count = rtMidiIn->getPortCount();
  224. }
  225. catch (RtMidiError& e) {
  226. throw Exception("Failed to get RtMidi input device count: %s", e.what());
  227. }
  228. std::vector<int> deviceIds;
  229. for (int i = 0; i < count; i++)
  230. deviceIds.push_back(i);
  231. return deviceIds;
  232. }
  233. std::string getInputDeviceName(int deviceId) override {
  234. if (deviceId < 0)
  235. return "";
  236. try {
  237. return rtMidiIn->getPortName(deviceId);
  238. }
  239. catch (RtMidiError& e) {
  240. throw Exception("Failed to get RtMidi input device name: %s", e.what());
  241. }
  242. }
  243. midi::InputDevice* subscribeInput(int deviceId, midi::Input* input) override {
  244. if (!(0 <= deviceId && deviceId < (int) rtMidiIn->getPortCount()))
  245. return NULL;
  246. RtMidiInputDevice* device = get(inputDevices, deviceId, NULL);
  247. if (!device) {
  248. try {
  249. inputDevices[deviceId] = device = new RtMidiInputDevice(driverId, deviceId);
  250. }
  251. catch (RtMidiError& e) {
  252. throw Exception("Failed to create RtMidi input device: %s", e.what());
  253. }
  254. }
  255. device->subscribe(input);
  256. return device;
  257. }
  258. void unsubscribeInput(int deviceId, midi::Input* input) override {
  259. auto it = inputDevices.find(deviceId);
  260. if (it == inputDevices.end())
  261. return;
  262. RtMidiInputDevice* device = it->second;
  263. device->unsubscribe(input);
  264. // Destroy device if nothing is subscribed anymore
  265. if (device->subscribed.empty()) {
  266. inputDevices.erase(it);
  267. try {
  268. delete device;
  269. }
  270. catch (RtMidiError& e) {
  271. throw Exception("Failed to delete RtMidi input device: %s", e.what());
  272. }
  273. }
  274. }
  275. std::vector<int> getOutputDeviceIds() override {
  276. // TODO The IDs unfortunately jump around in RtMidi. Is there a way to keep them constant when a MIDI device is added/removed?
  277. int count;
  278. try {
  279. count = rtMidiOut->getPortCount();
  280. }
  281. catch (RtMidiError& e) {
  282. throw Exception("Failed to get RtMidi output device count: %s", e.what());
  283. }
  284. std::vector<int> deviceIds;
  285. for (int i = 0; i < count; i++)
  286. deviceIds.push_back(i);
  287. return deviceIds;
  288. }
  289. std::string getOutputDeviceName(int deviceId) override {
  290. if (deviceId < 0)
  291. return "";
  292. try {
  293. return rtMidiOut->getPortName(deviceId);
  294. }
  295. catch (RtMidiError& e) {
  296. throw Exception("Failed to get RtMidi output device count: %s", e.what());
  297. }
  298. }
  299. midi::OutputDevice* subscribeOutput(int deviceId, midi::Output* output) override {
  300. if (!(0 <= deviceId && deviceId < (int) rtMidiOut->getPortCount()))
  301. return NULL;
  302. RtMidiOutputDevice* device = get(outputDevices, deviceId, NULL);
  303. if (!device) {
  304. try {
  305. outputDevices[deviceId] = device = new RtMidiOutputDevice(driverId, deviceId);
  306. }
  307. catch (RtMidiError& e) {
  308. throw Exception("Failed to create RtMidi output device: %s", e.what());
  309. }
  310. }
  311. device->subscribe(output);
  312. return device;
  313. }
  314. void unsubscribeOutput(int deviceId, midi::Output* output) override {
  315. auto it = outputDevices.find(deviceId);
  316. if (it == outputDevices.end())
  317. return;
  318. RtMidiOutputDevice* device = it->second;
  319. device->unsubscribe(output);
  320. // Destroy device if nothing is subscribed anymore
  321. if (device->subscribed.empty()) {
  322. outputDevices.erase(it);
  323. try {
  324. delete device;
  325. }
  326. catch (RtMidiError& e) {
  327. throw Exception("Failed to delete RtMidi output device: %s", e.what());
  328. }
  329. }
  330. }
  331. };
  332. void rtmidiInit() {
  333. std::vector<RtMidi::Api> rtApis;
  334. RtMidi::getCompiledApi(rtApis);
  335. for (RtMidi::Api api : rtApis) {
  336. int driverId = (int) api;
  337. midi::Driver* driver = new RtMidiDriver(driverId);
  338. midi::addDriver(driverId, driver);
  339. }
  340. }
  341. } // namespace rack