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.

410 lines
10KB

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