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