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.

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