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.

390 lines
11KB

  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_AUTOMABLE;
  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 char* midi2cv_get_buffer_port_name(NativePluginHandle handle, uint32_t index, bool isOutput)
  153. {
  154. if (! isOutput)
  155. return NULL;
  156. switch (index)
  157. {
  158. case 0:
  159. return "Pitch";
  160. case 1:
  161. return "Velocity";
  162. case 2:
  163. return "Trigger";
  164. default:
  165. return NULL;
  166. }
  167. // unused
  168. (void)handle;
  169. }
  170. static void midi2cv_activate(NativePluginHandle handle)
  171. {
  172. panic(handlePtr);
  173. }
  174. // FIXME for v3.0, use const for the input buffer
  175. static void midi2cv_process(NativePluginHandle handle,
  176. float** inBuffer, float** outBuffer, uint32_t frames,
  177. const NativeMidiEvent* midiEvents, uint32_t midiEventCount)
  178. {
  179. float* const pitch = outBuffer[0];
  180. float* const velocity = outBuffer[1];
  181. float* const trigger = outBuffer[2];
  182. const float oC = handlePtr->params[PARAM_OCTAVE];
  183. const float sC = handlePtr->params[PARAM_SEMITONE];
  184. const float cC = handlePtr->params[PARAM_CENT];
  185. const bool rC = handlePtr->params[PARAM_RETRIGGER] > 0.5f;
  186. bool retrigger = true;
  187. for (uint32_t i=0; i < midiEventCount; ++i)
  188. {
  189. const NativeMidiEvent* const midiEvent = &midiEvents[i];
  190. if (midiEvent->size <= 1 || midiEvent->size > 3)
  191. continue;
  192. const uint8_t* const mdata = midiEvent->data;
  193. const uint8_t status = MIDI_GET_STATUS_FROM_DATA(mdata);
  194. int storeN = 0;
  195. bool emptySlot = false;
  196. int notesIndex = NUM_NOTESBUFFER - 1;
  197. bool noteFound = false;
  198. switch (status)
  199. {
  200. case MIDI_STATUS_NOTE_ON:
  201. while (!emptySlot && storeN < NUM_NOTESBUFFER)
  202. {
  203. if (handlePtr->activeNotesList[storeN] == 200)
  204. {
  205. handlePtr->activeNotesList[storeN] = mdata[1];
  206. emptySlot = true;
  207. }
  208. storeN++;
  209. }
  210. handlePtr->activeNotes = mdata[1];
  211. handlePtr->activeVelocity = mdata[2];
  212. handlePtr->triggerIndex = (handlePtr->triggerIndex + 1U) % 8U;
  213. handlePtr->reTriggerBuffer[handlePtr->triggerIndex] = 1U;
  214. handlePtr->reTriggered = mdata[1];
  215. break;
  216. case MIDI_STATUS_NOTE_OFF:
  217. handlePtr->notesPressed--;
  218. for (int n = 0; n < NUM_NOTESBUFFER; ++n)
  219. if (mdata[1] == handlePtr->activeNotesList[n])
  220. handlePtr->activeNotesList[n] = 200;
  221. while (!noteFound && notesIndex >= 0)
  222. {
  223. if (handlePtr->activeNotesList[notesIndex] < 200)
  224. {
  225. handlePtr->activeNotes = handlePtr->activeNotesList[notesIndex];
  226. if(retrigger && handlePtr->activeNotes != handlePtr->reTriggered)
  227. {
  228. handlePtr->reTriggered = mdata[1];
  229. }
  230. noteFound = true;
  231. }
  232. notesIndex--;
  233. }
  234. break;
  235. case MIDI_STATUS_CONTROL_CHANGE:
  236. if (mdata[1] == MIDI_CONTROL_ALL_NOTES_OFF)
  237. panic(handlePtr);
  238. break;
  239. }
  240. }
  241. int checked_note = 0;
  242. bool active_notes_found = false;
  243. while (checked_note < NUM_NOTESBUFFER && ! active_notes_found)
  244. {
  245. if (handlePtr->activeNotesList[checked_note] != 200)
  246. active_notes_found = true;
  247. checked_note++;
  248. }
  249. if (active_notes_found)
  250. {
  251. set_status(handlePtr, 1);
  252. }
  253. else
  254. {
  255. set_status(handlePtr, 0);
  256. handlePtr->activeVelocity = 0;
  257. }
  258. for (uint32_t i=0; i<frames; ++i)
  259. {
  260. pitch[i] = (0.0f + (float)((oC) + (sC/12.0f)+(cC/1200.0f)) + ((float)handlePtr->activeNotes * 1/12.0f));
  261. velocity[i] = (0.0f + ((float)handlePtr->activeVelocity * 1/12.0f));
  262. trigger[i] = ((handlePtr->triggerState == true) ? 10.0f : 0.0f);
  263. if (handlePtr->reTriggerBuffer[handlePtr->triggerIndex] == 1 && rC)
  264. {
  265. handlePtr->reTriggerBuffer[handlePtr->triggerIndex] = 0;
  266. trigger[i] = 0.0f;
  267. }
  268. }
  269. return;
  270. // unused
  271. (void)inBuffer;
  272. }
  273. #undef handlePtr
  274. // -----------------------------------------------------------------------
  275. static const NativePluginDescriptor midi2cvDesc = {
  276. .category = NATIVE_PLUGIN_CATEGORY_UTILITY,
  277. .hints = NATIVE_PLUGIN_IS_RTSAFE,
  278. .supports = NATIVE_PLUGIN_SUPPORTS_ALL_SOUND_OFF,
  279. .audioIns = 0,
  280. .audioOuts = 0,
  281. .cvIns = 0,
  282. .cvOuts = 3, // pitch, velocity, gate
  283. .midiIns = 1,
  284. .midiOuts = 0,
  285. .paramIns = PARAM_COUNT,
  286. .paramOuts = 0,
  287. .name = "MIDI to CV",
  288. .label = "midi2cv",
  289. .maker = "falkTX, Bram Giesen, Jarno Verheesen",
  290. .copyright = "GNU GPL v2+",
  291. .instantiate = midi2cv_instantiate,
  292. .cleanup = midi2cv_cleanup,
  293. .get_parameter_count = midi2cv_get_parameter_count,
  294. .get_parameter_info = midi2cv_get_parameter_info,
  295. .get_parameter_value = midi2cv_get_parameter_value,
  296. .get_midi_program_count = NULL,
  297. .get_midi_program_info = NULL,
  298. .set_parameter_value = midi2cv_set_parameter_value,
  299. .set_midi_program = NULL,
  300. .set_custom_data = NULL,
  301. .get_buffer_port_name = midi2cv_get_buffer_port_name,
  302. .ui_show = NULL,
  303. .ui_idle = NULL,
  304. .ui_set_parameter_value = NULL,
  305. .ui_set_midi_program = NULL,
  306. .ui_set_custom_data = NULL,
  307. .activate = midi2cv_activate,
  308. .deactivate = NULL,
  309. .process = midi2cv_process,
  310. .get_state = NULL,
  311. .set_state = NULL,
  312. .dispatcher = NULL,
  313. .render_inline_display = NULL
  314. };
  315. // -----------------------------------------------------------------------
  316. void carla_register_native_plugin_midi2cv(void);
  317. void carla_register_native_plugin_midi2cv(void)
  318. {
  319. carla_register_native_plugin(&midi2cvDesc);
  320. }
  321. // -----------------------------------------------------------------------