Fix potential wrong-over-optimization in math utilities
[carla.git] / source / native-plugins / midi-pattern.cpp
blob9bf65aa85fc82b5b4549779f1d7544b129918f93
1 /*
2 * Carla Native Plugins
3 * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of
8 * the License, or any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
18 #include "CarlaNativeExtUI.hpp"
19 #include "RtLinkedList.hpp"
21 #include "midi-base.hpp"
22 #include "midi-queue.hpp"
24 // matches UI side
25 #define TICKS_PER_BEAT 48
27 // -----------------------------------------------------------------------
29 class MidiPatternPlugin : public NativePluginAndUiClass,
30 public AbstractMidiPlayer
32 public:
33 enum Parameters {
34 kParameterTimeSig = 0,
35 kParameterMeasures,
36 kParameterDefLength,
37 kParameterQuantize,
38 kParameterCount
41 MidiPatternPlugin(const NativeHostDescriptor* const host)
42 : NativePluginAndUiClass(host, "midipattern-ui"),
43 fNeedsAllNotesOff(false),
44 fWasPlayingBefore(false),
45 fTimeSigNum(4),
46 fLastPosition(0.0),
47 fLastFrame(0),
48 fTicksPerFrame(0.0),
49 fMaxTicksPerSigNum(0.0),
50 fMidiOut(this),
51 fTimeInfo(),
52 fMidiQueue(),
53 fMidiQueueRT()
55 carla_zeroStruct(fTimeInfo);
57 // set default param values
58 fParameters[kParameterTimeSig] = 3.0f;
59 fParameters[kParameterMeasures] = 4.0f;
60 fParameters[kParameterDefLength] = 4.0f;
61 fParameters[kParameterQuantize] = 4.0f;
63 fMaxTicksPerSigNum = TICKS_PER_BEAT * fTimeSigNum * 4 /* kParameterMeasures */;
66 protected:
67 // -------------------------------------------------------------------
68 // Plugin parameter calls
70 uint32_t getParameterCount() const override
72 return kParameterCount;
75 const NativeParameter* getParameterInfo(const uint32_t index) const override
77 CARLA_SAFE_ASSERT_RETURN(index < kParameterCount, nullptr);
79 static NativeParameter param;
80 static NativeParameterScalePoint scalePoints[10];
82 int hints = NATIVE_PARAMETER_IS_ENABLED|NATIVE_PARAMETER_IS_AUTOMATABLE|NATIVE_PARAMETER_IS_INTEGER;
84 switch (index)
86 case 0:
87 hints |= NATIVE_PARAMETER_USES_SCALEPOINTS;
88 param.name = "Time Signature";
89 param.ranges.def = 3.0f;
90 param.ranges.min = 0.0f;
91 param.ranges.max = 5.0f;
92 scalePoints[0].value = 0.0f;
93 scalePoints[0].label = "1/4";
94 scalePoints[1].value = 1.0f;
95 scalePoints[1].label = "2/4";
96 scalePoints[2].value = 2.0f;
97 scalePoints[2].label = "3/4";
98 scalePoints[3].value = 3.0f;
99 scalePoints[3].label = "4/4";
100 scalePoints[4].value = 4.0f;
101 scalePoints[4].label = "5/4";
102 scalePoints[5].value = 5.0f;
103 scalePoints[5].label = "6/4";
104 param.scalePointCount = 6;
105 param.scalePoints = scalePoints;
106 break;
107 case 1:
108 param.name = "Measures";
109 param.ranges.def = 4.0f;
110 param.ranges.min = 1.0f;
111 param.ranges.max = 16.0f;
112 break;
113 case 2:
114 hints |= NATIVE_PARAMETER_USES_SCALEPOINTS;
115 param.name = "Default Length";
116 param.ranges.def = 4.0f;
117 param.ranges.min = 0.0f;
118 param.ranges.max = 9.0f;
119 scalePoints[0].value = 0.0f;
120 scalePoints[0].label = "1/16";
121 scalePoints[1].value = 1.0f;
122 scalePoints[1].label = "1/15";
123 scalePoints[2].value = 2.0f;
124 scalePoints[2].label = "1/12";
125 scalePoints[3].value = 3.0f;
126 scalePoints[3].label = "1/9";
127 scalePoints[4].value = 4.0f;
128 scalePoints[4].label = "1/8";
129 scalePoints[5].value = 5.0f;
130 scalePoints[5].label = "1/6";
131 scalePoints[6].value = 6.0f;
132 scalePoints[6].label = "1/4";
133 scalePoints[7].value = 7.0f;
134 scalePoints[7].label = "1/3";
135 scalePoints[8].value = 8.0f;
136 scalePoints[8].label = "1/2";
137 scalePoints[9].value = 9.0f;
138 scalePoints[9].label = "1";
139 param.scalePointCount = 10;
140 param.scalePoints = scalePoints;
141 break;
142 case 3:
143 hints |= NATIVE_PARAMETER_USES_SCALEPOINTS;
144 param.name = "Quantize";
145 param.ranges.def = 4.0f;
146 param.ranges.min = 0.0f;
147 param.ranges.max = 9.0f;
148 scalePoints[0].value = 0.0f;
149 scalePoints[0].label = "1/16";
150 scalePoints[1].value = 1.0f;
151 scalePoints[1].label = "1/15";
152 scalePoints[2].value = 2.0f;
153 scalePoints[2].label = "1/12";
154 scalePoints[3].value = 3.0f;
155 scalePoints[3].label = "1/9";
156 scalePoints[4].value = 4.0f;
157 scalePoints[4].label = "1/8";
158 scalePoints[5].value = 5.0f;
159 scalePoints[5].label = "1/6";
160 scalePoints[6].value = 6.0f;
161 scalePoints[6].label = "1/4";
162 scalePoints[7].value = 7.0f;
163 scalePoints[7].label = "1/3";
164 scalePoints[8].value = 8.0f;
165 scalePoints[8].label = "1/2";
166 scalePoints[9].value = 9.0f;
167 scalePoints[9].label = "1";
168 param.scalePointCount = 10;
169 param.scalePoints = scalePoints;
170 break;
173 param.hints = static_cast<NativeParameterHints>(hints);
175 return &param;
178 float getParameterValue(const uint32_t index) const override
180 CARLA_SAFE_ASSERT_RETURN(index < kParameterCount, 0.0f);
182 return fParameters[index];
185 // -------------------------------------------------------------------
186 // Plugin state calls
188 void setParameterValue(const uint32_t index, const float value) override
190 CARLA_SAFE_ASSERT_RETURN(index < kParameterCount,);
192 fParameters[index] = value;
194 switch (index)
196 case kParameterTimeSig:
197 fTimeSigNum = static_cast<int>(value + 1.5f);
198 // fall through
199 case kParameterMeasures:
200 fMaxTicksPerSigNum = TICKS_PER_BEAT * fTimeSigNum * static_cast<double>(fParameters[kParameterMeasures]);
201 fNeedsAllNotesOff = true;
202 break;
206 // -------------------------------------------------------------------
207 // Plugin process calls
209 void process(const float* const*, float**, const uint32_t frames,
210 const NativeMidiEvent* /*midiEvents*/, uint32_t /*midiEventCount*/) override
212 if (const NativeTimeInfo* const timeInfo = getTimeInfo())
213 fTimeInfo = *timeInfo;
215 if (fWasPlayingBefore != fTimeInfo.playing)
217 fLastFrame = 0;
218 fLastPosition = 0.0;
219 fNeedsAllNotesOff = true;
220 fWasPlayingBefore = fTimeInfo.playing;
222 else if (fTimeInfo.playing && fLastFrame + frames != fTimeInfo.frame)
224 fNeedsAllNotesOff = true;
227 if (fNeedsAllNotesOff)
229 NativeMidiEvent midiEvent;
231 midiEvent.port = 0;
232 midiEvent.time = 0;
233 midiEvent.data[0] = 0;
234 midiEvent.data[1] = MIDI_CONTROL_ALL_NOTES_OFF;
235 midiEvent.data[2] = 0;
236 midiEvent.data[3] = 0;
237 midiEvent.size = 3;
239 for (int channel=MAX_MIDI_CHANNELS; --channel >= 0;)
241 midiEvent.data[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (channel & MIDI_CHANNEL_BIT));
242 NativePluginAndUiClass::writeMidiEvent(&midiEvent);
245 fNeedsAllNotesOff = false;
248 if (fMidiQueue.isNotEmpty() && fMidiQueueRT.tryToCopyDataFrom(fMidiQueue))
250 uint8_t d1, d2, d3;
251 NativeMidiEvent ev = { 0, 0, 3, { 0, 0, 0, 0 } };
253 while (fMidiQueueRT.get(d1, d2, d3))
255 ev.data[0] = d1;
256 ev.data[1] = d2;
257 ev.data[2] = d3;
258 NativePluginAndUiClass::writeMidiEvent(&ev);
262 if (fTimeInfo.playing)
264 if (! fTimeInfo.bbt.valid)
265 fTimeInfo.bbt.beatsPerMinute = 120.0;
267 fTicksPerFrame = TICKS_PER_BEAT / (60.0 / fTimeInfo.bbt.beatsPerMinute * getSampleRate());
269 double playPos;
271 if (fLastFrame + frames == fTimeInfo.frame)
273 // continuous playback
274 playPos = fLastPosition + fTicksPerFrame * static_cast<double>(frames);
276 else
278 // non-continuous, reset playPos
279 playPos = fTicksPerFrame * static_cast<double>(fTimeInfo.frame);
282 const double endPos = playPos + fTicksPerFrame * static_cast<double>(frames);
283 const double loopedEndPos = std::fmod(endPos, fMaxTicksPerSigNum);
285 for (; playPos < endPos; playPos += fMaxTicksPerSigNum)
287 const double loopedPlayPos = std::fmod(playPos, fMaxTicksPerSigNum);
289 if (loopedEndPos >= loopedPlayPos)
291 if (! fMidiOut.play(loopedPlayPos, loopedEndPos-loopedPlayPos))
292 fNeedsAllNotesOff = true;
294 else
296 const double diff = fMaxTicksPerSigNum - loopedPlayPos;
298 if (! (fMidiOut.play(loopedPlayPos, diff) && fMidiOut.play(0.0, loopedEndPos, diff)))
299 fNeedsAllNotesOff = true;
303 fLastPosition = playPos;
306 fLastFrame = fTimeInfo.frame;
309 #ifndef CARLA_OS_WASM
310 // -------------------------------------------------------------------
311 // Plugin UI calls
313 void uiShow(const bool show) override
315 NativePluginAndUiClass::uiShow(show);
317 if (show)
318 _sendEventsToUI();
321 void uiIdle() override
323 NativePluginAndUiClass::uiIdle();
325 // send transport
326 if (isPipeRunning())
328 char strBuf[0xff+1];
329 carla_zeroChars(strBuf, 0xff+1);
331 const double beatsPerBar = fTimeSigNum;
332 const double beatsPerMinute = fTimeInfo.bbt.valid ? fTimeInfo.bbt.beatsPerMinute : 120.0;
334 const double ticksPerBeat = TICKS_PER_BEAT;
335 const double fullTicks = fLastPosition;
336 const double fullBeats = fullTicks / ticksPerBeat;
338 const uint32_t tick = static_cast<uint32_t>(std::floor(std::fmod(fullTicks, ticksPerBeat)));
339 const uint32_t beat = static_cast<uint32_t>(std::floor(std::fmod(fullBeats, beatsPerBar)));
340 const uint32_t bar = static_cast<uint32_t>(std::floor(fullBeats/beatsPerBar));
342 const CarlaMutexLocker cml(getPipeLock());
344 CARLA_SAFE_ASSERT_RETURN(writeMessage("transport\n"),);
346 std::snprintf(strBuf, 0xff, "%i:" P_UINT64 ":%i:%i:%i\n", int(fTimeInfo.playing), fTimeInfo.frame, bar, beat, tick);
347 CARLA_SAFE_ASSERT_RETURN(writeMessage(strBuf),);
350 const CarlaScopedLocale csl;
351 std::snprintf(strBuf, 0xff, "%.12g\n", beatsPerMinute);
354 CARLA_SAFE_ASSERT_RETURN(writeMessage(strBuf),);
356 syncMessages();
359 #endif
361 // -------------------------------------------------------------------
362 // Plugin state calls
364 char* getState() const override
366 return fMidiOut.getState();
369 void setState(const char* const data) override
371 fMidiOut.setState(data);
373 #ifndef CARLA_OS_WASM
374 if (isPipeRunning())
375 _sendEventsToUI();
376 #endif
379 // -------------------------------------------------------------------
380 // AbstractMidiPlayer calls
382 void writeMidiEvent(const uint8_t port, const double timePosFrame, const RawMidiEvent* const event) override
384 NativeMidiEvent midiEvent;
386 midiEvent.port = port;
387 midiEvent.time = uint32_t(timePosFrame/fTicksPerFrame);
388 midiEvent.data[0] = event->data[0];
389 midiEvent.data[1] = event->data[1];
390 midiEvent.data[2] = event->data[2];
391 midiEvent.data[3] = event->data[3];
392 midiEvent.size = event->size;
394 #ifdef DEBUG
395 carla_stdout("Playing at %f|%u :: %03X:%03i:%03i",
396 midiEvent.time,
397 static_cast<double>(midiEvent.time)*fTicksPerFrame,
398 midiEvent.data[0], midiEvent.data[1], midiEvent.data[2]);
399 #endif
401 NativePluginAndUiClass::writeMidiEvent(&midiEvent);
404 #ifndef CARLA_OS_WASM
405 // -------------------------------------------------------------------
406 // Pipe Server calls
408 bool msgReceived(const char* const msg) noexcept override
410 if (NativePluginAndUiClass::msgReceived(msg))
411 return true;
413 if (std::strcmp(msg, "midi-clear-all") == 0)
415 fMidiOut.clear();
416 fNeedsAllNotesOff = true;
417 return true;
420 if (std::strcmp(msg, "midi-note") == 0)
422 uint8_t note;
423 bool on;
424 CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(note), true);
425 CARLA_SAFE_ASSERT_RETURN(readNextLineAsBool(on), true);
427 const uint8_t status = on ? MIDI_STATUS_NOTE_ON : MIDI_STATUS_NOTE_OFF;
428 const uint8_t velocity = on ? 100 : 0;
430 const CarlaMutexLocker cml(fMidiQueue.getMutex());
432 fMidiQueue.put(status, note, velocity);
433 return true;
436 if (std::strcmp(msg, "midievent-add") == 0)
438 uint32_t time;
439 uint8_t size;
441 CARLA_SAFE_ASSERT_RETURN(readNextLineAsUInt(time), true);
442 CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(size), true);
443 CARLA_SAFE_ASSERT_RETURN(size > 0, true);
445 uint8_t data[size], dvalue;
447 for (uint8_t i=0; i<size; ++i)
449 CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(dvalue), true);
450 data[i] = dvalue;
453 fMidiOut.addRaw(time, data, size);
454 return true;
457 if (std::strcmp(msg, "midievent-remove") == 0)
459 uint32_t time;
460 uint8_t size;
462 CARLA_SAFE_ASSERT_RETURN(readNextLineAsUInt(time), true);
463 CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(size), true);
464 CARLA_SAFE_ASSERT_RETURN(size > 0, true);
466 uint8_t data[size], dvalue;
468 for (uint8_t i=0; i<size; ++i)
470 CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(dvalue), true);
471 data[i] = dvalue;
474 fMidiOut.removeRaw(time, data, size);
476 if (MIDI_IS_STATUS_NOTE_ON(data[0]))
478 const uint8_t status = MIDI_STATUS_NOTE_OFF | (data[0] & MIDI_CHANNEL_BIT);
480 const CarlaMutexLocker cml(fMidiQueue.getMutex());
481 fMidiQueue.put(status, data[1], 0);
484 return true;
487 return false;
489 #endif
491 // -------------------------------------------------------------------
493 private:
494 bool fNeedsAllNotesOff;
495 bool fWasPlayingBefore;
496 int fTimeSigNum;
498 double fLastPosition;
499 uint64_t fLastFrame;
501 double fTicksPerFrame;
502 double fMaxTicksPerSigNum;
504 MidiPattern fMidiOut;
505 NativeTimeInfo fTimeInfo;
507 MIDIEventQueue<32> fMidiQueue, fMidiQueueRT;
509 float fParameters[kParameterCount];
511 #ifndef CARLA_OS_WASM
512 void _sendEventsToUI() const noexcept
514 char strBuf[0xff+1];
515 carla_zeroChars(strBuf, 0xff);
517 const CarlaMutexLocker cml1(getPipeLock());
518 const CarlaMutexLocker cml2(fMidiOut.getWriteMutex());
520 writeMessage("midi-clear-all\n", 15);
522 writeMessage("parameters\n", 11);
523 std::snprintf(strBuf, 0xff, "%i:%i:%i:%i\n",
524 static_cast<int>(fParameters[kParameterTimeSig]),
525 static_cast<int>(fParameters[kParameterMeasures]),
526 static_cast<int>(fParameters[kParameterDefLength]),
527 static_cast<int>(fParameters[kParameterQuantize]));
528 writeMessage(strBuf);
530 for (LinkedList<const RawMidiEvent*>::Itenerator it = fMidiOut.iteneratorBegin(); it.valid(); it.next())
532 const RawMidiEvent* const rawMidiEvent(it.getValue(nullptr));
533 CARLA_SAFE_ASSERT_CONTINUE(rawMidiEvent != nullptr);
535 writeMessage("midievent-add\n", 14);
537 std::snprintf(strBuf, 0xff, "%u\n", rawMidiEvent->time);
538 writeMessage(strBuf);
540 std::snprintf(strBuf, 0xff, "%i\n", rawMidiEvent->size);
541 writeMessage(strBuf);
543 for (uint8_t i=0, size=rawMidiEvent->size; i<size; ++i)
545 std::snprintf(strBuf, 0xff, "%i\n", rawMidiEvent->data[i]);
546 writeMessage(strBuf);
550 #endif
552 PluginClassEND(MidiPatternPlugin)
553 CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MidiPatternPlugin)
556 // -----------------------------------------------------------------------
558 static const NativePluginDescriptor midipatternDesc = {
559 /* category */ NATIVE_PLUGIN_CATEGORY_UTILITY,
560 /* hints */ static_cast<NativePluginHints>(NATIVE_PLUGIN_IS_RTSAFE
561 |NATIVE_PLUGIN_HAS_UI
562 |NATIVE_PLUGIN_USES_STATE
563 |NATIVE_PLUGIN_USES_TIME),
564 /* supports */ NATIVE_PLUGIN_SUPPORTS_NOTHING,
565 /* audioIns */ 0,
566 /* audioOuts */ 0,
567 /* midiIns */ 0,
568 /* midiOuts */ 1,
569 /* paramIns */ MidiPatternPlugin::kParameterCount,
570 /* paramOuts */ 0,
571 /* name */ "MIDI Pattern",
572 /* label */ "midipattern",
573 /* maker */ "falkTX, tatch",
574 /* copyright */ "GNU GPL v2+",
575 PluginDescriptorFILL(MidiPatternPlugin)
578 // -----------------------------------------------------------------------
580 CARLA_API_EXPORT
581 void carla_register_native_plugin_midipattern();
583 CARLA_API_EXPORT
584 void carla_register_native_plugin_midipattern()
586 carla_register_native_plugin(&midipatternDesc);
589 // -----------------------------------------------------------------------