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 "CarlaNativePrograms.hpp"
19 #include "midi-base.hpp"
21 #include "water/files/FileInputStream.h"
22 #include "water/midi/MidiFile.h"
24 // -----------------------------------------------------------------------
26 class MidiFilePlugin
: public NativePluginWithMidiPrograms
<FileMIDI
>,
27 public AbstractMidiPlayer
34 kParameterInfoNumTracks
,
36 kParameterInfoPosition
,
40 MidiFilePlugin(const NativeHostDescriptor
* const host
)
41 : NativePluginWithMidiPrograms
<FileMIDI
>(host
, fPrograms
, 0),
43 #ifdef __MOD_DEVICES__
49 fNeedsAllNotesOff(false),
50 fWasPlayingBefore(false),
55 fInternalTransportFrame(0),
58 fPrograms(hostGetFilePath("midi"), "*.mid;*.midi")
63 // -------------------------------------------------------------------
64 // Plugin parameter calls
66 uint32_t getParameterCount() const override
68 return kParameterCount
;
71 const NativeParameter
* getParameterInfo(const uint32_t index
) const override
73 static NativeParameter param
;
75 param
.scalePointCount
= 0;
76 param
.scalePoints
= nullptr;
78 param
.ranges
.step
= 1.0f
;
79 param
.ranges
.stepSmall
= 1.0f
;
80 param
.ranges
.stepLarge
= 1.0f
;
81 param
.designation
= NATIVE_PARAMETER_DESIGNATION_NONE
;
85 case kParameterRepeating
:
86 param
.name
= "Repeat Mode";
87 param
.hints
= static_cast<NativeParameterHints
>(NATIVE_PARAMETER_IS_AUTOMATABLE
|
88 NATIVE_PARAMETER_IS_ENABLED
|
89 NATIVE_PARAMETER_IS_BOOLEAN
);
90 param
.ranges
.def
= 0.0f
;
91 param
.ranges
.min
= 0.0f
;
92 param
.ranges
.max
= 1.0f
;
94 case kParameterHostSync
:
95 param
.name
= "Host Sync";
96 param
.hints
= static_cast<NativeParameterHints
>(NATIVE_PARAMETER_IS_AUTOMATABLE
|
97 NATIVE_PARAMETER_IS_ENABLED
|
98 NATIVE_PARAMETER_IS_BOOLEAN
);
99 #ifdef __MOD_DEVICES__
100 param
.ranges
.def
= 0.0f
;
102 param
.ranges
.def
= 1.0f
;
104 param
.ranges
.min
= 0.0f
;
105 param
.ranges
.max
= 1.0f
;
107 case kParameterEnabled
:
108 param
.name
= "Enabled";
109 param
.hints
= static_cast<NativeParameterHints
>(NATIVE_PARAMETER_IS_AUTOMATABLE
|
110 NATIVE_PARAMETER_IS_ENABLED
|
111 NATIVE_PARAMETER_IS_BOOLEAN
|
112 NATIVE_PARAMETER_USES_DESIGNATION
);
113 param
.ranges
.def
= 1.0f
;
114 param
.ranges
.min
= 0.0f
;
115 param
.ranges
.max
= 1.0f
;
116 param
.designation
= NATIVE_PARAMETER_DESIGNATION_ENABLED
;
118 case kParameterInfoNumTracks
:
119 param
.name
= "Num Tracks";
120 param
.hints
= static_cast<NativeParameterHints
>(NATIVE_PARAMETER_IS_AUTOMATABLE
|
121 NATIVE_PARAMETER_IS_ENABLED
|
122 NATIVE_PARAMETER_IS_INTEGER
|
123 NATIVE_PARAMETER_IS_OUTPUT
);
124 param
.ranges
.def
= 0.0f
;
125 param
.ranges
.min
= 0.0f
;
126 param
.ranges
.max
= 256.0f
;
128 case kParameterInfoLength
:
129 param
.name
= "Length";
130 param
.hints
= static_cast<NativeParameterHints
>(NATIVE_PARAMETER_IS_AUTOMATABLE
|
131 NATIVE_PARAMETER_IS_ENABLED
|
132 NATIVE_PARAMETER_IS_OUTPUT
);
133 param
.ranges
.def
= 0.0f
;
134 param
.ranges
.min
= 0.0f
;
135 param
.ranges
.max
= (float)INT64_MAX
;
138 case kParameterInfoPosition
:
139 param
.name
= "Position";
140 param
.hints
= static_cast<NativeParameterHints
>(NATIVE_PARAMETER_IS_AUTOMATABLE
|
141 NATIVE_PARAMETER_IS_ENABLED
|
142 NATIVE_PARAMETER_IS_OUTPUT
);
143 param
.ranges
.def
= 0.0f
;
144 param
.ranges
.min
= 0.0f
;
145 param
.ranges
.max
= 100.0f
;
155 float getParameterValue(const uint32_t index
) const override
159 case kParameterRepeating
:
160 return fRepeatMode
? 1.0f
: 0.0f
;
161 case kParameterHostSync
:
162 return fHostSync
? 1.0f
: 0.0f
;
163 case kParameterEnabled
:
164 return fEnabled
? 1.0f
: 0.0f
;
165 case kParameterInfoNumTracks
:
167 case kParameterInfoLength
:
169 case kParameterInfoPosition
:
170 return fLastPosition
;
176 // -------------------------------------------------------------------
177 // Plugin state calls
179 void setParameterValue(const uint32_t index
, const float value
) override
181 const bool b
= (value
> 0.5f
);
185 case kParameterRepeating
:
186 if (fRepeatMode
!= b
)
189 fNeedsAllNotesOff
= true;
192 case kParameterHostSync
:
195 fInternalTransportFrame
= 0;
199 case kParameterEnabled
:
202 fInternalTransportFrame
= 0;
211 void setCustomData(const char* const key
, const char* const value
) override
213 CARLA_SAFE_ASSERT_RETURN(key
!= nullptr && key
[0] != '\0',);
214 CARLA_SAFE_ASSERT_RETURN(value
!= nullptr && value
[0] != '\0',);
216 if (std::strcmp(key
, "file") != 0)
219 invalidateNextFilename();
220 _loadMidiFile(value
);
223 // -------------------------------------------------------------------
224 // Plugin process calls
226 void process2(const float* const*, float**, const uint32_t frames
, const NativeMidiEvent
* const, const uint32_t) override
228 const uint32_t maxFrame
= fMaxFrame
;
234 const NativeTimeInfo
* const timePos
= getTimeInfo();
235 playing
= fEnabled
&& timePos
->playing
;
236 frame
= timePos
->frame
;
241 frame
= fInternalTransportFrame
;
244 fInternalTransportFrame
+= frames
;
247 if (fRepeatMode
&& maxFrame
!= 0 && frame
>= maxFrame
)
250 if (fWasPlayingBefore
!= playing
|| frame
< fLastFrame
)
252 fNeedsAllNotesOff
= true;
253 fWasPlayingBefore
= playing
;
256 if (fNeedsAllNotesOff
)
258 NativeMidiEvent midiEvent
;
262 midiEvent
.data
[0] = 0;
263 midiEvent
.data
[1] = MIDI_CONTROL_ALL_NOTES_OFF
;
264 midiEvent
.data
[2] = 0;
265 midiEvent
.data
[3] = 0;
268 for (int channel
=MAX_MIDI_CHANNELS
; --channel
>= 0;)
270 midiEvent
.data
[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE
| (channel
& MIDI_CHANNEL_BIT
));
271 NativePluginClass::writeMidiEvent(&midiEvent
);
274 fNeedsAllNotesOff
= false;
277 if (fWasPlayingBefore
)
278 if (! fMidiOut
.play(static_cast<uint32_t>(frame
), frames
))
279 fNeedsAllNotesOff
= true;
283 if (frame
< maxFrame
)
284 fLastPosition
= static_cast<float>(frame
) / static_cast<float>(maxFrame
) * 100.0f
;
286 fLastPosition
= 100.0f
;
289 // -------------------------------------------------------------------
292 void uiShow(const bool show
) override
297 if (const char* const filename
= uiOpenFile(false, "Open MIDI File", "MIDI Files (*.mid *.midi);;"))
298 uiCustomDataChanged("file", filename
);
303 // -------------------------------------------------------------------
304 // Plugin state calls
306 char* getState() const override
308 return fMidiOut
.getState();
311 void setState(const char* const data
) override
313 fMidiOut
.setState(data
);
316 void setStateFromFile(const char* const filename
) override
318 _loadMidiFile(filename
);
321 // -------------------------------------------------------------------
322 // AbstractMidiPlayer calls
324 void writeMidiEvent(const uint8_t port
, const double timePosFrame
, const RawMidiEvent
* const event
) override
326 NativeMidiEvent midiEvent
;
328 midiEvent
.port
= port
;
329 midiEvent
.time
= static_cast<uint32_t>(timePosFrame
);
330 midiEvent
.size
= event
->size
;
331 midiEvent
.data
[0] = event
->data
[0];
332 midiEvent
.data
[1] = event
->data
[1];
333 midiEvent
.data
[2] = event
->data
[2];
334 midiEvent
.data
[3] = event
->data
[3];
336 NativePluginClass::writeMidiEvent(&midiEvent
);
339 // -------------------------------------------------------------------
345 bool fNeedsAllNotesOff
;
346 bool fWasPlayingBefore
;
348 MidiPattern fMidiOut
;
351 uint32_t fInternalTransportFrame
;
354 NativeMidiPrograms fPrograms
;
356 void _loadMidiFile(const char* const filename
)
359 fInternalTransportFrame
= 0;
364 fLastPosition
= 0.0f
;
366 using namespace water
;
368 const String jfilename
= String(CharPointer_UTF8(filename
));
369 File
file(jfilename
);
371 if (! file
.existsAsFile())
374 FileInputStream
fileStream(file
);
377 if (! midiFile
.readFrom(fileStream
))
380 midiFile
.convertTimestampTicksToSeconds();
382 const double sampleRate
= getSampleRate();
383 const size_t numTracks
= midiFile
.getNumTracks();
385 for (size_t i
=0; i
<numTracks
; ++i
)
387 const MidiMessageSequence
* const track
= midiFile
.getTrack(i
);
388 CARLA_SAFE_ASSERT_CONTINUE(track
!= nullptr);
390 for (int j
=0, numEvents
= track
->getNumEvents(); j
<numEvents
; ++j
)
392 const MidiMessageSequence::MidiEventHolder
* const midiEventHolder
= track
->getEventPointer(j
);
393 CARLA_SAFE_ASSERT_CONTINUE(midiEventHolder
!= nullptr);
395 const MidiMessage
& midiMessage(midiEventHolder
->message
);
397 const int dataSize
= midiMessage
.getRawDataSize();
398 if (dataSize
<= 0 || dataSize
> MAX_EVENT_DATA_SIZE
)
401 const uint8_t* const data
= midiMessage
.getRawData();
402 if (! MIDI_IS_CHANNEL_MESSAGE(data
[0]))
405 const double time
= midiMessage
.getTimeStamp() * sampleRate
;
406 // const double time = track->getEventTime(i) * sampleRate;
407 CARLA_SAFE_ASSERT_CONTINUE(time
>= 0.0);
409 fMidiOut
.addRaw(static_cast<uint32_t>(time
+ 0.5),
410 midiMessage
.getRawData(), static_cast<uint8_t>(dataSize
));
414 const double lastTimeStamp
= midiFile
.getLastTimestamp();
416 fFileLength
= static_cast<float>(lastTimeStamp
);
417 fNumTracks
= static_cast<float>(numTracks
);
418 fNeedsAllNotesOff
= true;
419 fInternalTransportFrame
= 0;
421 fMaxFrame
= static_cast<uint32_t>(lastTimeStamp
* sampleRate
+ 0.5);
424 PluginClassEND(MidiFilePlugin
)
425 CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MidiFilePlugin
)
428 // -----------------------------------------------------------------------
430 static const NativePluginDescriptor midifileDesc
= {
431 /* category */ NATIVE_PLUGIN_CATEGORY_UTILITY
,
432 /* hints */ static_cast<NativePluginHints
>(NATIVE_PLUGIN_IS_RTSAFE
433 |NATIVE_PLUGIN_HAS_UI
434 |NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE
435 |NATIVE_PLUGIN_REQUESTS_IDLE
436 |NATIVE_PLUGIN_USES_STATE
437 |NATIVE_PLUGIN_USES_TIME
),
438 /* supports */ NATIVE_PLUGIN_SUPPORTS_NOTHING
,
445 /* name */ "MIDI File",
446 /* label */ "midifile",
447 /* maker */ "falkTX",
448 /* copyright */ "GNU GPL v2+",
449 PluginDescriptorFILL(MidiFilePlugin
)
452 // -----------------------------------------------------------------------
455 void carla_register_native_plugin_midifile();
458 void carla_register_native_plugin_midifile()
460 carla_register_native_plugin(&midifileDesc
);
463 // -----------------------------------------------------------------------