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.

406 lines
11KB

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