LMMS
Loading...
Searching...
No Matches
MidiFile.cpp
Go to the documentation of this file.
1/*
2 ==============================================================================
3
4 This file is part of the Water library.
5 Copyright (c) 2016 ROLI Ltd.
6 Copyright (C) 2017-2022 Filipe Coelho <falktx@falktx.com>
7
8 Permission is granted to use this software under the terms of the ISC license
9 http://www.isc.org/downloads/software-support-policy/isc-license/
10
11 Permission to use, copy, modify, and/or distribute this software for any
12 purpose with or without fee is hereby granted, provided that the above
13 copyright notice and this permission notice appear in all copies.
14
15 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
16 TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
17 FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
18 OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
19 USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
20 TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
21 OF THIS SOFTWARE.
22
23 ==============================================================================
24*/
25
26#include "MidiFile.h"
27#include "../memory/ByteOrder.h"
30
31namespace water {
32
34{
35 static bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept
36 {
37 unsigned int ch = ByteOrder::bigEndianInt (data);
38 data += 4;
39
40 if (ch != ByteOrder::bigEndianInt ("MThd"))
41 {
42 bool ok = false;
43
44 if (ch == ByteOrder::bigEndianInt ("RIFF"))
45 {
46 for (int i = 0; i < 8; ++i)
47 {
49 data += 4;
50
51 if (ch == ByteOrder::bigEndianInt ("MThd"))
52 {
53 ok = true;
54 break;
55 }
56 }
57 }
58
59 if (! ok)
60 return false;
61 }
62
63 unsigned int bytesRemaining = ByteOrder::bigEndianInt (data);
64 data += 4;
65 fileType = (short) ByteOrder::bigEndianShort (data);
66 data += 2;
67 numberOfTracks = (short) ByteOrder::bigEndianShort (data);
68 data += 2;
69 timeFormat = (short) ByteOrder::bigEndianShort (data);
70 data += 2;
71 bytesRemaining -= 6;
72 data += bytesRemaining;
73
74 return true;
75 }
76
77 static double convertTicksToSeconds (const double time,
78 const MidiMessageSequence& tempoEvents,
79 const int timeFormat)
80 {
81 if (timeFormat < 0)
82 return time / (-(timeFormat >> 8) * (timeFormat & 0xff));
83
84 double lastTime = 0.0, correctedTime = 0.0;
85 const double tickLen = 1.0 / (timeFormat & 0x7fff);
86 double secsPerTick = 0.5 * tickLen;
87 const int numEvents = tempoEvents.getNumEvents();
88
89 for (int i = 0; i < numEvents; ++i)
90 {
91 const MidiMessage& m = tempoEvents.getEventPointer(i)->message;
92 const double eventTime = m.getTimeStamp();
93
94 if (eventTime >= time)
95 break;
96
97 correctedTime += (eventTime - lastTime) * secsPerTick;
98 lastTime = eventTime;
99
100 if (m.isTempoMetaEvent())
101 secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote();
102
103 while (i + 1 < numEvents)
104 {
105 const MidiMessage& m2 = tempoEvents.getEventPointer(i + 1)->message;
106
107 if (m2.getTimeStamp() != eventTime)
108 break;
109
110 if (m2.isTempoMetaEvent())
111 secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote();
112
113 ++i;
114 }
115 }
116
117 return correctedTime + (time - lastTime) * secsPerTick;
118 }
119
120 // a comparator that puts all the note-offs before note-ons that have the same time
121 struct Sorter
122 {
124 const MidiMessageSequence::MidiEventHolder* const second) noexcept
125 {
126 const double diff = (first->message.getTimeStamp() - second->message.getTimeStamp());
127
128 if (diff > 0) return 1;
129 if (diff < 0) return -1;
130 if (first->message.isNoteOff() && second->message.isNoteOn()) return -1;
131 if (first->message.isNoteOn() && second->message.isNoteOff()) return 1;
132
133 return 0;
134 }
135 };
136
137 template <typename MethodType>
139 MidiMessageSequence& results,
140 MethodType method)
141 {
142 for (size_t i = 0; i < tracks.size(); ++i)
143 {
144 const MidiMessageSequence& track = *tracks.getUnchecked(i);
145 const int numEvents = track.getNumEvents();
146
147 for (int j = 0; j < numEvents; ++j)
148 {
149 const MidiMessage& m = track.getEventPointer(j)->message;
150
151 if ((m.*method)())
152 results.addEvent (m);
153 }
154 }
155 }
156}
157
158//==============================================================================
160 : timeFormat ((short) (unsigned short) 0xe728)
161{
162}
163
167
169 : timeFormat (other.timeFormat)
170{
171 tracks.addCopiesOf (other.tracks);
172}
173
175{
176 timeFormat = other.timeFormat;
177 tracks.clear();
178 tracks.addCopiesOf (other.tracks);
179
180 return *this;
181}
182
184{
185 tracks.clear();
186}
187
188//==============================================================================
190{
191 return tracks.size();
192}
193
194const MidiMessageSequence* MidiFile::getTrack (const size_t index) const noexcept
195{
196 return tracks [index];
197}
198
199void MidiFile::addTrack (const MidiMessageSequence& trackSequence)
200{
201 tracks.add (new MidiMessageSequence (trackSequence));
202}
203
204//==============================================================================
206{
207 return timeFormat;
208}
209
210void MidiFile::setTicksPerQuarterNote (const int ticks) noexcept
211{
212 timeFormat = (short) ticks;
213}
214
215void MidiFile::setSmpteTimeFormat (const int framesPerSecond,
216 const int subframeResolution) noexcept
217{
218 timeFormat = (short) (((-framesPerSecond) << 8) | subframeResolution);
219}
220
221//==============================================================================
226
231
236
238{
239 double t = 0.0;
240
241 for (size_t i=0; i < tracks.size(); ++i)
242 t = jmax (t, tracks.getUnchecked(i)->getEndTime());
243
244 return t;
245}
246
247//==============================================================================
248bool MidiFile::readFrom (InputStream& sourceStream)
249{
250 clear();
252
253 const int maxSensibleMidiFileSize = 200 * 1024 * 1024;
254
255 // (put a sanity-check on the file size, as midi files are generally small)
256 if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize))
257 {
258 size_t size = data.getSize();
259 const uint8* d = static_cast<const uint8*> (data.getData());
260 short fileType, expectedTracks;
261
262 if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks))
263 {
264 size -= (size_t) (d - static_cast<const uint8*> (data.getData()));
265
266 int track = 0;
267
268 while (size > 0 && track < expectedTracks)
269 {
270 const int chunkType = (int) ByteOrder::bigEndianInt (d);
271 d += 4;
272 const int chunkSize = (int) ByteOrder::bigEndianInt (d);
273 d += 4;
274
275 if (chunkSize <= 0)
276 break;
277
278 if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk"))
279 readNextTrack (d, chunkSize);
280
281 size -= (size_t) chunkSize + 8;
282 d += chunkSize;
283 ++track;
284 }
285
286 return true;
287 }
288 }
289
290 return false;
291}
292
294{
295 double time = 0;
296 uint8 lastStatusByte = 0;
297
299
300 while (size > 0)
301 {
302 int bytesUsed;
303 const int delay = MidiMessage::readVariableLengthVal (data, bytesUsed);
304 data += bytesUsed;
305 size -= bytesUsed;
306 time += delay;
307
308 int messSize = 0;
309 const MidiMessage mm (data, size, messSize, lastStatusByte, time);
310
311 if (messSize <= 0)
312 break;
313
314 size -= messSize;
315 data += messSize;
316
317 result.addEvent (mm);
318
319 const uint8 firstByte = *(mm.getRawData());
320 if ((firstByte & 0xf0) != 0xf0)
321 lastStatusByte = firstByte;
322 }
323
324 // use a sort that puts all the note-offs before note-ons that have the same time
326 result.list.sort (sorter, true);
327
329 tracks.getLast()->updateMatchedPairs();
330}
331
332//==============================================================================
334{
335 MidiMessageSequence tempoEvents;
336 findAllTempoEvents (tempoEvents);
337 findAllTimeSigEvents (tempoEvents);
338
339 if (timeFormat != 0)
340 {
341 for (size_t i = 0; i < tracks.size(); ++i)
342 {
343 const MidiMessageSequence& ms = *tracks.getUnchecked(i);
344
345 for (int j = ms.getNumEvents(); --j >= 0;)
346 {
348 m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), tempoEvents, timeFormat));
349 }
350 }
351 }
352}
353
354}
#define noexcept
Definition DistrhoDefines.h:72
static uint16 bigEndianShort(const void *bytes) noexcept
Definition ByteOrder.h:241
static uint32 bigEndianInt(const void *bytes) noexcept
Definition ByteOrder.h:239
Definition InputStream.h:42
virtual size_t readIntoMemoryBlock(MemoryBlock &destBlock, ssize_t maxNumBytesToRead=-1)
Definition InputStream.cpp:217
Definition MemoryBlock.h:39
bool readFrom(InputStream &sourceStream)
Definition MidiFile.cpp:248
void convertTimestampTicksToSeconds()
Definition MidiFile.cpp:333
double getLastTimestamp() const
Definition MidiFile.cpp:237
void clear()
Definition MidiFile.cpp:183
void findAllTimeSigEvents(MidiMessageSequence &timeSigEvents) const
Definition MidiFile.cpp:227
size_t getNumTracks() const noexcept
Definition MidiFile.cpp:189
MidiFile & operator=(const MidiFile &other)
Definition MidiFile.cpp:174
MidiFile()
Definition MidiFile.cpp:159
void readNextTrack(const uint8 *, int size)
Definition MidiFile.cpp:293
const MidiMessageSequence * getTrack(size_t index) const noexcept
Definition MidiFile.cpp:194
void setTicksPerQuarterNote(int ticksPerQuarterNote) noexcept
Definition MidiFile.cpp:210
void findAllKeySigEvents(MidiMessageSequence &keySigEvents) const
Definition MidiFile.cpp:232
void findAllTempoEvents(MidiMessageSequence &tempoChangeEvents) const
Definition MidiFile.cpp:222
short getTimeFormat() const noexcept
Definition MidiFile.cpp:205
void addTrack(const MidiMessageSequence &trackSequence)
Definition MidiFile.cpp:199
short timeFormat
Definition MidiFile.h:170
void setSmpteTimeFormat(int framesPerSecond, int subframeResolution) noexcept
Definition MidiFile.cpp:215
~MidiFile()
Definition MidiFile.cpp:164
OwnedArray< MidiMessageSequence > tracks
Definition MidiFile.h:169
Definition MidiMessage.h:40
bool isTimeSignatureMetaEvent() const noexcept
Definition MidiMessage.cpp:719
bool isTempoMetaEvent() const noexcept
Definition MidiMessage.cpp:660
bool isKeySignatureMetaEvent() const noexcept
Definition MidiMessage.cpp:761
double getTimeStamp() const noexcept
Definition MidiMessage.h:144
static int readVariableLengthVal(const uint8 *data, int &numBytesUsed) noexcept
Definition MidiMessage.cpp:63
double getTempoSecondsPerQuarterNote() const noexcept
Definition MidiMessage.cpp:669
Definition MidiMessageSequence.h:69
MidiMessage message
Definition MidiMessageSequence.h:76
Definition MidiMessageSequence.h:45
int getNumEvents() const noexcept
Definition MidiMessageSequence.cpp:61
MidiEventHolder * addEvent(const MidiMessage &newMessage, double timeAdjustment=0)
Definition MidiMessageSequence.cpp:91
MidiEventHolder * getEventPointer(int index) const noexcept
Definition MidiMessageSequence.cpp:66
Definition OwnedArray.h:57
ObjectClass * getUnchecked(const int index) const noexcept
Definition OwnedArray.h:136
size_t size() const noexcept
Definition OwnedArray.h:101
unsigned * m
Definition inflate.c:1559
struct huft * t
Definition inflate.c:943
register unsigned j
Definition inflate.c:1576
unsigned d
Definition inflate.c:940
register unsigned i
Definition inflate.c:1575
JSAMPIMAGE data
Definition jpeglib.h:945
Definition MidiFile.cpp:34
static void findAllMatchingEvents(const OwnedArray< MidiMessageSequence > &tracks, MidiMessageSequence &results, MethodType method)
Definition MidiFile.cpp:138
static double convertTicksToSeconds(const double time, const MidiMessageSequence &tempoEvents, const int timeFormat)
Definition MidiFile.cpp:77
static bool parseMidiHeader(const uint8 *&data, short &timeFormat, short &fileType, short &numberOfTracks) noexcept
Definition MidiFile.cpp:35
Definition AudioSampleBuffer.h:33
unsigned char uint8
Definition water.h:90
Type jmax(const Type a, const Type b)
Definition MathsFunctions.h:48
static bool diff(const std::string fn1, const std::string fn2)
Definition playertest.cpp:161
Definition MidiFile.cpp:122
static int compareElements(const MidiMessageSequence::MidiEventHolder *const first, const MidiMessageSequence::MidiEventHolder *const second) noexcept
Definition MidiFile.cpp:123
ulg size
Definition extract.c:2350
int result
Definition process.c:1455
typedef int(UZ_EXP MsgFn)()
static ZCONST char Far * method[NUM_METHODS]
Definition zipinfo.c:1008
mm
Definition zipinfo.c:2291
#define const
Definition zconf.h:137