1 /*
2  * MIDI PBToCC plugin based on DISTRHO Plugin Framework (DPF)
3  *
4  * SPDX-License-Identifier: MIT
5  *
6  * Copyright (C) 2019 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 "PluginMIDIPBToCC.hpp"
28 
29 START_NAMESPACE_DISTRHO
30 
31 // -----------------------------------------------------------------------
32 
PluginMIDIPBToCC()33 PluginMIDIPBToCC::PluginMIDIPBToCC()
34     : Plugin(paramCount, presetCount, 0)  // 0 states
35 {
36     loadProgram(0);
37 }
38 
39 // -----------------------------------------------------------------------
40 // Init
41 
initParameter(uint32_t index,Parameter & parameter)42 void PluginMIDIPBToCC::initParameter(uint32_t index, Parameter& parameter) {
43     if (index >= paramCount)
44         return;
45 
46     parameter.hints = kParameterIsAutomable | kParameterIsInteger;
47     parameter.ranges.def = 0;
48     parameter.ranges.min = 0;
49     parameter.ranges.max = 127;
50 
51     switch (index) {
52         case paramFilterChannel:
53             parameter.name = "Filter Channel";
54             parameter.symbol = "channelf";
55             parameter.ranges.def = 0;
56             parameter.ranges.min = 0;
57             parameter.ranges.max = 16;
58             parameter.enumValues.count = 17;
59             parameter.enumValues.restrictedMode = true;
60             {
61                 ParameterEnumerationValue* const channels = new ParameterEnumerationValue[17];
62                 parameter.enumValues.values = channels;
63                 channels[0].label = "Any";
64                 channels[0].value = 0;
65                 channels[1].label = "Channel 1";
66                 channels[1].value = 1;
67                 channels[2].label = "Channel 2";
68                 channels[2].value = 2;
69                 channels[3].label = "Channel 3";
70                 channels[3].value = 3;
71                 channels[4].label = "Channel 4";
72                 channels[4].value = 4;
73                 channels[5].label = "Channel 5";
74                 channels[5].value = 5;
75                 channels[6].label = "Channel 6";
76                 channels[6].value = 6;
77                 channels[7].label = "Channel 7";
78                 channels[7].value = 7;
79                 channels[8].label = "Channel 8";
80                 channels[8].value = 8;
81                 channels[9].label = "Channel 9";
82                 channels[9].value = 9;
83                 channels[10].label = "Channel 10";
84                 channels[10].value = 10;
85                 channels[11].label = "Channel 11";
86                 channels[11].value = 11;
87                 channels[12].label = "Channel 12";
88                 channels[12].value = 12;
89                 channels[13].label = "Channel 13";
90                 channels[13].value = 13;
91                 channels[14].label = "Channel 14";
92                 channels[14].value = 14;
93                 channels[15].label = "Channel 15";
94                 channels[15].value = 15;
95                 channels[16].label = "Channel 16";
96                 channels[16].value = 16;
97             }
98             break;
99         case paramKeepOriginal:
100             parameter.name = "Keep original PB events";
101             parameter.shortName = "Keep PB";
102             parameter.symbol = "keep_original";
103             parameter.hints |= kParameterIsBoolean;
104             parameter.ranges.max = 1;
105             break;
106         case paramPBMin:
107             parameter.name = "PB min. value";
108             parameter.symbol = "pb_min";
109             parameter.ranges.def = -8192;
110             parameter.ranges.min = -8192;
111             parameter.ranges.max = 8191;
112             break;
113         case paramPBMax:
114             parameter.name = "PB max. value";
115             parameter.symbol = "pb_max";
116             parameter.ranges.def = 8192;
117             parameter.ranges.min = -8192;
118             parameter.ranges.max = 8191;
119             break;
120         case paramCC1:
121             parameter.name = "Pos. PB -> CC A";
122             parameter.symbol = "cc1";
123             parameter.ranges.def = 1;
124             break;
125         case paramCC1Min:
126             parameter.name = "CC A min. value";
127             parameter.symbol = "cc1_min";
128             break;
129         case paramCC1Max:
130             parameter.name = "CC A max. value";
131             parameter.symbol = "cc1_max";
132             parameter.ranges.def = 127;
133             break;
134         case paramCC2:
135             parameter.name = "Neg. PB -> CC B";
136             parameter.symbol = "cc2";
137             parameter.ranges.def = 1;
138             break;
139         case paramCC2Min:
140             parameter.name = "CC B min. value";
141             parameter.symbol = "cc2_min";
142             break;
143         case paramCC2Max:
144             parameter.name = "CC B max. value";
145             parameter.symbol = "cc2_max";
146             parameter.ranges.def = 127;
147             break;
148    }
149 }
150 
151 /**
152   Set the name of the program @a index.
153   This function will be called once, shortly after the plugin is created.
154 */
initProgramName(uint32_t index,String & programName)155 void PluginMIDIPBToCC::initProgramName(uint32_t index, String& programName) {
156     if (index < presetCount) {
157         programName = factoryPresets[index].name;
158     }
159 }
160 
161 // -----------------------------------------------------------------------
162 // Internal data
163 
164 /**
165   Optional callback to inform the plugin about a sample rate change.
166 */
sampleRateChanged(double newSampleRate)167 void PluginMIDIPBToCC::sampleRateChanged(double newSampleRate) {
168     (void) newSampleRate;
169 }
170 
171 /**
172   Get the current value of a parameter.
173 */
getParameterValue(uint32_t index) const174 float PluginMIDIPBToCC::getParameterValue(uint32_t index) const {
175     return fParams[index];
176 }
177 
178 /**
179   Change a parameter value.
180 */
setParameterValue(uint32_t index,float value)181 void PluginMIDIPBToCC::setParameterValue(uint32_t index, float value) {
182     switch (index) {
183         case paramFilterChannel:
184             fParams[index] = CLAMP(value, 0.0f, 16.0f);
185             filterChannel = (int8_t) fParams[index] - 1;
186             break;
187         case paramKeepOriginal:
188             fParams[index] = CLAMP(value, 0.0f, 1.0f);
189             break;
190         case paramPBMin:
191         case paramPBMax:
192             fParams[index] = CLAMP(value, -8192.0f, 8191.0f);
193             break;
194         case paramCC1:
195         case paramCC1Min:
196         case paramCC1Max:
197         case paramCC2:
198         case paramCC2Min:
199         case paramCC2Max:
200             fParams[index] = CLAMP(value, 0.0f, 127.0f);
201             break;
202     }
203 }
204 
205 /**
206   Load a program.
207   The host may call this function from any context,
208   including realtime processing.
209 */
loadProgram(uint32_t index)210 void PluginMIDIPBToCC::loadProgram(uint32_t index) {
211     if (index < presetCount) {
212         for (int i=0; i < paramCount; i++) {
213             setParameterValue(i, factoryPresets[index].params[i]);
214         }
215 
216     }
217 }
218 
219 // -----------------------------------------------------------------------
220 // Process
221 
activate()222 void PluginMIDIPBToCC::activate() {
223     // plugin is activated
224 }
225 
226 
run(const float **,float **,uint32_t,const MidiEvent * events,uint32_t eventCount)227 void PluginMIDIPBToCC::run(const float**, float**, uint32_t,
228                            const MidiEvent* events, uint32_t eventCount) {
229     bool pass;
230     uint8_t chan;
231     int16_t pb_value,
232             pb_min = (uint16_t) fParams[paramPBMin],
233             pb_max = (uint16_t) fParams[paramPBMax];
234     struct MidiEvent cc_event;
235 
236     for (uint32_t i=0; i<eventCount; ++i) {
237         pass = true;
238 
239         if ((events[i].data[0] & 0xF0) != MIDI_PITCH_BEND) {
240             writeMidiEvent(events[i]);
241             continue;
242         }
243 
244         chan = events[i].data[0] & 0x0F;
245 
246         if (filterChannel == -1 || chan == filterChannel) {
247             pb_value = (((events[i].data[2] & 0x7f) << 7) | (events[i].data[1] & 0x7f)) - 8192;
248 
249             if (IN_RANGE(pb_value, pb_min, pb_max)) {
250                 pass = (bool) fParams[paramKeepOriginal];
251                 cc_event.frame = events[i].frame;
252                 cc_event.size = 3;
253                 cc_event.data[0] = MIDI_CONTROL_CHANGE | chan;
254 
255                 if (pb_value >= 0) {
256                     cc_event.data[1] = (uint8_t) fParams[paramCC1];
257 
258                     if (pb_min <= pb_max)
259                         cc_event.data[2] = ((uint8_t) MAP(pb_value, 0, pb_max, fParams[paramCC1Min], fParams[paramCC1Max])) & 0x7f;
260                     else
261                         cc_event.data[2] = ((uint8_t) MAP(pb_value, pb_min, 8191, fParams[paramCC2Min], fParams[paramCC1Max])) & 0x7f;
262                 }
263                 else {
264                     cc_event.data[1] = (uint8_t) fParams[paramCC2];
265 
266                     if (pb_min <= pb_max)
267                         cc_event.data[2] = ((uint8_t) MAP(pb_value, -1, pb_min, fParams[paramCC2Min], fParams[paramCC2Max])) & 0x7f;
268                     else
269                         cc_event.data[2] = ((uint8_t) MAP(pb_value, pb_max, -8192, fParams[paramCC2Min], fParams[paramCC2Max])) & 0x7f;
270                 }
271 
272                 writeMidiEvent(cc_event);
273             }
274         }
275 
276         if (pass) writeMidiEvent(events[i]);
277     }
278 }
279 
280 // -----------------------------------------------------------------------
281 
createPlugin()282 Plugin* createPlugin() {
283     return new PluginMIDIPBToCC();
284 }
285 
286 // -----------------------------------------------------------------------
287 
288 END_NAMESPACE_DISTRHO
289