1 /*
2  * MIDI CCRecorder 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 <iostream>
28 #include <iomanip>
29 #include <vector>
30 
31 #include "PluginMIDICCRecorder.hpp"
32 #include "extra/Base64.hpp"
33 
34 START_NAMESPACE_DISTRHO
35 
36 // -----------------------------------------------------------------------
37 
PluginMIDICCRecorder()38 PluginMIDICCRecorder::PluginMIDICCRecorder()
39     : Plugin(paramCount, presetCount, stateCount), playing(false)
40 {
41     clearState();
42     loadProgram(0);
43 }
44 
45 // -----------------------------------------------------------------------
46 // Init
47 
initParameter(uint32_t index,Parameter & parameter)48 void PluginMIDICCRecorder::initParameter(uint32_t index, Parameter& parameter) {
49     if (index >= paramCount)
50         return;
51 
52     parameter.hints = kParameterIsAutomable | kParameterIsInteger;
53     parameter.ranges.def = 0;
54     parameter.ranges.min = 0;
55     parameter.ranges.max = 1;
56 
57     switch (index) {
58         case paramRecordEnable:
59             parameter.name = "Record";
60             parameter.symbol = "rec_enable";
61             parameter.hints |= kParameterIsBoolean;
62             break;
63         case paramTrigClear:
64             parameter.name = "Clear";
65             parameter.symbol = "trig_clear";
66             parameter.hints |= kParameterIsTrigger;
67             break;
68         case paramTrigSend:
69             parameter.name = "Send";
70             parameter.symbol = "trig_send";
71             parameter.hints |= kParameterIsTrigger;
72             break;
73         case paramTrigTransport:
74             parameter.name = "Trigger send on transport start?";
75             parameter.shortName = "Transport";
76             parameter.symbol = "trig_transport";
77             parameter.ranges.max = 2;
78             parameter.enumValues.count = 3;
79             parameter.enumValues.restrictedMode = true;
80             {
81                 ParameterEnumerationValue* const channels = new ParameterEnumerationValue[3];
82                 parameter.enumValues.values = channels;
83                 channels[0].label = "Disabled";
84                 channels[0].value = 0;
85                 channels[1].label = "Always";
86                 channels[1].value = 1;
87                 channels[2].label = "Only at Position 0";
88                 channels[2].value = 2;
89             }
90             break;
91         case paramTrigPCChannel:
92             parameter.name = "Trigger send on PC?";
93             parameter.shortName = "Trigger by PC?";
94             parameter.symbol = "trig_pc_chan";
95             parameter.ranges.def = 17;
96             parameter.ranges.max = 17;
97             parameter.enumValues.count = 18;
98             parameter.enumValues.restrictedMode = true;
99             {
100                 ParameterEnumerationValue* const channels = new ParameterEnumerationValue[18];
101                 parameter.enumValues.values = channels;
102                 channels[0].label = "Any channel";
103                 channels[0].value = 0;
104                 channels[1].label = "Channel 1";
105                 channels[1].value = 1;
106                 channels[2].label = "Channel 2";
107                 channels[2].value = 2;
108                 channels[3].label = "Channel 3";
109                 channels[3].value = 3;
110                 channels[4].label = "Channel 4";
111                 channels[4].value = 4;
112                 channels[5].label = "Channel 5";
113                 channels[5].value = 5;
114                 channels[6].label = "Channel 6";
115                 channels[6].value = 6;
116                 channels[7].label = "Channel 7";
117                 channels[7].value = 7;
118                 channels[8].label = "Channel 8";
119                 channels[8].value = 8;
120                 channels[9].label = "Channel 9";
121                 channels[9].value = 9;
122                 channels[10].label = "Channel 10";
123                 channels[10].value = 10;
124                 channels[11].label = "Channel 11";
125                 channels[11].value = 11;
126                 channels[12].label = "Channel 12";
127                 channels[12].value = 12;
128                 channels[13].label = "Channel 13";
129                 channels[13].value = 13;
130                 channels[14].label = "Channel 14";
131                 channels[14].value = 14;
132                 channels[15].label = "Channel 15";
133                 channels[15].value = 15;
134                 channels[16].label = "Channel 16";
135                 channels[16].value = 16;
136                 channels[17].label = "Disabled";
137                 channels[17].value = 17;
138             }
139             break;
140         case paramTrigPC:
141             parameter.name = "Program number";
142             parameter.symbol = "trig_pc";
143             parameter.ranges.max = 127;
144             break;
145         case paramSendChannel:
146             parameter.name = "Send channel";
147             parameter.symbol = "send_chan";
148             parameter.ranges.max = 16;
149             parameter.enumValues.count = 17;
150             parameter.enumValues.restrictedMode = true;
151             {
152                 ParameterEnumerationValue* const channels = new ParameterEnumerationValue[17];
153                 parameter.enumValues.values = channels;
154                 channels[0].label = "All";
155                 channels[0].value = 0;
156                 channels[1].label = "Channel 1";
157                 channels[1].value = 1;
158                 channels[2].label = "Channel 2";
159                 channels[2].value = 2;
160                 channels[3].label = "Channel 3";
161                 channels[3].value = 3;
162                 channels[4].label = "Channel 4";
163                 channels[4].value = 4;
164                 channels[5].label = "Channel 5";
165                 channels[5].value = 5;
166                 channels[6].label = "Channel 6";
167                 channels[6].value = 6;
168                 channels[7].label = "Channel 7";
169                 channels[7].value = 7;
170                 channels[8].label = "Channel 8";
171                 channels[8].value = 8;
172                 channels[9].label = "Channel 9";
173                 channels[9].value = 9;
174                 channels[10].label = "Channel 10";
175                 channels[10].value = 10;
176                 channels[11].label = "Channel 11";
177                 channels[11].value = 11;
178                 channels[12].label = "Channel 12";
179                 channels[12].value = 12;
180                 channels[13].label = "Channel 13";
181                 channels[13].value = 13;
182                 channels[14].label = "Channel 14";
183                 channels[14].value = 14;
184                 channels[15].label = "Channel 15";
185                 channels[15].value = 15;
186                 channels[16].label = "Channel 16";
187                 channels[16].value = 16;
188             }
189             break;
190         case paramSendInterval:
191             parameter.name = "Send interval (ms)";
192             parameter.unit = "ms";
193             parameter.symbol = "send_interval";
194             parameter.ranges.min = 1;
195             parameter.ranges.max = 200;
196             break;
197    }
198 }
199 
200 /**
201   Set the state key and default value of @a index.
202   This function will be called once, shortly after the plugin is created.
203 */
initState(uint32_t index,String & stateKey,String & defaultStateValue)204 void PluginMIDICCRecorder::initState(uint32_t index, String& stateKey, String& defaultStateValue) {
205     if (index < stateCount) {
206         char key[6];
207         snprintf(key, 6, "ch-%02d", index);
208         stateKey = key;
209         defaultStateValue = "false";
210     }
211 }
212 
213 /**
214   Set the name of the program @a index.
215   This function will be called once, shortly after the plugin is created.
216 */
initProgramName(uint32_t index,String & programName)217 void PluginMIDICCRecorder::initProgramName(uint32_t index, String& programName) {
218     if (index < presetCount) {
219         programName = factoryPresets[index].name;
220     }
221 }
222 
223 // -----------------------------------------------------------------------
224 // Internal data
225 
226 /**
227   Optional callback to inform the plugin about a sample rate change.
228 */
sampleRateChanged(double newSampleRate)229 void PluginMIDICCRecorder::sampleRateChanged(double newSampleRate) {
230     fSampleRate = newSampleRate;
231 }
232 
233 /**
234   Get the current value of a parameter.
235 */
getParameterValue(uint32_t index) const236 float PluginMIDICCRecorder::getParameterValue(uint32_t index) const {
237     return fParams[index];
238 }
239 
240 /**
241   Change a parameter value.
242 */
setParameterValue(uint32_t index,float value)243 void PluginMIDICCRecorder::setParameterValue(uint32_t index, float value) {
244     switch (index) {
245         case paramRecordEnable:
246             fParams[index] = CLAMP(value, 0, 1);
247             break;
248         case paramTrigClear:
249             fParams[index] = CLAMP(value, 0, 1);
250 
251             if (fParams[index] > 0.0f)
252                 clearState();
253 
254             break;
255         case paramTrigSend:
256             fParams[index] = CLAMP(value, 0, 1);
257 
258             if (fParams[index] > 0.0f)
259                 startSend();
260 
261             break;
262         case paramTrigTransport:
263             fParams[index] = CLAMP(value, 0, 2);
264             break;
265         case paramTrigPCChannel:
266             fParams[index] = CLAMP(value, 0, 17);
267             break;
268         case paramTrigPC:
269             fParams[index] = CLAMP(value, 0, 127);
270             break;
271         case paramSendChannel:
272             fParams[index] = CLAMP(value, 0, 16);
273             break;
274         case paramSendInterval:
275             fParams[index] = CLAMP(value, 0, 200);
276             break;
277     }
278 }
279 
280 /**
281   Get the value of an internal state.
282   The host may call this function from any non-realtime context.
283 */
getState(const char * key) const284 String PluginMIDICCRecorder::getState(const char* key) const {
285     static const String sFalse("false");
286     int index;
287 
288     if (std::strncmp(key, "ch-", 3) == 0) {
289         try {
290             index = std::stoi(key+3);
291         } catch (...) {
292             return sFalse;
293         }
294 
295         if (index < (int) stateCount) {
296             return String::asBase64((char *) stateCC[index], 128);
297         }
298     }
299 
300     return sFalse;
301 }
302 
303 
304 /**
305   Change an internal state.
306 */
setState(const char * key,const char * value)307 void PluginMIDICCRecorder::setState(const char* key, const char* value) {
308     int index;
309 
310     if (std::strncmp(key, "ch-", 3) == 0) {
311         try {
312             index = std::stoi(key+3);
313         } catch (...) {
314             return;
315         }
316 
317         if (index < (int) stateCount) {
318             if (!std::strcmp(value, "false") == 0) {
319                 std::vector<uint8_t> state = d_getChunkFromBase64String(value);
320 
321                 for (uint i=0; i < state.size(); i++) {
322                     stateCC[index][i] = state[i];
323                 }
324 
325                 // std::cerr << "State 'ch-" << index << "': ";
326                 // for (uint i=0; i < 128; i++) {
327                 //     std::cerr << (uint) stateCC[index][i] << ",";
328                 // }
329                 // std::cerr << std::endl;
330             }
331         }
332     }
333 }
334 
335 
336 /**
337   Clear all internal state.
338 */
clearState()339 void PluginMIDICCRecorder::clearState() {
340     for (uint i=0; i < 16; i++) {
341         for (uint j=0; j < 128; j++) {
342             stateCC[i][j] = 0xFF;
343         }
344     }
345 }
346 
347 /**
348   Load a program.
349   The host may call this function from any context,
350   including realtime processing.
351 */
loadProgram(uint32_t index)352 void PluginMIDICCRecorder::loadProgram(uint32_t index) {
353     if (index < presetCount) {
354         for (int i=0; i < paramCount; i++) {
355             setParameterValue(i, factoryPresets[index].params[i]);
356         }
357 
358     }
359 }
360 
361 // -----------------------------------------------------------------------
362 // Process
363 
364 /*
365  *  Plugin is activated.
366  */
activate()367 void PluginMIDICCRecorder::activate() {
368     fSampleRate = getSampleRate();
369     sendInProgress = false;
370     curChan = 0;
371     curCC = 0;
372 }
373 
374 /*
375  *  Start sending.
376  */
startSend()377 void PluginMIDICCRecorder::startSend() {
378     if (!sendInProgress) {
379         sendChannel = fParams[paramSendChannel];
380         curChan = 0;
381         curCC = 0;
382     }
383 
384     sendInProgress = true;
385 }
386 
387 
388 
run(const float **,float **,uint32_t nframes,const MidiEvent * events,uint32_t eventCount)389 void PluginMIDICCRecorder::run(const float**, float**, uint32_t nframes,
390                                const MidiEvent* events, uint32_t eventCount) {
391     uint8_t cc, chan, status;
392     struct MidiEvent cc_event;
393     bool block, start_send = false;
394     static uint32_t next_frame = 0;
395 
396     const TimePosition& pos(getTimePosition());
397     uint8_t trig_pc = (uint8_t) fParams[paramTrigPC];
398     uint8_t trig_pc_chan = (uint8_t) fParams[paramTrigPCChannel];
399 
400     for (uint32_t i=0; i<eventCount; ++i) {
401         block = false;
402         status = events[i].data[0] & 0xF0;
403 
404         if (events[i].frame > next_frame) {
405             next_frame = events[i].frame;
406         }
407 
408         if (status >= 0xF0) {
409             writeMidiEvent(events[i]);
410             continue;
411         }
412 
413         if (status < 0xF0) {
414             chan = events[i].data[0] & 0x0F;
415 
416             if (status == MIDI_CONTROL_CHANGE) {
417                 if (sendInProgress && (sendChannel == 0 || sendChannel == chan + 1))
418                     block = true;
419 
420                 if (fParams[paramRecordEnable] && ! sendInProgress) {
421                     cc = events[i].data[1] & 0x7F;
422                     stateCC[chan][cc] = events[i].data[2] & 0x7F;
423                 }
424             }
425             else if (status == MIDI_PROGRAM_CHANGE &&
426                      (trig_pc_chan == 0 || trig_pc_chan == chan + 1) &&
427                       events[i].data[1] == trig_pc) {
428                 // trigger start of sending after MIDI events have been handled
429                 start_send = true;
430             }
431         }
432 
433         if (!block) writeMidiEvent(events[i]);
434     }
435 
436     if (pos.playing and !playing) {
437         playing = true;
438 
439         if (fParams[paramTrigTransport] == 1 ||
440            (fParams[paramTrigTransport] == 2 && pos.frame == 0)) {
441             startSend();
442         }
443     }
444     else if (!pos.playing && playing) {
445         playing = false;
446     }
447 
448     if (start_send) {
449         startSend();
450     }
451 
452     if (sendInProgress) {
453         bool sendOk = true;
454 
455         do {
456             if (next_frame >= nframes) {
457                 next_frame -= nframes;
458                 break;
459             }
460 
461             if ((sendChannel == 0 || sendChannel == curChan + 1) &&
462                 stateCC[curChan][curCC] != 0xFF)
463             {
464                 cc_event.frame = next_frame;
465                 cc_event.size = 3;
466                 cc_event.data[0] = MIDI_CONTROL_CHANGE | (curChan & 0xF);
467                 cc_event.data[1] = curCC & 0x7F;
468                 cc_event.data[2] = stateCC[curChan][curCC] & 0x7F;
469                 sendOk = writeMidiEvent(cc_event);
470                 next_frame += cc_event.frame + (int) (fSampleRate / 1000 * fParams[paramSendInterval]);
471             }
472 
473             curCC++;
474 
475             if (curCC >= NUM_CONTROLLERS) {
476                 curCC = 0;
477                 curChan++;
478 
479                 if (curChan >= NUM_CHANNELS) {
480                     curChan = 0;
481                     sendInProgress = false;
482                     next_frame = 0;
483                     break;
484                 }
485             }
486 
487         } while (sendOk);
488     }
489 }
490 
491 // -----------------------------------------------------------------------
492 
createPlugin()493 Plugin* createPlugin() {
494     return new PluginMIDICCRecorder();
495 }
496 
497 // -----------------------------------------------------------------------
498 
499 END_NAMESPACE_DISTRHO
500