Fix wine build
[carla.git] / source / native-plugins / midi-file.cpp
blob1fa5eb8cc9ec382ff1464b1eea60a9ea959697e7
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 "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
29 public:
30 enum Parameters {
31 kParameterRepeating,
32 kParameterHostSync,
33 kParameterEnabled,
34 kParameterInfoNumTracks,
35 kParameterInfoLength,
36 kParameterInfoPosition,
37 kParameterCount
40 MidiFilePlugin(const NativeHostDescriptor* const host)
41 : NativePluginWithMidiPrograms<FileMIDI>(host, fPrograms, 0),
42 fRepeatMode(false),
43 #ifdef __MOD_DEVICES__
44 fHostSync(false),
45 #else
46 fHostSync(true),
47 #endif
48 fEnabled(true),
49 fNeedsAllNotesOff(false),
50 fWasPlayingBefore(false),
51 fLastPosition(0.0f),
52 fMidiOut(this),
53 fFileLength(0.0f),
54 fNumTracks(0.0f),
55 fInternalTransportFrame(0),
56 fMaxFrame(0),
57 fLastFrame(0),
58 fPrograms(hostGetFilePath("midi"), "*.mid;*.midi")
62 protected:
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;
77 param.unit = 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;
83 switch (index)
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;
93 break;
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;
101 #else
102 param.ranges.def = 1.0f;
103 #endif
104 param.ranges.min = 0.0f;
105 param.ranges.max = 1.0f;
106 break;
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;
117 break;
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;
127 break;
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;
136 param.unit = "s";
137 break;
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;
146 param.unit = "%";
147 break;
148 default:
149 return nullptr;
152 return &param;
155 float getParameterValue(const uint32_t index) const override
157 switch (index)
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:
166 return fNumTracks;
167 case kParameterInfoLength:
168 return fFileLength;
169 case kParameterInfoPosition:
170 return fLastPosition;
171 default:
172 return 0.0f;
176 // -------------------------------------------------------------------
177 // Plugin state calls
179 void setParameterValue(const uint32_t index, const float value) override
181 const bool b = (value > 0.5f);
183 switch (index)
185 case kParameterRepeating:
186 if (fRepeatMode != b)
188 fRepeatMode = b;
189 fNeedsAllNotesOff = true;
191 break;
192 case kParameterHostSync:
193 if (fHostSync != b)
195 fInternalTransportFrame = 0;
196 fHostSync = b;
198 break;
199 case kParameterEnabled:
200 if (fEnabled != b)
202 fInternalTransportFrame = 0;
203 fEnabled = b;
205 break;
206 default:
207 break;
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)
217 return;
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;
229 bool playing;
230 uint64_t frame;
232 if (fHostSync)
234 const NativeTimeInfo* const timePos = getTimeInfo();
235 playing = fEnabled && timePos->playing;
236 frame = timePos->frame;
238 else
240 playing = fEnabled;
241 frame = fInternalTransportFrame;
243 if (playing)
244 fInternalTransportFrame += frames;
247 if (fRepeatMode && maxFrame != 0 && frame >= maxFrame)
248 frame %= maxFrame;
250 if (fWasPlayingBefore != playing || frame < fLastFrame)
252 fNeedsAllNotesOff = true;
253 fWasPlayingBefore = playing;
256 if (fNeedsAllNotesOff)
258 NativeMidiEvent midiEvent;
260 midiEvent.port = 0;
261 midiEvent.time = 0;
262 midiEvent.data[0] = 0;
263 midiEvent.data[1] = MIDI_CONTROL_ALL_NOTES_OFF;
264 midiEvent.data[2] = 0;
265 midiEvent.data[3] = 0;
266 midiEvent.size = 3;
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;
281 fLastFrame = frame;
283 if (frame < maxFrame)
284 fLastPosition = static_cast<float>(frame) / static_cast<float>(maxFrame) * 100.0f;
285 else
286 fLastPosition = 100.0f;
289 // -------------------------------------------------------------------
290 // Plugin UI calls
292 void uiShow(const bool show) override
294 if (! show)
295 return;
297 if (const char* const filename = uiOpenFile(false, "Open MIDI File", "MIDI Files (*.mid *.midi);;"))
298 uiCustomDataChanged("file", filename);
300 uiClosed();
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 // -------------------------------------------------------------------
341 private:
342 bool fRepeatMode;
343 bool fHostSync;
344 bool fEnabled;
345 bool fNeedsAllNotesOff;
346 bool fWasPlayingBefore;
347 float fLastPosition;
348 MidiPattern fMidiOut;
349 float fFileLength;
350 float fNumTracks;
351 uint32_t fInternalTransportFrame;
352 uint32_t fMaxFrame;
353 uint64_t fLastFrame;
354 NativeMidiPrograms fPrograms;
356 void _loadMidiFile(const char* const filename)
358 fMidiOut.clear();
359 fInternalTransportFrame = 0;
360 fFileLength = 0.0f;
361 fNumTracks = 0.0f;
362 fMaxFrame = 0;
363 fLastFrame = 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())
372 return;
374 FileInputStream fileStream(file);
375 MidiFile midiFile;
377 if (! midiFile.readFrom(fileStream))
378 return;
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)
399 continue;
401 const uint8_t* const data = midiMessage.getRawData();
402 if (! MIDI_IS_CHANNEL_MESSAGE(data[0]))
403 continue;
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;
420 fLastFrame = 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,
439 /* audioIns */ 0,
440 /* audioOuts */ 0,
441 /* midiIns */ 0,
442 /* midiOuts */ 1,
443 /* paramIns */ 0,
444 /* paramOuts */ 0,
445 /* name */ "MIDI File",
446 /* label */ "midifile",
447 /* maker */ "falkTX",
448 /* copyright */ "GNU GPL v2+",
449 PluginDescriptorFILL(MidiFilePlugin)
452 // -----------------------------------------------------------------------
454 CARLA_API_EXPORT
455 void carla_register_native_plugin_midifile();
457 CARLA_API_EXPORT
458 void carla_register_native_plugin_midifile()
460 carla_register_native_plugin(&midifileDesc);
463 // -----------------------------------------------------------------------