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"
25 #define TICKS_PER_BEAT 48
27 // -----------------------------------------------------------------------
29 class MidiPatternPlugin
: public NativePluginAndUiClass
,
30 public AbstractMidiPlayer
34 kParameterTimeSig
= 0,
41 MidiPatternPlugin(const NativeHostDescriptor
* const host
)
42 : NativePluginAndUiClass(host
, "midipattern-ui"),
43 fNeedsAllNotesOff(false),
44 fWasPlayingBefore(false),
49 fMaxTicksPerSigNum(0.0),
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 */;
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
;
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
;
108 param
.name
= "Measures";
109 param
.ranges
.def
= 4.0f
;
110 param
.ranges
.min
= 1.0f
;
111 param
.ranges
.max
= 16.0f
;
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
;
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
;
173 param
.hints
= static_cast<NativeParameterHints
>(hints
);
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
;
196 case kParameterTimeSig
:
197 fTimeSigNum
= static_cast<int>(value
+ 1.5f
);
199 case kParameterMeasures
:
200 fMaxTicksPerSigNum
= TICKS_PER_BEAT
* fTimeSigNum
* static_cast<double>(fParameters
[kParameterMeasures
]);
201 fNeedsAllNotesOff
= true;
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
)
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
;
233 midiEvent
.data
[0] = 0;
234 midiEvent
.data
[1] = MIDI_CONTROL_ALL_NOTES_OFF
;
235 midiEvent
.data
[2] = 0;
236 midiEvent
.data
[3] = 0;
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
))
251 NativeMidiEvent ev
= { 0, 0, 3, { 0, 0, 0, 0 } };
253 while (fMidiQueueRT
.get(d1
, d2
, 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());
271 if (fLastFrame
+ frames
== fTimeInfo
.frame
)
273 // continuous playback
274 playPos
= fLastPosition
+ fTicksPerFrame
* static_cast<double>(frames
);
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;
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 // -------------------------------------------------------------------
313 void uiShow(const bool show
) override
315 NativePluginAndUiClass::uiShow(show
);
321 void uiIdle() override
323 NativePluginAndUiClass::uiIdle();
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
),);
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
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
;
395 carla_stdout("Playing at %f|%u :: %03X:%03i:%03i",
397 static_cast<double>(midiEvent
.time
)*fTicksPerFrame
,
398 midiEvent
.data
[0], midiEvent
.data
[1], midiEvent
.data
[2]);
401 NativePluginAndUiClass::writeMidiEvent(&midiEvent
);
404 #ifndef CARLA_OS_WASM
405 // -------------------------------------------------------------------
408 bool msgReceived(const char* const msg
) noexcept override
410 if (NativePluginAndUiClass::msgReceived(msg
))
413 if (std::strcmp(msg
, "midi-clear-all") == 0)
416 fNeedsAllNotesOff
= true;
420 if (std::strcmp(msg
, "midi-note") == 0)
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
);
436 if (std::strcmp(msg
, "midievent-add") == 0)
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);
453 fMidiOut
.addRaw(time
, data
, size
);
457 if (std::strcmp(msg
, "midievent-remove") == 0)
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);
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);
491 // -------------------------------------------------------------------
494 bool fNeedsAllNotesOff
;
495 bool fWasPlayingBefore
;
498 double fLastPosition
;
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
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
);
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
,
569 /* paramIns */ MidiPatternPlugin::kParameterCount
,
571 /* name */ "MIDI Pattern",
572 /* label */ "midipattern",
573 /* maker */ "falkTX, tatch",
574 /* copyright */ "GNU GPL v2+",
575 PluginDescriptorFILL(MidiPatternPlugin
)
578 // -----------------------------------------------------------------------
581 void carla_register_native_plugin_midipattern();
584 void carla_register_native_plugin_midipattern()
586 carla_register_native_plugin(&midipatternDesc
);
589 // -----------------------------------------------------------------------