1 /*
2 * MIDI CC Map X4 plugin based on DISTRHO Plugin Framework (DPF)
3 *
4 * SPDX-License-Identifier: MIT
5 *
6 * Copyright (C) 2020 Christopher Arndt <info@chrisarndt.de>
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to
10 * deal in the Software without restriction, including without limitation the
11 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 * sell copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 * IN THE SOFTWARE.
25 */
26
27 #include "PluginMIDICCMapX4.hpp"
28 #include <algorithm>
29
30 START_NAMESPACE_DISTRHO
31
32 enum {
33 kPortGroupSource,
34 kPortGroupCC1,
35 kPortGroupCC2,
36 kPortGroupCC3,
37 kPortGroupCC4,
38 };
39
40 const ParameterEnumerationValue paramEnumSrcChannels[] = {
41 {0,"Any"},
42 {1, "Channel 1"},
43 {2, "Channel 2"},
44 {3, "Channel 3"},
45 {4, "Channel 4"},
46 {5, "Channel 5"},
47 {6, "Channel 6"},
48 {7, "Channel 7"},
49 {8, "Channel 8"},
50 {9, "Channel 9"},
51 {10, "Channel 10"},
52 {11, "Channel 11"},
53 {12, "Channel 12"},
54 {13, "Channel 13"},
55 {14, "Channel 14"},
56 {15, "Channel 15"},
57 {16, "Channel 16"}
58 };
59
60 const ParameterEnumerationValue paramEnumDstChannels[] = {
61 {0,"Same as source"},
62 {1, "Channel 1"},
63 {2, "Channel 2"},
64 {3, "Channel 3"},
65 {4, "Channel 4"},
66 {5, "Channel 5"},
67 {6, "Channel 6"},
68 {7, "Channel 7"},
69 {8, "Channel 8"},
70 {9, "Channel 9"},
71 {10, "Channel 10"},
72 {11, "Channel 11"},
73 {12, "Channel 12"},
74 {13, "Channel 13"},
75 {14, "Channel 14"},
76 {15, "Channel 15"},
77 {16, "Channel 16"}
78 };
79
80 const ParameterEnumerationValue paramEnumModes[] {
81 {0, "Disabled"},
82 {1, "Map start/end range to min/max"},
83 {2, "Map full value range to min/max"}
84 };
85
86 // -----------------------------------------------------------------------
87
88 template <size_t N>
fillEnumValues(ParameterEnumerationValues & pev,const ParameterEnumerationValue (& list)[N])89 static inline void fillEnumValues(ParameterEnumerationValues& pev,
90 const ParameterEnumerationValue(& list)[N]) {
91 ParameterEnumerationValue* values = new ParameterEnumerationValue[N];
92 pev.count = N;
93 pev.values = values;
94 std::copy(list, list + N, values);
95 }
96
97 // -----------------------------------------------------------------------
98
PluginMIDICCMapX4()99 PluginMIDICCMapX4::PluginMIDICCMapX4()
100 : Plugin(paramCount, presetCount, 0) // 0 states
101 {
102 for (uint8_t ch=0; ch<16; ch++) {
103 for (uint8_t cc=0; cc<128; cc++) {
104 lastCCValue[ch][cc] = -1;
105 }
106 }
107 loadProgram(0);
108 }
109
110 // -----------------------------------------------------------------------
111 // Init
112
initParameter(uint32_t index,Parameter & parameter)113 void PluginMIDICCMapX4::initParameter(uint32_t index, Parameter& parameter) {
114 if (index >= paramCount)
115 return;
116
117 parameter.hints = kParameterIsAutomable | kParameterIsInteger;
118 parameter.ranges.def = 0;
119 parameter.ranges.min = 0;
120 parameter.ranges.max = 127;
121
122 switch (index) {
123 case paramFilterChannel:
124 parameter.name = "Filter Channel";
125 parameter.symbol = "channelf";
126 parameter.ranges.max = 16;
127 parameter.enumValues.restrictedMode = true;
128 fillEnumValues(parameter.enumValues, paramEnumSrcChannels);
129 parameter.group = kPortGroupSource;
130 break;
131 case paramCCSource:
132 parameter.name = "Source CC";
133 parameter.symbol = "cc_source";
134 parameter.ranges.def = 1;
135 parameter.group = kPortGroupSource;
136 break;
137 case paramKeepOriginal:
138 parameter.name = "Keep Source CC Events";
139 parameter.shortName = "Keep src. CC";
140 parameter.symbol = "keep_original";
141 parameter.hints |= kParameterIsBoolean;
142 parameter.ranges.max = 1;
143 parameter.group = kPortGroupSource;
144 break;
145 case paramCC1Mode:
146 parameter.name = "CC 1 Mode";
147 parameter.shortName = "CC1 Mode";
148 parameter.symbol = "cc1_mode";
149 parameter.ranges.max = 2;
150 parameter.enumValues.restrictedMode = true;
151 fillEnumValues(parameter.enumValues, paramEnumModes);
152 parameter.group = kPortGroupCC1;
153 break;
154 case paramCC1Dest:
155 parameter.name = "CC 1 Destination";
156 parameter.shortName = "CC1 Dest.";
157 parameter.symbol = "cc1_dest";
158 parameter.ranges.def = 14;
159 parameter.group = kPortGroupCC1;
160 break;
161 case paramCC1Channel:
162 parameter.name = "CC1 Channel";
163 parameter.symbol = "cc1_chan";
164 parameter.ranges.max = 16;
165 parameter.enumValues.restrictedMode = true;
166 fillEnumValues(parameter.enumValues, paramEnumDstChannels);
167 parameter.group = kPortGroupCC1;
168 break;
169 case paramCC1FilterDups:
170 parameter.name = "CC 1 Filter repeated values";
171 parameter.shortName = "CC1 Filter dups";
172 parameter.symbol = "cc1_filterdups";
173 parameter.ranges.def = 1;
174 parameter.ranges.max = 1;
175 parameter.hints |= kParameterIsBoolean;
176 parameter.group = kPortGroupCC1;
177 break;
178 case paramCC1Start:
179 parameter.name = "CC 1 Start";
180 parameter.shortName = "CC1 Start";
181 parameter.symbol = "cc1_start";
182 parameter.group = kPortGroupCC1;
183 break;
184 case paramCC1End:
185 parameter.name = "CC 1 End";
186 parameter.shortName = "CC1 End";
187 parameter.symbol = "cc1_end";
188 parameter.ranges.def = 127;
189 parameter.group = kPortGroupCC1;
190 break;
191 case paramCC1Min:
192 parameter.name = "CC 1 Minimum value";
193 parameter.shortName = "CC1 Min. value";
194 parameter.symbol = "cc1_min";
195 parameter.group = kPortGroupCC1;
196 break;
197 case paramCC1Max:
198 parameter.name = "CC 1 Maximum value";
199 parameter.shortName = "CC1 Max. value";
200 parameter.symbol = "cc1_max";
201 parameter.ranges.def = 127;
202 parameter.group = kPortGroupCC1;
203 break;
204 case paramCC2Mode:
205 parameter.name = "CC 2 Mode";
206 parameter.shortName = "CC2 Mode";
207 parameter.symbol = "cc2_mode";
208 parameter.ranges.max = 2;
209 parameter.enumValues.restrictedMode = true;
210 fillEnumValues(parameter.enumValues, paramEnumModes);
211 parameter.group = kPortGroupCC2;
212 break;
213 case paramCC2Dest:
214 parameter.name = "CC 2 Destination";
215 parameter.shortName = "CC2 Dest.";
216 parameter.symbol = "cc2_dest";
217 parameter.ranges.def = 15;
218 parameter.group = kPortGroupCC2;
219 break;
220 case paramCC2Channel:
221 parameter.name = "CC2 Channel";
222 parameter.symbol = "cc2_chan";
223 parameter.ranges.max = 16;
224 parameter.enumValues.restrictedMode = true;
225 fillEnumValues(parameter.enumValues, paramEnumDstChannels);
226 parameter.group = kPortGroupCC2;
227 break;
228 case paramCC2FilterDups:
229 parameter.name = "CC 2 Filter repeated values";
230 parameter.shortName = "CC2 Filter dups";
231 parameter.symbol = "cc2_filterdups";
232 parameter.ranges.def = 1;
233 parameter.ranges.max = 1;
234 parameter.hints |= kParameterIsBoolean;
235 parameter.ranges.max = 1;
236 parameter.group = kPortGroupCC2;
237 break;
238 case paramCC2Start:
239 parameter.name = "CC 2 Start";
240 parameter.shortName = "CC2 Start";
241 parameter.symbol = "cc2_start";
242 parameter.group = kPortGroupCC2;
243 break;
244 case paramCC2End:
245 parameter.name = "CC 2 End";
246 parameter.shortName = "CC2 End";
247 parameter.symbol = "cc2_end";
248 parameter.ranges.def = 127;
249 parameter.group = kPortGroupCC2;
250 break;
251 case paramCC2Min:
252 parameter.name = "CC 2 Minimum value";
253 parameter.shortName = "CC2 Min. value";
254 parameter.symbol = "cc2_min";
255 parameter.group = kPortGroupCC2;
256 break;
257 case paramCC2Max:
258 parameter.name = "CC 2 Maximum value";
259 parameter.shortName = "CC2 Max. value";
260 parameter.symbol = "cc2_max";
261 parameter.ranges.def = 127;
262 parameter.group = kPortGroupCC2;
263 break;
264 case paramCC3Mode:
265 parameter.name = "CC 3 Mode";
266 parameter.shortName = "CC3 Mode";
267 parameter.symbol = "cc3_mode";
268 parameter.ranges.max = 2;
269 parameter.enumValues.restrictedMode = true;
270 fillEnumValues(parameter.enumValues, paramEnumModes);
271 parameter.group = kPortGroupCC3;
272 break;
273 case paramCC3Dest:
274 parameter.name = "CC 3 Destination";
275 parameter.shortName = "CC3 Dest.";
276 parameter.symbol = "cc3_dest";
277 parameter.ranges.def = 16;
278 parameter.group = kPortGroupCC3;
279 break;
280 case paramCC3Channel:
281 parameter.name = "CC3 Channel";
282 parameter.symbol = "cc3_chan";
283 parameter.ranges.max = 16;
284 parameter.enumValues.restrictedMode = true;
285 fillEnumValues(parameter.enumValues, paramEnumDstChannels);
286 parameter.group = kPortGroupCC3;
287 break;
288 case paramCC3FilterDups:
289 parameter.name = "CC 3 Filter repeated values";
290 parameter.shortName = "CC3 Filter dups";
291 parameter.symbol = "cc3_filterdups";
292 parameter.ranges.def = 1;
293 parameter.ranges.max = 1;
294 parameter.hints |= kParameterIsBoolean;
295 parameter.group = kPortGroupCC3;
296 break;
297 case paramCC3Start:
298 parameter.name = "CC 3 Start";
299 parameter.shortName = "CC3 Start";
300 parameter.symbol = "cc3_start";
301 parameter.group = kPortGroupCC3;
302 break;
303 case paramCC3End:
304 parameter.name = "CC 3 End";
305 parameter.shortName = "CC3 End";
306 parameter.symbol = "cc3_end";
307 parameter.ranges.def = 127;
308 parameter.group = kPortGroupCC3;
309 break;
310 case paramCC3Min:
311 parameter.name = "CC 3 Minimum value";
312 parameter.shortName = "CC3 Min. value";
313 parameter.symbol = "cc3_min";
314 parameter.group = kPortGroupCC3;
315 break;
316 case paramCC3Max:
317 parameter.name = "CC 3 Maximum value";
318 parameter.shortName = "CC3 Max. value";
319 parameter.symbol = "cc3_max";
320 parameter.ranges.def = 127;
321 parameter.group = kPortGroupCC3;
322 break;
323 case paramCC4Mode:
324 parameter.name = "CC 4 Mode";
325 parameter.shortName = "CC4 Mode";
326 parameter.symbol = "cc4_mode";
327 parameter.ranges.max = 2;
328 parameter.enumValues.restrictedMode = true;
329 fillEnumValues(parameter.enumValues, paramEnumModes);
330 parameter.group = kPortGroupCC4;
331 break;
332 case paramCC4Dest:
333 parameter.name = "CC 4 Destination";
334 parameter.shortName = "CC4 Dest.";
335 parameter.symbol = "cc4_dest";
336 parameter.ranges.def = 17;
337 parameter.group = kPortGroupCC4;
338 break;
339 case paramCC4Channel:
340 parameter.name = "CC4 Channel";
341 parameter.symbol = "cc4_chan";
342 parameter.ranges.max = 16;
343 parameter.enumValues.restrictedMode = true;
344 fillEnumValues(parameter.enumValues, paramEnumDstChannels);
345 parameter.group = kPortGroupCC4;
346 break;
347 case paramCC4FilterDups:
348 parameter.name = "CC 4 Filter repeated values";
349 parameter.shortName = "CC4 Filter dups";
350 parameter.symbol = "cc4_filterdups";
351 parameter.ranges.def = 1;
352 parameter.ranges.max = 1;
353 parameter.hints |= kParameterIsBoolean;
354 parameter.group = kPortGroupCC4;
355 break;
356 case paramCC4Start:
357 parameter.name = "CC Start";
358 parameter.shortName = "CC4 Start";
359 parameter.symbol = "cc4_start";
360 parameter.group = kPortGroupCC4;
361 break;
362 case paramCC4End:
363 parameter.name = "CC 4 End";
364 parameter.shortName = "CC4 End";
365 parameter.symbol = "cc4_end";
366 parameter.ranges.def = 127;
367 parameter.group = kPortGroupCC4;
368 break;
369 case paramCC4Min:
370 parameter.name = "CC 4 Minimum value";
371 parameter.shortName = "CC4 Min. value";
372 parameter.symbol = "cc4_min";
373 parameter.group = kPortGroupCC4;
374 break;
375 case paramCC4Max:
376 parameter.name = "CC 4 Maximum value";
377 parameter.shortName = "CC4 Max. value";
378 parameter.symbol = "cc4_max";
379 parameter.ranges.def = 127;
380 parameter.group = kPortGroupCC4;
381 break;
382 }
383 }
384
385 /**
386 Set the name and symbol of the port group @a index.
387 This function will be called once for every port group, shortly after the plugin is created.
388 */
initPortGroup(uint32_t index,PortGroup & pgroup)389 void PluginMIDICCMapX4::initPortGroup(uint32_t index, PortGroup& pgroup) {
390 switch (index) {
391 case kPortGroupSource:
392 pgroup.name = "Source";
393 pgroup.symbol = "source";
394 break;
395 case kPortGroupCC1:
396 pgroup.name = "Destination #1";
397 pgroup.symbol = "dest1";
398 break;
399 case kPortGroupCC2:
400 pgroup.name = "Destination #2";
401 pgroup.symbol = "dest2";
402 break;
403 case kPortGroupCC3:
404 pgroup.name = "Destination #3";
405 pgroup.symbol = "dest3";
406 break;
407 case kPortGroupCC4:
408 pgroup.name = "Destination #4";
409 pgroup.symbol = "dest4";
410 break;
411 }
412 }
413
414
415 /**
416 Set the name of the program @a index.
417 This function will be called once, shortly after the plugin is created.
418 */
initProgramName(uint32_t index,String & programName)419 void PluginMIDICCMapX4::initProgramName(uint32_t index, String& programName) {
420 if (index < presetCount) {
421 programName = factoryPresets[index].name;
422 }
423 }
424
425 // -----------------------------------------------------------------------
426 // Internal data
427
428 /**
429 Optional callback to inform the plugin about a sample rate change.
430 */
sampleRateChanged(double newSampleRate)431 void PluginMIDICCMapX4::sampleRateChanged(double newSampleRate) {
432 (void) newSampleRate;
433 }
434
435 /**
436 Get the current value of a parameter.
437 */
getParameterValue(uint32_t index) const438 float PluginMIDICCMapX4::getParameterValue(uint32_t index) const {
439 return fParams[index];
440 }
441
442 /**
443 Change a parameter value.
444 */
setParameterValue(uint32_t index,float value)445 void PluginMIDICCMapX4::setParameterValue(uint32_t index, float value) {
446 switch (index) {
447 case paramFilterChannel:
448 fParams[index] = CLAMP(value, 0.0f, 16.0f);
449 filterChannel = (int8_t) fParams[index] - 1;
450 break;
451 case paramCC1Channel:
452 case paramCC2Channel:
453 case paramCC3Channel:
454 case paramCC4Channel:
455 fParams[index] = CLAMP(value, 0.0f, 16.0f);
456 break;
457 case paramKeepOriginal:
458 case paramCC1FilterDups:
459 case paramCC2FilterDups:
460 case paramCC3FilterDups:
461 case paramCC4FilterDups:
462 fParams[index] = CLAMP(value, 0.0f, 1.0f);
463 break;
464 case paramCC1Mode:
465 case paramCC2Mode:
466 case paramCC3Mode:
467 case paramCC4Mode:
468 fParams[index] = CLAMP(value, 0.0f, 2.0f);
469 break;
470 case paramCCSource:
471 case paramCC1Dest:
472 case paramCC1Min:
473 case paramCC1Max:
474 case paramCC1Start:
475 case paramCC1End:
476 case paramCC2Dest:
477 case paramCC2Min:
478 case paramCC2Max:
479 case paramCC2Start:
480 case paramCC2End:
481 case paramCC3Dest:
482 case paramCC3Min:
483 case paramCC3Max:
484 case paramCC3Start:
485 case paramCC3End:
486 case paramCC4Dest:
487 case paramCC4Min:
488 case paramCC4Max:
489 case paramCC4Start:
490 case paramCC4End:
491 fParams[index] = CLAMP(value, 0.0f, 127.0f);
492 break;
493 }
494 }
495
496 /**
497 Load a program.
498 The host may call this function from any context,
499 including realtime processing.
500 */
loadProgram(uint32_t index)501 void PluginMIDICCMapX4::loadProgram(uint32_t index) {
502 if (index < presetCount) {
503 for (int i=0; i < paramCount; i++) {
504 setParameterValue(i, factoryPresets[index].params[i]);
505 }
506
507 }
508 }
509
510 // -----------------------------------------------------------------------
511 // Process
512
activate()513 void PluginMIDICCMapX4::activate() {
514 // plugin is activated
515 }
516
517
run(const float **,float **,uint32_t,const MidiEvent * events,uint32_t eventCount)518 void PluginMIDICCMapX4::run(const float**, float**, uint32_t,
519 const MidiEvent* events, uint32_t eventCount) {
520 bool pass;
521 uint8_t status, chan, cc_mode, cc_dest, cc_end, cc_min, cc_max, cc_no_dups,
522 cc_num, cc_start, cc_val, new_val, param_offset;
523 int8_t cc_chan;
524 uint8_t cc_src = (uint8_t) fParams[paramCCSource];
525 struct MidiEvent cc_event;
526
527 for (uint32_t i=0; i<eventCount; ++i) {
528 pass = true;
529
530 if ((status = events[i].data[0] & 0xF0) != MIDI_CONTROL_CHANGE) {
531 writeMidiEvent(events[i]);
532 continue;
533 }
534
535 chan = events[i].data[0] & 0x0F;
536 cc_num = events[i].data[1] & 0x7f;
537
538 if ((filterChannel == -1 || chan == filterChannel) && cc_num == cc_src) {
539 pass = (bool) fParams[paramKeepOriginal];
540 cc_val = events[i].data[2] & 0x7f;
541
542 for (int dest=0; dest<4; dest++) {
543 param_offset = paramCC1Mode + (8 * dest);
544 cc_mode = (uint8_t) fParams[param_offset];
545 cc_dest = (uint8_t) fParams[param_offset + 1];
546 cc_chan = (int8_t) fParams[param_offset + 2] - 1;
547 cc_no_dups = (bool) fParams[param_offset + 3];
548 cc_start = (uint8_t) fParams[param_offset + 4];
549 cc_end = (uint8_t) fParams[param_offset + 5];
550 cc_min = (uint8_t) fParams[param_offset + 6];
551 cc_max = (uint8_t) fParams[param_offset + 7];
552
553 if (IN_RANGE(cc_val, cc_start, cc_end)) {
554 switch (cc_mode) {
555 case 1:
556 new_val = (uint8_t) MAP(cc_val, cc_start, cc_end, cc_min, cc_max);
557 break;
558 case 2:
559 new_val = (uint8_t) MAP(cc_val, 0, 127, cc_min, cc_max);
560 break;
561 default:
562 continue;
563 }
564
565 if (cc_chan == -1) {
566 cc_chan = chan;
567 }
568
569 if (cc_no_dups && new_val == lastCCValue[cc_chan][cc_dest])
570 continue;
571
572 cc_event.frame = events[i].frame;
573 cc_event.size = 3;
574 cc_event.data[0] = MIDI_CONTROL_CHANGE | cc_chan;
575 cc_event.data[1] = (uint8_t) cc_dest;
576 cc_event.data[2] = new_val;
577 lastCCValue[cc_chan][cc_dest] = new_val;
578 writeMidiEvent(cc_event);
579 }
580 }
581
582 if (pass)
583 writeMidiEvent(events[i]);
584 }
585 else {
586 writeMidiEvent(events[i]);
587 }
588 }
589 }
590
591 // -----------------------------------------------------------------------
592
createPlugin()593 Plugin* createPlugin() {
594 return new PluginMIDICCMapX4();
595 }
596
597 // -----------------------------------------------------------------------
598
599 END_NAMESPACE_DISTRHO
600