JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_MidiFile.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21*/
22
23namespace juce
24{
25
26namespace MidiFileHelpers
27{
28 static void writeVariableLengthInt (OutputStream& out, uint32 v)
29 {
30 auto buffer = v & 0x7f;
31
32 while ((v >>= 7) != 0)
33 {
34 buffer <<= 8;
35 buffer |= ((v & 0x7f) | 0x80);
36 }
37
38 for (;;)
39 {
40 out.writeByte ((char) buffer);
41
42 if (buffer & 0x80)
43 buffer >>= 8;
44 else
45 break;
46 }
47 }
48
49 template <typename Integral>
50 struct ReadTrait;
51
52 template <>
53 struct ReadTrait<uint32> { static constexpr auto read = ByteOrder::bigEndianInt; };
54
55 template <>
56 struct ReadTrait<uint16> { static constexpr auto read = ByteOrder::bigEndianShort; };
57
58 template <typename Integral>
59 Optional<Integral> tryRead (const uint8*& data, size_t& remaining)
60 {
62 constexpr auto size = sizeof (Integral);
63
64 if (remaining < size)
65 return {};
66
67 const Optional<Integral> result { Trait::read (data) };
68
69 data += size;
70 remaining -= size;
71
72 return result;
73 }
74
76 {
77 size_t bytesRead = 0;
78 short timeFormat = 0;
79 short fileType = 0;
80 short numberOfTracks = 0;
81 };
82
83 static Optional<HeaderDetails> parseMidiHeader (const uint8* const initialData,
84 const size_t maxSize)
85 {
86 auto* data = initialData;
87 auto remaining = maxSize;
88
89 auto ch = tryRead<uint32> (data, remaining);
90
91 if (! ch.hasValue())
92 return {};
93
94 if (*ch != ByteOrder::bigEndianInt ("MThd"))
95 {
96 auto ok = false;
97
98 if (*ch == ByteOrder::bigEndianInt ("RIFF"))
99 {
100 for (int i = 0; i < 8; ++i)
101 {
102 ch = tryRead<uint32> (data, remaining);
103
104 if (! ch.hasValue())
105 return {};
106
107 if (*ch == ByteOrder::bigEndianInt ("MThd"))
108 {
109 ok = true;
110 break;
111 }
112 }
113 }
114
115 if (! ok)
116 return {};
117 }
118
119 const auto bytesRemaining = tryRead<uint32> (data, remaining);
120
121 if (! bytesRemaining.hasValue() || *bytesRemaining > remaining)
122 return {};
123
124 const auto optFileType = tryRead<uint16> (data, remaining);
125
126 if (! optFileType.hasValue() || 2 < *optFileType)
127 return {};
128
129 const auto optNumTracks = tryRead<uint16> (data, remaining);
130
131 if (! optNumTracks.hasValue() || (*optFileType == 0 && *optNumTracks != 1))
132 return {};
133
134 const auto optTimeFormat = tryRead<uint16> (data, remaining);
135
136 if (! optTimeFormat.hasValue())
137 return {};
138
139 HeaderDetails result;
140
141 result.fileType = (short) *optFileType;
142 result.timeFormat = (short) *optTimeFormat;
143 result.numberOfTracks = (short) *optNumTracks;
144 result.bytesRead = maxSize - remaining;
145
146 return { result };
147 }
148
149 static double convertTicksToSeconds (double time,
150 const MidiMessageSequence& tempoEvents,
151 int timeFormat)
152 {
153 if (timeFormat < 0)
154 return time / (-(timeFormat >> 8) * (timeFormat & 0xff));
155
156 double lastTime = 0, correctedTime = 0;
157 auto tickLen = 1.0 / (timeFormat & 0x7fff);
158 auto secsPerTick = 0.5 * tickLen;
159 auto numEvents = tempoEvents.getNumEvents();
160
161 for (int i = 0; i < numEvents; ++i)
162 {
163 auto& m = tempoEvents.getEventPointer (i)->message;
164 auto eventTime = m.getTimeStamp();
165
166 if (eventTime >= time)
167 break;
168
169 correctedTime += (eventTime - lastTime) * secsPerTick;
170 lastTime = eventTime;
171
172 if (m.isTempoMetaEvent())
173 secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote();
174
175 while (i + 1 < numEvents)
176 {
177 auto& m2 = tempoEvents.getEventPointer (i + 1)->message;
178
179 if (! approximatelyEqual (m2.getTimeStamp(), eventTime))
180 break;
181
182 if (m2.isTempoMetaEvent())
183 secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote();
184
185 ++i;
186 }
187 }
188
189 return correctedTime + (time - lastTime) * secsPerTick;
190 }
191
192 template <typename MethodType>
193 static void findAllMatchingEvents (const OwnedArray<MidiMessageSequence>& tracks,
194 MidiMessageSequence& results,
195 MethodType method)
196 {
197 for (auto* track : tracks)
198 {
199 auto numEvents = track->getNumEvents();
200
201 for (int j = 0; j < numEvents; ++j)
202 {
203 auto& m = track->getEventPointer (j)->message;
204
205 if ((m.*method)())
206 results.addEvent (m);
207 }
208 }
209 }
210
211 static MidiMessageSequence readTrack (const uint8* data, int size)
212 {
213 double time = 0;
215
216 MidiMessageSequence result;
217
218 while (size > 0)
219 {
220 const auto delay = MidiMessage::readVariableLengthValue (data, (int) size);
221
222 if (! delay.isValid())
223 break;
224
225 data += delay.bytesUsed;
226 size -= delay.bytesUsed;
227 time += delay.value;
228
229 if (size <= 0)
230 break;
231
232 int messSize = 0;
233 const MidiMessage mm (data, size, messSize, lastStatusByte, time);
234
235 if (messSize <= 0)
236 break;
237
238 size -= messSize;
239 data += messSize;
240
241 result.addEvent (mm);
242
243 auto firstByte = *(mm.getRawData());
244
245 if ((firstByte & 0xf0) != 0xf0)
247 }
248
249 return result;
250 }
251}
252
253//==============================================================================
254MidiFile::MidiFile() : timeFormat ((short) (unsigned short) 0xe728) {}
255
256MidiFile::MidiFile (const MidiFile& other) : timeFormat (other.timeFormat)
257{
258 tracks.addCopiesOf (other.tracks);
259}
260
262{
263 tracks.clear();
264 tracks.addCopiesOf (other.tracks);
265 timeFormat = other.timeFormat;
266 return *this;
267}
268
270 : tracks (std::move (other.tracks)),
271 timeFormat (other.timeFormat)
272{
273}
274
276{
277 tracks = std::move (other.tracks);
278 timeFormat = other.timeFormat;
279 return *this;
280}
281
283{
284 tracks.clear();
285}
286
287//==============================================================================
289{
290 return tracks.size();
291}
292
293const MidiMessageSequence* MidiFile::getTrack (int index) const noexcept
294{
295 return tracks[index];
296}
297
302
303//==============================================================================
305{
306 return timeFormat;
307}
308
310{
311 timeFormat = (short) ticks;
312}
313
314void MidiFile::setSmpteTimeFormat (int framesPerSecond, int subframeResolution) noexcept
315{
316 timeFormat = (short) (((-framesPerSecond) << 8) | subframeResolution);
317}
318
319//==============================================================================
321{
322 MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTempoMetaEvent);
323}
324
326{
327 MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTimeSignatureMetaEvent);
328}
329
331{
332 MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isKeySignatureMetaEvent);
333}
334
336{
337 double t = 0.0;
338
339 for (auto* ms : tracks)
340 t = jmax (t, ms->getEndTime());
341
342 return t;
343}
344
345//==============================================================================
346bool MidiFile::readFrom (InputStream& sourceStream,
348 int* fileType)
349{
350 clear();
351 MemoryBlock data;
352
353 const int maxSensibleMidiFileSize = 200 * 1024 * 1024;
354
355 // (put a sanity-check on the file size, as midi files are generally small)
356 if (! sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize))
357 return false;
358
359 auto size = data.getSize();
360 auto d = static_cast<const uint8*> (data.getData());
361
362 const auto optHeader = MidiFileHelpers::parseMidiHeader (d, size);
363
364 if (! optHeader.hasValue())
365 return false;
366
367 const auto header = *optHeader;
368 timeFormat = header.timeFormat;
369
370 d += header.bytesRead;
371 size -= (size_t) header.bytesRead;
372
373 for (int track = 0; track < header.numberOfTracks; ++track)
374 {
375 const auto optChunkType = MidiFileHelpers::tryRead<uint32> (d, size);
376
377 if (! optChunkType.hasValue())
378 return false;
379
380 const auto optChunkSize = MidiFileHelpers::tryRead<uint32> (d, size);
381
382 if (! optChunkSize.hasValue())
383 return false;
384
385 const auto chunkSize = *optChunkSize;
386
387 if (size < chunkSize)
388 return false;
389
390 if (*optChunkType == ByteOrder::bigEndianInt ("MTrk"))
391 readNextTrack (d, (int) chunkSize, createMatchingNoteOffs);
392
393 size -= chunkSize;
394 d += chunkSize;
395 }
396
397 const auto successful = (size == 0);
398
399 if (successful && fileType != nullptr)
400 *fileType = header.fileType;
401
402 return successful;
403}
404
405void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs)
406{
407 auto sequence = MidiFileHelpers::readTrack (data, size);
408
409 // sort so that we put all the note-offs before note-ons that have the same time
410 std::stable_sort (sequence.list.begin(), sequence.list.end(),
413 {
414 auto t1 = a->message.getTimeStamp();
415 auto t2 = b->message.getTimeStamp();
416
417 if (t1 < t2) return true;
418 if (t2 < t1) return false;
419
420 return a->message.isNoteOff() && b->message.isNoteOn();
421 });
422
424 sequence.updateMatchedPairs();
425
426 addTrack (sequence);
427}
428
429//==============================================================================
431{
435
436 if (timeFormat != 0)
437 {
438 for (auto* ms : tracks)
439 {
440 for (int j = ms->getNumEvents(); --j >= 0;)
441 {
442 auto& m = ms->getEventPointer (j)->message;
443 m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), tempoEvents, timeFormat));
444 }
445 }
446 }
447}
448
449//==============================================================================
451{
452 jassert (midiFileType >= 0 && midiFileType <= 2);
453
454 if (! out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd"))) return false;
455 if (! out.writeIntBigEndian (6)) return false;
456 if (! out.writeShortBigEndian ((short) midiFileType)) return false;
457 if (! out.writeShortBigEndian ((short) tracks.size())) return false;
458 if (! out.writeShortBigEndian (timeFormat)) return false;
459
460 for (auto* ms : tracks)
461 if (! writeTrack (out, *ms))
462 return false;
463
464 out.flush();
465 return true;
466}
467
468bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms) const
469{
471
472 int lastTick = 0;
474 bool endOfTrackEventWritten = false;
475
476 for (int i = 0; i < ms.getNumEvents(); ++i)
477 {
478 auto& mm = ms.getEventPointer (i)->message;
479
480 if (mm.isEndOfTrackMetaEvent())
482
483 auto tick = roundToInt (mm.getTimeStamp());
484 auto delta = jmax (0, tick - lastTick);
485 MidiFileHelpers::writeVariableLengthInt (out, (uint32) delta);
486 lastTick = tick;
487
488 auto* data = mm.getRawData();
489 auto dataSize = mm.getRawDataSize();
490 auto statusByte = data[0];
491
493 && (statusByte & 0xf0) != 0xf0
494 && dataSize > 1
495 && i > 0)
496 {
497 ++data;
498 --dataSize;
499 }
500 else if (statusByte == 0xf0) // Write sysex message with length bytes.
501 {
502 out.writeByte ((char) statusByte);
503
504 ++data;
505 --dataSize;
506
507 MidiFileHelpers::writeVariableLengthInt (out, (uint32) dataSize);
508 }
509
510 out.write (data, (size_t) dataSize);
512 }
513
515 {
516 out.writeByte (0); // (tick delta)
517 auto m = MidiMessage::endOfTrack();
518 out.write (m.getRawData(), (size_t) m.getRawDataSize());
519 }
520
521 if (! mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk"))) return false;
522 if (! mainOut.writeIntBigEndian ((int) out.getDataSize())) return false;
523
524 mainOut << out;
525
526 return true;
527}
528
529//==============================================================================
530//==============================================================================
531#if JUCE_UNIT_TESTS
532
533struct MidiFileTest final : public UnitTest
534{
536 : UnitTest ("MidiFile", UnitTestCategories::midi)
537 {}
538
539 void runTest() override
540 {
541 beginTest ("ReadTrack respects running status");
542 {
543 const auto sequence = parseSequence ([] (OutputStream& os)
544 {
545 MidiFileHelpers::writeVariableLengthInt (os, 100);
546 writeBytes (os, { 0x90, 0x40, 0x40 });
547 MidiFileHelpers::writeVariableLengthInt (os, 200);
548 writeBytes (os, { 0x40, 0x40 });
549 MidiFileHelpers::writeVariableLengthInt (os, 300);
550 writeBytes (os, { 0xff, 0x2f, 0x00 });
551 });
552
553 expectEquals (sequence.getNumEvents(), 3);
554 expect (sequence.getEventPointer (0)->message.isNoteOn());
555 expect (sequence.getEventPointer (1)->message.isNoteOn());
556 expect (sequence.getEventPointer (2)->message.isEndOfTrackMetaEvent());
557 }
558
559 beginTest ("ReadTrack returns available messages if input is truncated");
560 {
561 {
562 const auto sequence = parseSequence ([] (OutputStream& os)
563 {
564 // Incomplete delta time
565 writeBytes (os, { 0xff });
566 });
567
568 expectEquals (sequence.getNumEvents(), 0);
569 }
570
571 {
572 const auto sequence = parseSequence ([] (OutputStream& os)
573 {
574 // Complete delta with no following event
575 MidiFileHelpers::writeVariableLengthInt (os, 0xffff);
576 });
577
578 expectEquals (sequence.getNumEvents(), 0);
579 }
580
581 {
582 const auto sequence = parseSequence ([] (OutputStream& os)
583 {
584 // Complete delta with malformed following event
585 MidiFileHelpers::writeVariableLengthInt (os, 0xffff);
586 writeBytes (os, { 0x90, 0x40 });
587 });
588
589 expectEquals (sequence.getNumEvents(), 1);
590 expect (sequence.getEventPointer (0)->message.isNoteOff());
591 expectEquals (sequence.getEventPointer (0)->message.getNoteNumber(), 0x40);
592 expectEquals (sequence.getEventPointer (0)->message.getVelocity(), (uint8) 0x00);
593 }
594 }
595
596 beginTest ("Header parsing works");
597 {
598 {
599 // No data
600 const auto header = parseHeader ([] (OutputStream&) {});
601 expect (! header.hasValue());
602 }
603
604 {
605 // Invalid initial byte
606 const auto header = parseHeader ([] (OutputStream& os)
607 {
608 writeBytes (os, { 0xff });
609 });
610
611 expect (! header.hasValue());
612 }
613
614 {
615 // Type block, but no header data
616 const auto header = parseHeader ([] (OutputStream& os)
617 {
618 writeBytes (os, { 'M', 'T', 'h', 'd' });
619 });
620
621 expect (! header.hasValue());
622 }
623
624 {
625 // We (ll-formed header, but track type is 0 and channels != 1
626 const auto header = parseHeader ([] (OutputStream& os)
627 {
628 writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 0, 0, 16, 0, 1 });
629 });
630
631 expect (! header.hasValue());
632 }
633
634 {
635 // Well-formed header, but track type is 5
636 const auto header = parseHeader ([] (OutputStream& os)
637 {
638 writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 5, 0, 16, 0, 1 });
639 });
640
641 expect (! header.hasValue());
642 }
643
644 {
645 // Well-formed header
646 const auto header = parseHeader ([] (OutputStream& os)
647 {
648 writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 16, 0, 1 });
649 });
650
651 expect (header.hasValue());
652
653 expectEquals (header->fileType, (short) 1);
654 expectEquals (header->numberOfTracks, (short) 16);
655 expectEquals (header->timeFormat, (short) 1);
656 expectEquals ((int) header->bytesRead, 14);
657 }
658 }
659
660 beginTest ("Read from stream");
661 {
662 {
663 // Empty input
664 const auto file = parseFile ([] (OutputStream&) {});
665 expect (! file.hasValue());
666 }
667
668 {
669 // Malformed header
670 const auto file = parseFile ([] (OutputStream& os)
671 {
672 writeBytes (os, { 'M', 'T', 'h', 'd' });
673 });
674
675 expect (! file.hasValue());
676 }
677
678 {
679 // Header, no channels
680 const auto file = parseFile ([] (OutputStream& os)
681 {
682 writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 0, 0, 1 });
683 });
684
685 expect (file.hasValue());
686 expectEquals (file->getNumTracks(), 0);
687 }
688
689 {
690 // Header, one malformed channel
691 const auto file = parseFile ([] (OutputStream& os)
692 {
693 writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 });
694 writeBytes (os, { 'M', 'T', 'r', '?' });
695 });
696
697 expect (! file.hasValue());
698 }
699
700 {
701 // Header, one channel with malformed message
702 const auto file = parseFile ([] (OutputStream& os)
703 {
704 writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 });
705 writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 1, 0xff });
706 });
707
708 expect (file.hasValue());
709 expectEquals (file->getNumTracks(), 1);
710 expectEquals (file->getTrack (0)->getNumEvents(), 0);
711 }
712
713 {
714 // Header, one channel with incorrect length message
715 const auto file = parseFile ([] (OutputStream& os)
716 {
717 writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 });
718 writeBytes (os, { 'M', 'T', 'r', 'k', 0x0f, 0, 0, 0, 0xff });
719 });
720
721 expect (! file.hasValue());
722 }
723
724 {
725 // Header, one channel, all well-formed
726 const auto file = parseFile ([] (OutputStream& os)
727 {
728 writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 });
729 writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 4 });
730
731 MidiFileHelpers::writeVariableLengthInt (os, 0x0f);
732 writeBytes (os, { 0x80, 0x00, 0x00 });
733 });
734
735 expect (file.hasValue());
736 expectEquals (file->getNumTracks(), 1);
737
738 auto& track = *file->getTrack (0);
739 expectEquals (track.getNumEvents(), 1);
740 expect (track.getEventPointer (0)->message.isNoteOff());
741 expectEquals (track.getEventPointer (0)->message.getTimeStamp(), (double) 0x0f);
742 }
743 }
744 }
745
746 template <typename Fn>
747 static MidiMessageSequence parseSequence (Fn&& fn)
748 {
749 MemoryOutputStream os;
750 fn (os);
751
752 return MidiFileHelpers::readTrack (reinterpret_cast<const uint8*> (os.getData()),
753 (int) os.getDataSize());
754 }
755
756 template <typename Fn>
757 static Optional<MidiFileHelpers::HeaderDetails> parseHeader (Fn&& fn)
758 {
759 MemoryOutputStream os;
760 fn (os);
761
762 return MidiFileHelpers::parseMidiHeader (reinterpret_cast<const uint8*> (os.getData()),
763 os.getDataSize());
764 }
765
766 template <typename Fn>
767 static Optional<MidiFile> parseFile (Fn&& fn)
768 {
769 MemoryOutputStream os;
770 fn (os);
771
772 MemoryInputStream is (os.getData(), os.getDataSize(), false);
773 MidiFile mf;
774
775 int fileType = 0;
776
777 if (mf.readFrom (is, true, &fileType))
778 return mf;
779
780 return {};
781 }
782
783 static void writeBytes (OutputStream& os, const std::vector<uint8>& bytes)
784 {
785 for (const auto& byte : bytes)
786 os.writeByte ((char) byte);
787 }
788};
789
791
792#endif
793
794} // namespace juce
static constexpr uint32 bigEndianInt(const void *bytes) noexcept
Turns 4 bytes into a big-endian integer.
static constexpr uint16 bigEndianShort(const void *bytes) noexcept
Turns 2 bytes into a big-endian integer.
The base class for streams that read data.
virtual size_t readIntoMemoryBlock(MemoryBlock &destBlock, ssize_t maxNumBytesToRead=-1)
Reads from the stream and appends the data to a MemoryBlock.
A class to hold a resizable block of raw data.
Writes data to an internal memory buffer, which grows as required.
size_t getDataSize() const noexcept
Returns the number of bytes of data that have been written to the stream.
bool write(const void *, size_t) override
Writes a block of data to the stream.
Reads/writes standard midi format files.
void convertTimestampTicksToSeconds()
Converts the timestamp of all the midi events from midi ticks to seconds.
void addTrack(const MidiMessageSequence &trackSequence)
Adds a midi track to the file.
void setTicksPerQuarterNote(int ticksPerQuarterNote) noexcept
Sets the time format to use when this file is written to a stream.
int getNumTracks() const noexcept
Returns the number of tracks in the file.
short getTimeFormat() const noexcept
Returns the raw time format code that will be written to a stream.
void setSmpteTimeFormat(int framesPerSecond, int subframeResolution) noexcept
Sets the time format to use when this file is written to a stream.
double getLastTimestamp() const
Returns the latest timestamp in any of the tracks.
bool readFrom(InputStream &sourceStream, bool createMatchingNoteOffs=true, int *midiFileType=nullptr)
Reads a midi file format stream.
MidiFile & operator=(const MidiFile &)
Copies from another MidiFile object.
MidiFile()
Creates an empty MidiFile object.
void findAllTimeSigEvents(MidiMessageSequence &timeSigEvents) const
Makes a list of all the time-signature meta-events from all tracks in the midi file.
void findAllKeySigEvents(MidiMessageSequence &keySigEvents) const
Makes a list of all the key-signature meta-events from all tracks in the midi file.
void findAllTempoEvents(MidiMessageSequence &tempoChangeEvents) const
Makes a list of all the tempo-change meta-events from all tracks in the midi file.
void clear()
Removes all midi tracks from the file.
const MidiMessageSequence * getTrack(int index) const noexcept
Returns a pointer to one of the tracks in the file.
bool writeTo(OutputStream &destStream, int midiFileType=1) const
Writes the midi tracks as a standard midi file.
Structure used to hold midi events in the sequence.
A sequence of timestamped midi messages.
bool isKeySignatureMetaEvent() const noexcept
Returns true if this is a 'key-signature' meta-event.
bool isTimeSignatureMetaEvent() const noexcept
Returns true if this is a 'time-signature' meta-event.
bool isTempoMetaEvent() const noexcept
Returns true if this is a 'tempo' meta-event.
static MidiMessage endOfTrack() noexcept
Creates an end-of-track meta-event.
static VariableLengthValue readVariableLengthValue(const uint8 *data, int maxBytesToUse) noexcept
Reads a midi variable-length integer, with protection against buffer overflow.
A simple optional type.
The base class for streams that write data to some kind of destination.
virtual bool writeByte(char byte)
Writes a single byte to the stream.
virtual bool writeIntBigEndian(int value)
Writes a 32-bit integer to the stream in a big-endian byte order.
virtual bool writeShortBigEndian(short value)
Writes a 16-bit integer to the stream in a big-endian byte order.
virtual void flush()=0
If the stream is using a buffer, this will ensure it gets written out to the destination.
T data(T... args)
#define jassert(expression)
Platform-independent assertion macro.
typedef short
JUCE Namespace.
unsigned short uint16
A platform-independent 16-bit unsigned integer type.
constexpr bool approximatelyEqual(Type a, Type b, Tolerance< Type > tolerance=Tolerance< Type >{} .withAbsolute(std::numeric_limits< Type >::min()) .withRelative(std::numeric_limits< Type >::epsilon()))
Returns true if the two floating-point numbers are approximately equal.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
unsigned int uint32
A platform-independent 32-bit unsigned integer type.
unsigned char uint8
A platform-independent 8-bit unsigned integer type.
int roundToInt(const FloatType value) noexcept
Fast floating-point-to-integer conversion.
read
T size(T... args)
T stable_sort(T... args)
typedef size_t
time