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