Audio plugin host https://kx.studio/carla
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.

midi-to-cv.c 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. /*
  2. * Carla Native Plugins
  3. * Copyright (C) 2012-2019 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 2 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the doc/GPL.txt file.
  16. */
  17. /* This plugin code is based on MOD Devices' midi-to-cv-mono by Bram Giesen and Jarno Verheesen
  18. */
  19. #include "CarlaNative.h"
  20. #include "CarlaMIDI.h"
  21. #include <stdlib.h>
  22. #include <string.h>
  23. // -----------------------------------------------------------------------
  24. #define NUM_NOTESBUFFER 8
  25. typedef enum {
  26. PARAM_OCTAVE = 0,
  27. PARAM_SEMITONE,
  28. PARAM_CENT,
  29. PARAM_RETRIGGER,
  30. PARAM_COUNT
  31. } Midi2CvParams;
  32. typedef struct {
  33. // keep track of active notes
  34. uint8_t activeNotesList[NUM_NOTESBUFFER];
  35. uint8_t reTriggerBuffer[NUM_NOTESBUFFER];
  36. uint8_t triggerIndex;
  37. uint8_t activeNotes;
  38. uint8_t activeVelocity;
  39. uint8_t reTriggered;
  40. size_t notesIndex;
  41. bool activePorts;
  42. // other stuff
  43. bool triggerState;
  44. int notesPressed;
  45. float params[PARAM_COUNT];
  46. } Midi2CvHandle;
  47. static void panic(Midi2CvHandle* const handle)
  48. {
  49. memset(handle->activeNotesList, 200, sizeof(uint8_t)*NUM_NOTESBUFFER);
  50. memset(handle->reTriggerBuffer, 0, sizeof(uint8_t)*NUM_NOTESBUFFER);
  51. handle->triggerIndex = 0;
  52. handle->reTriggered = 200;
  53. handle->activeNotes = 0;
  54. handle->activeVelocity = 0;
  55. handle->activePorts = false;
  56. handle->notesPressed = 0;
  57. handle->notesIndex = 0;
  58. handle->triggerState = false;
  59. }
  60. static void set_status(Midi2CvHandle* const handle, int status)
  61. {
  62. handle->activePorts = status;
  63. handle->triggerState = status;
  64. }
  65. // -----------------------------------------------------------------------
  66. static NativePluginHandle midi2cv_instantiate(const NativeHostDescriptor* host)
  67. {
  68. Midi2CvHandle* const handle = (Midi2CvHandle*)malloc(sizeof(Midi2CvHandle));
  69. if (handle == NULL)
  70. return NULL;
  71. panic(handle);
  72. memset(handle->params, 0, sizeof(float)*PARAM_COUNT);
  73. return handle;
  74. // unused
  75. (void)host;
  76. }
  77. #define handlePtr ((Midi2CvHandle*)handle)
  78. static void midi2cv_cleanup(NativePluginHandle handle)
  79. {
  80. free(handlePtr);
  81. }
  82. static uint32_t midi2cv_get_parameter_count(NativePluginHandle handle)
  83. {
  84. return PARAM_COUNT;
  85. // unused
  86. (void)handle;
  87. }
  88. static const NativeParameter* midi2cv_get_parameter_info(NativePluginHandle handle, uint32_t index)
  89. {
  90. if (index > PARAM_COUNT)
  91. return NULL;
  92. static NativeParameter param;
  93. param.hints = NATIVE_PARAMETER_IS_ENABLED|NATIVE_PARAMETER_IS_AUTOMATABLE;
  94. param.unit = NULL;
  95. param.scalePointCount = 0;
  96. param.scalePoints = NULL;
  97. switch (index)
  98. {
  99. case PARAM_OCTAVE:
  100. param.name = "Octave";
  101. param.hints |= NATIVE_PARAMETER_IS_INTEGER;
  102. param.ranges.def = 0.0f;
  103. param.ranges.min = -3.0f;
  104. param.ranges.max = 3.0f;
  105. param.ranges.step = 1.0f;
  106. param.ranges.stepSmall = 1.0f;
  107. param.ranges.stepLarge = 1.0f;
  108. break;
  109. case PARAM_SEMITONE:
  110. param.name = "Semitone";
  111. param.hints |= NATIVE_PARAMETER_IS_INTEGER;
  112. param.ranges.def = 0.0f;
  113. param.ranges.min = -12.0f;
  114. param.ranges.max = 12.0f;
  115. param.ranges.step = 1.0f;
  116. param.ranges.stepSmall = 1.0f;
  117. param.ranges.stepLarge = 6.0f;
  118. break;
  119. case PARAM_CENT:
  120. param.name = "Cent";
  121. param.hints |= NATIVE_PARAMETER_IS_INTEGER;
  122. param.ranges.def = 0.0f;
  123. param.ranges.min = -100.0f;
  124. param.ranges.max = 100.0f;
  125. param.ranges.step = 10.0f;
  126. param.ranges.stepSmall = 1.0f;
  127. param.ranges.stepLarge = 50.0f;
  128. break;
  129. case PARAM_RETRIGGER:
  130. param.name = "Retrigger";
  131. param.hints |= NATIVE_PARAMETER_IS_BOOLEAN;
  132. param.ranges.def = 0.0f;
  133. param.ranges.min = 0.0f;
  134. param.ranges.max = 1.0f;
  135. param.ranges.step = 1.0f;
  136. param.ranges.stepSmall = 1.0f;
  137. param.ranges.stepLarge = 1.0f;
  138. break;
  139. }
  140. return &param;
  141. // unused
  142. (void)handle;
  143. }
  144. static float midi2cv_get_parameter_value(NativePluginHandle handle, uint32_t index)
  145. {
  146. return handlePtr->params[index];
  147. }
  148. static void midi2cv_set_parameter_value(NativePluginHandle handle, uint32_t index, float value)
  149. {
  150. handlePtr->params[index] = value;
  151. }
  152. static const NativePortRange* midi2cv_get_buffer_port_range(NativePluginHandle handle, uint32_t index, bool isOutput)
  153. {
  154. if (! isOutput)
  155. return NULL;
  156. static NativePortRange npr;
  157. switch (index)
  158. {
  159. case 0:
  160. npr.minimum = 0.0f;
  161. npr.maximum = 9.0f;
  162. return &npr;
  163. case 1:
  164. npr.minimum = 0.0f;
  165. npr.maximum = 10.5f;
  166. return &npr;
  167. case 2:
  168. npr.minimum = 0.0f;
  169. npr.maximum = 10.0f;
  170. return &npr;
  171. default:
  172. return NULL;
  173. }
  174. // unused
  175. (void)handle;
  176. }
  177. static const char* midi2cv_get_buffer_port_name(NativePluginHandle handle, uint32_t index, bool isOutput)
  178. {
  179. if (! isOutput)
  180. return NULL;
  181. switch (index)
  182. {
  183. case 0:
  184. return "Pitch";
  185. case 1:
  186. return "Velocity";
  187. case 2:
  188. return "Gate";
  189. default:
  190. return NULL;
  191. }
  192. // unused
  193. (void)handle;
  194. }
  195. static void midi2cv_activate(NativePluginHandle handle)
  196. {
  197. panic(handlePtr);
  198. }
  199. // FIXME for v3.0, use const for the input buffer
  200. static void midi2cv_process(NativePluginHandle handle,
  201. float** inBuffer, float** outBuffer, uint32_t frames,
  202. const NativeMidiEvent* midiEvents, uint32_t midiEventCount)
  203. {
  204. float* const pitch = outBuffer[0];
  205. float* const velocity = outBuffer[1];
  206. float* const trigger = outBuffer[2];
  207. const float oC = handlePtr->params[PARAM_OCTAVE];
  208. const float sC = handlePtr->params[PARAM_SEMITONE];
  209. const float cC = handlePtr->params[PARAM_CENT];
  210. const bool rC = handlePtr->params[PARAM_RETRIGGER] > 0.5f;
  211. bool retrigger = true;
  212. for (uint32_t i=0; i < midiEventCount; ++i)
  213. {
  214. const NativeMidiEvent* const midiEvent = &midiEvents[i];
  215. if (midiEvent->size <= 1 || midiEvent->size > 3)
  216. continue;
  217. const uint8_t* const mdata = midiEvent->data;
  218. const uint8_t status = MIDI_GET_STATUS_FROM_DATA(mdata);
  219. int storeN = 0;
  220. bool emptySlot = false;
  221. int notesIndex = NUM_NOTESBUFFER - 1;
  222. bool noteFound = false;
  223. switch (status)
  224. {
  225. case MIDI_STATUS_NOTE_ON:
  226. while (!emptySlot && storeN < NUM_NOTESBUFFER)
  227. {
  228. if (handlePtr->activeNotesList[storeN] == 200)
  229. {
  230. handlePtr->activeNotesList[storeN] = mdata[1];
  231. emptySlot = true;
  232. }
  233. storeN++;
  234. }
  235. handlePtr->activeNotes = mdata[1];
  236. handlePtr->activeVelocity = mdata[2];
  237. handlePtr->triggerIndex = (handlePtr->triggerIndex + 1U) % 8U;
  238. handlePtr->reTriggerBuffer[handlePtr->triggerIndex] = 1U;
  239. handlePtr->reTriggered = mdata[1];
  240. break;
  241. case MIDI_STATUS_NOTE_OFF:
  242. handlePtr->notesPressed--;
  243. for (int n = 0; n < NUM_NOTESBUFFER; ++n)
  244. if (mdata[1] == handlePtr->activeNotesList[n])
  245. handlePtr->activeNotesList[n] = 200;
  246. while (!noteFound && notesIndex >= 0)
  247. {
  248. if (handlePtr->activeNotesList[notesIndex] < 200)
  249. {
  250. handlePtr->activeNotes = handlePtr->activeNotesList[notesIndex];
  251. if(retrigger && handlePtr->activeNotes != handlePtr->reTriggered)
  252. {
  253. handlePtr->reTriggered = mdata[1];
  254. }
  255. noteFound = true;
  256. }
  257. notesIndex--;
  258. }
  259. break;
  260. case MIDI_STATUS_CONTROL_CHANGE:
  261. if (mdata[1] == MIDI_CONTROL_ALL_NOTES_OFF)
  262. panic(handlePtr);
  263. break;
  264. }
  265. }
  266. int checked_note = 0;
  267. bool active_notes_found = false;
  268. while (checked_note < NUM_NOTESBUFFER && ! active_notes_found)
  269. {
  270. if (handlePtr->activeNotesList[checked_note] != 200)
  271. active_notes_found = true;
  272. checked_note++;
  273. }
  274. if (active_notes_found)
  275. {
  276. set_status(handlePtr, 1);
  277. }
  278. else
  279. {
  280. set_status(handlePtr, 0);
  281. handlePtr->activeVelocity = 0;
  282. }
  283. for (uint32_t i=0; i<frames; ++i)
  284. {
  285. pitch[i] = (0.0f + (float)((oC) + (sC/12.0f)+(cC/1200.0f)) + ((float)handlePtr->activeNotes * 1/12.0f));
  286. velocity[i] = (0.0f + ((float)handlePtr->activeVelocity * 1/12.0f));
  287. trigger[i] = ((handlePtr->triggerState == true) ? 10.0f : 0.0f);
  288. if (handlePtr->reTriggerBuffer[handlePtr->triggerIndex] == 1 && rC)
  289. {
  290. handlePtr->reTriggerBuffer[handlePtr->triggerIndex] = 0;
  291. trigger[i] = 0.0f;
  292. }
  293. }
  294. return;
  295. // unused
  296. (void)inBuffer;
  297. }
  298. #undef handlePtr
  299. // -----------------------------------------------------------------------
  300. static const NativePluginDescriptor midi2cvDesc = {
  301. .category = NATIVE_PLUGIN_CATEGORY_UTILITY,
  302. .hints = NATIVE_PLUGIN_IS_RTSAFE|NATIVE_PLUGIN_USES_CONTROL_VOLTAGE,
  303. .supports = NATIVE_PLUGIN_SUPPORTS_ALL_SOUND_OFF,
  304. .audioIns = 0,
  305. .audioOuts = 0,
  306. .cvIns = 0,
  307. .cvOuts = 3, // pitch, velocity, gate
  308. .midiIns = 1,
  309. .midiOuts = 0,
  310. .paramIns = PARAM_COUNT,
  311. .paramOuts = 0,
  312. .name = "MIDI to CV",
  313. .label = "midi2cv",
  314. .maker = "falkTX, Bram Giesen, Jarno Verheesen",
  315. .copyright = "GNU GPL v2+",
  316. .instantiate = midi2cv_instantiate,
  317. .cleanup = midi2cv_cleanup,
  318. .get_parameter_count = midi2cv_get_parameter_count,
  319. .get_parameter_info = midi2cv_get_parameter_info,
  320. .get_parameter_value = midi2cv_get_parameter_value,
  321. .get_midi_program_count = NULL,
  322. .get_midi_program_info = NULL,
  323. .set_parameter_value = midi2cv_set_parameter_value,
  324. .set_midi_program = NULL,
  325. .set_custom_data = NULL,
  326. .get_buffer_port_name = midi2cv_get_buffer_port_name,
  327. .get_buffer_port_range = midi2cv_get_buffer_port_range,
  328. .ui_show = NULL,
  329. .ui_idle = NULL,
  330. .ui_set_parameter_value = NULL,
  331. .ui_set_midi_program = NULL,
  332. .ui_set_custom_data = NULL,
  333. .activate = midi2cv_activate,
  334. .deactivate = NULL,
  335. .process = midi2cv_process,
  336. .get_state = NULL,
  337. .set_state = NULL,
  338. .dispatcher = NULL,
  339. .render_inline_display = NULL
  340. };
  341. // -----------------------------------------------------------------------
  342. void carla_register_native_plugin_midi2cv(void);
  343. void carla_register_native_plugin_midi2cv(void)
  344. {
  345. carla_register_native_plugin(&midi2cvDesc);
  346. }
  347. // -----------------------------------------------------------------------