47 using std::stringstream;
67 m_format(MIDI_FILE_BAD_FORMAT),
70 m_decrementCount(false),
74 m_mainModelSampleRate(mainModelSampleRate),
87 for (MIDITrack::iterator j = i->second.begin();
88 j != i->second.end(); ++j) {
113 if (bytes.length() != 4) {
114 throw MIDIException(tr(
"Wrong length for long data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(4));
117 long longRet = ((long)(((
MIDIByte)bytes[0]) << 24)) |
118 ((
long)(((
MIDIByte)bytes[1]) << 16)) |
119 ((long)(((
MIDIByte)bytes[2]) << 8)) |
128 if (bytes.length() != 2) {
129 throw MIDIException(tr(
"Wrong length for int data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(2));
132 int intRet = ((int)(((
MIDIByte)bytes[0]) << 8)) |
146 throw MIDIException(tr(
"getMIDIByte called but no MIDI file open"));
150 throw MIDIException(tr(
"End of MIDI file encountered while reading"));
154 throw MIDIException(tr(
"Attempt to get more bytes than expected on Track"));
163 throw MIDIException(tr(
"Attempt to read past MIDI file end"));
175 throw MIDIException(tr(
"getMIDIBytes called but no MIDI file open"));
179 throw MIDIException(tr(
"End of MIDI file encountered while reading"));
183 throw MIDIException(tr(
"Attempt to get more bytes than available on Track (%1, only have %2)").arg(numberOfBytes).arg(m_trackByteCount));
189 while (stringRet.length() < numberOfBytes &&
191 stringRet += fileMIDIByte;
197 if (stringRet.length() < numberOfBytes) {
199 throw MIDIException(tr(
"Attempt to read past MIDI file end"));
204 m_trackByteCount -= stringRet.length();
216 throw MIDIException(tr(
"getNumberFromMIDIBytes called but no MIDI file open"));
222 if (firstByte >= 0) {
231 if (midiByte & 0x80) {
235 longRet = (longRet << 7) + (midiByte & 0x7F);
236 }
while (!
m_midiFile->eof() && (midiByte & 0x80));
250 throw MIDIException(tr(
"skipToNextTrack called but no MIDI file open"));
253 string buffer, buffer2;
283 SVDEBUG <<
"MIDIFileReader::open() : fileName = " << m_fileName.c_str() << endl;
288 ios::in | ios::binary);
291 m_error =
"File not found or not readable.";
322 SVDEBUG <<
"Parsing Track " << j << endl;
327 SVDEBUG <<
"Couldn't find Track " << j << endl;
329 m_error =
"File corrupted or in non-standard format?";
342 SVDEBUG <<
"Track " << j <<
" parsing failed" << endl;
344 m_error =
"File corrupted or in non-standard format?";
352 m_numberOfTracks = i;
357 SVDEBUG <<
"MIDIFileReader::open() - caught exception - " << e.
what() << endl;
371 unsigned long acc = 0;
375 acc = (*i)->addTime(acc);
397 if (midiHeader.size() < 14) {
399 SVDEBUG <<
"MIDIFileReader::parseHeader() - file header undersized" << endl;
406 SVDEBUG <<
"MIDIFileReader::parseHeader()" 407 <<
"- file header not found or malformed" 415 SVDEBUG <<
"MIDIFileReader::parseHeader()" 416 <<
" - header length incorrect" 426 if (m_timingDivision >= 32768) {
428 m_fps = 256 - (m_timingDivision >> 8);
443 MIDIByte midiByte, metaEventCode, data1, data2;
448 long accumulatedTime = 0;
459 vector<int> channelTrackMap(16, -1);
465 map<int, unsigned long> trackTimeMap;
469 unsigned int metaTrack = lastTrackNum;
472 int runningStatus = -1;
474 bool firstTrack =
true;
478 if (eventCode < 0x80) {
480 SVDEBUG <<
"WARNING: Invalid event code " << eventCode
481 <<
" in MIDI file" << endl;
483 throw MIDIException(tr(
"Invalid event code %1 found").arg(
int(eventCode)));
489 SVDEBUG <<
"read delta time " << deltaTime << endl;
497 if (runningStatus < 0) {
498 throw MIDIException(tr(
"Running status used for first event in track"));
501 eventCode = (
MIDIByte)runningStatus;
505 SVDEBUG <<
"using running status (byte " << int(midiByte) <<
" found)" << endl;
509 SVDEBUG <<
"have new event code " << int(midiByte) << endl;
511 eventCode = midiByte;
517 metaEventCode = data1;
521 SVDEBUG <<
"Meta event of type " << int(metaEventCode) <<
" and " << messageLength <<
" bytes found, putting on track " << metaTrack << endl;
525 long gap = accumulatedTime - trackTimeMap[metaTrack];
526 accumulatedTime += deltaTime;
528 trackTimeMap[metaTrack] = accumulatedTime;
543 runningStatus = eventCode;
548 if (channelTrackMap[channel] == -1) {
549 if (!firstTrack) ++lastTrackNum;
550 else firstTrack =
false;
551 channelTrackMap[channel] = lastTrackNum;
554 unsigned int trackNum = channelTrackMap[channel];
559 long gap = accumulatedTime - trackTimeMap[trackNum];
560 accumulatedTime += deltaTime;
562 trackTimeMap[trackNum] = accumulatedTime;
573 midiEvent =
new MIDIEvent(deltaTime, eventCode, data1, data2);
594 midiEvent =
new MIDIEvent(deltaTime, eventCode, data1, data2);
601 midiEvent =
new MIDIEvent(deltaTime, eventCode, data1);
609 SVDEBUG <<
"SysEx of " << messageLength <<
" bytes found" << endl;
614 if (
MIDIByte(metaMessage[metaMessage.length() - 1]) !=
618 SVDEBUG <<
"MIDIFileReader::parseTrack() - " 619 <<
"malformed or unsupported SysEx type" 628 metaMessage = metaMessage.substr(0, metaMessage.length()-1);
638 SVDEBUG <<
"MIDIFileReader::parseTrack()" 639 <<
" - Unsupported MIDI Event Code: " 640 << (int)eventCode << endl;
647 if (lastTrackNum > metaTrack) {
648 for (
unsigned int track = metaTrack + 1; track <= lastTrackNum; ++track) {
650 .arg(
m_trackNames[metaTrack]).arg(track - metaTrack + 1);
664 bool notesOnTrack =
false;
670 if ((*i)->getMessageType() ==
MIDI_NOTE_ON && (*i)->getVelocity() > 0) {
673 noteOffFound =
false;
675 for (MIDITrack::iterator j = i;
678 if (((*j)->getChannelNumber() == (*i)->getChannelNumber()) &&
679 ((*j)->getPitch() == (*i)->getPitch()) &&
682 (*j)->getVelocity() == 0x00))) {
684 (*i)->setDuration((*j)->getTime() - (*i)->getTime());
700 (*i)->setDuration((*j)->getTime() - (*i)->getTime());
718 if ((*i)->isMeta() &&
721 MIDIByte m0 = (*i)->getMetaMessage()[0];
722 MIDIByte m1 = (*i)->getMetaMessage()[1];
723 MIDIByte m2 = (*i)->getMetaMessage()[2];
725 long tempo = (((m0 << 8) + m1) << 8) + m2;
727 SVDEBUG <<
"updateTempoMap: have tempo, it's " << tempo <<
" at " << (*i)->getTime() << endl;
730 double qpm = 60000000.0 / double(tempo);
741 unsigned long lastMIDITime = 0;
743 double tempo = 120.0;
745 if (td == 0) td = 96;
749 unsigned long mtime = i->first;
750 unsigned long melapsed = mtime - lastMIDITime;
751 double quarters = double(melapsed) / double(td);
752 double seconds = (60.0 * quarters) / tempo;
759 lastMIDITime = mtime;
760 tempo = i->second.second;
767 unsigned long tempoMIDITime = 0;
769 double tempo = 120.0;
771 TempoMap::const_iterator i =
m_tempoMap.lower_bound(midiTime);
774 tempoMIDITime = i->first;
775 tempoRealTime = i->second.first;
776 tempo = i->second.second;
780 if (td == 0) td = 96;
782 unsigned long melapsed = midiTime - tempoMIDITime;
783 double quarters = double(melapsed) / double(td);
784 double seconds = (60.0 * quarters) / tempo;
804 if (!
isOK())
return nullptr;
809 (tr(
"MIDI file \"%1\" has no notes in any track").arg(
m_path));
814 std::set<unsigned int> tracksToLoad;
822 QStringList displayNames;
827 unsigned int trackNo = *i;
832 perc = tr(
" - uses GM percussion channel");
836 label = tr(
"Track %1 (%2)%3")
840 label = tr(
"Track %1 (untitled)%3").arg(trackNo).arg(perc);
843 displayNames << label;
848 bool haveSomePercussion =
873 tracksToLoad.insert(*i);
884 if (singleTrack == displayNames[j]) {
885 tracksToLoad.insert(*i);
894 if (tracksToLoad.empty())
return nullptr;
896 int n = int(tracksToLoad.size()), count = 0;
897 Model *model =
nullptr;
899 for (std::set<unsigned int>::iterator i = tracksToLoad.begin();
900 i != tracksToLoad.end(); ++i) {
902 int minProgress = (100 * count) / n;
903 int progressAmount = 100 / n;
905 model =
loadTrack(*i, model, minProgress, progressAmount);
910 if (dynamic_cast<NoteModel *>(model)) {
911 dynamic_cast<NoteModel *
>(model)->setCompletion(100);
919 Model *existingModel,
921 int progressAmount)
const 930 model =
dynamic_cast<NoteModel *
>(existingModel);
932 SVDEBUG <<
"WARNING: MIDIFileReader::loadTrack: Existing model given, but it isn't a NoteModel -- ignoring it" << endl;
939 model->setObjectName(QFileInfo(
m_path).fileName());
944 int totalEvents = int(track.size());
947 bool sharpKey =
true;
949 for (MIDITrack::const_iterator i = track.begin(); i != track.end(); ++i) {
952 unsigned long midiTime = (*i)->getTime();
963 if ((*i)->isMeta()) {
965 switch((*i)->getMetaEventCode()) {
969 sharpKey = (int((*i)->getMetaMessage()[0]) >= 0);
1001 switch ((*i)->getMessageType()) {
1005 if ((*i)->getVelocity() == 0)
break;
1008 unsigned long endMidiTime = (*i)->getTime() + (*i)->getDuration();
1025 QString noteLabel = tr(
"%1 - vel %2")
1026 .arg(pitchLabel).arg(
int((*i)->getVelocity()));
1028 float level = float((*i)->getVelocity()) / 128.f;
1030 Event note(startFrame, (*i)->getPitch(),
1031 endFrame - startFrame, level, noteLabel);
1057 (count * progressAmount) / totalEvents);
double sv_samplerate_t
Sample rate.
std::set< unsigned int > m_percussionTracks
bool isOK() const override
Return true if the file appears to be of the correct type.
static const MIDIByte MIDI_KEY_SIGNATURE
static const MIDIByte MIDI_CHANNEL_PREFIX_OR_PORT
sv_samplerate_t getSampleRate() const override
Return the frame rate in frames per second.
static const MIDIByte MIDI_COPYRIGHT_NOTICE
std::pair< RealTime, double > TempoChange
static RealTime frame2RealTime(sv_frame_t frame, sv_samplerate_t sampleRate)
Convert a sample frame at the given sample rate into a RealTime.
static const MIDIByte MIDI_CHANNEL_PREFIX
MIDIComposition m_midiComposition
static const MIDIByte MIDI_CHANNEL_NUM_MASK
unsigned int m_numberOfTracks
static const char *const MIDI_TRACK_HEADER
static RealTime fromSeconds(double sec)
bool parseHeader(const std::string &midiHeader)
static const MIDIByte MIDI_TRACK_NAME
MIDIFileFormatType m_format
long midiBytesToLong(const std::string &bytes)
static const MIDIByte MIDI_TEXT_MARKER
static const MIDIByte MIDI_SEQUENCE_NUMBER
MIDIByte getChannelNumber() const
static const MIDIByte MIDI_TEXT_EVENT
long getNumberFromMIDIBytes(int firstByte=-1)
static const MIDIByte MIDI_SMPTE_OFFSET
static const MIDIByte MIDI_PERCUSSION_CHANNEL
QString getError() const override
std::ifstream * m_midiFile
MIDIFileImportPreferenceAcquirer * m_acquirer
static const MIDIByte MIDI_LYRIC
int midiBytesToInt(const std::string &bytes)
static const MIDIByte MIDI_CUE_POINT
virtual TrackPreference getTrackImportPreference(QStringList trackNames, bool haveSomePercussion, QString &singleTrack) const =0
static const MIDIByte MIDI_FILE_META_EVENT
static const MIDIByte MIDI_SET_TEMPO
void setValueQuantization(float q)
Model * load() const override
Read the file and return the corresponding data model.
static const MIDIByte MIDI_INSTRUMENT_NAME
static const MIDIByte MIDI_SEQUENCER_SPECIFIC
static const MIDIByte MIDI_STATUS_BYTE_MASK
bool parseTrack(unsigned int &trackNum)
Model is the base class for all data models that represent any sort of data on a time scale based on ...
bool consolidateNoteOffEvents(unsigned int track)
sv_samplerate_t m_mainModelSampleRate
static const MIDIByte MIDI_PITCH_BEND
void calculateTempoTimestamps()
static sv_frame_t realTime2Frame(const RealTime &r, sv_samplerate_t sampleRate)
Convert a RealTime into a sample frame at the given sample rate.
void setCompletion(int completion, bool update=true)
std::map< int, QString > m_trackNames
void updateTempoMap(unsigned int track)
MIDIFileReader(QString path, MIDIFileImportPreferenceAcquirer *pref, sv_samplerate_t mainModelSampleRate, ProgressReporter *reporter=0)
RealTime getTimeForMIDITime(unsigned long midiTime) const
virtual void showError(QString error)=0
virtual ~MIDIFileReader()
An immutable(-ish) type used for point and event representation in sparse models, as well as for inte...
static const MIDIByte MIDI_CTRL_CHANGE
static const MIDIByte MIDI_POLY_AFTERTOUCH
static const MIDIByte MIDI_SYSTEM_EXCLUSIVE
std::string getMIDIBytes(unsigned long bytes)
static const MIDIByte MIDI_NOTE_OFF
static const char *const MIDI_FILE_HEADER
static const MIDIByte MIDI_PROG_CHANGE
Model * loadTrack(unsigned int trackNum, Model *existingModel=0, int minProgress=0, int progressAmount=100) const
static const MIDIByte MIDI_TIME_SIGNATURE
static const RealTime zeroTime
std::set< unsigned int > m_loadableTracks
void add(Event e) override
Editing methods.
static const MIDIByte MIDI_MESSAGE_TYPE_MASK
std::vector< MIDIEvent * > MIDITrack
static const MIDIByte MIDI_NOTE_ON
static QString getPitchLabel(int midiPitch, double centsOffset=0, bool useFlats=false)
Return a string describing the given MIDI pitch, with optional cents offset.
static const MIDIByte MIDI_END_OF_EXCLUSIVE
static const MIDIByte MIDI_CHNL_AFTERTOUCH
const char * what() const override
RealTime represents time values to nanosecond precision with accurate arithmetic and frame-rate conve...