/* Rosegarden A sequencer and musical notation editor. This program is Copyright 2000-2008 Guillaume Laurent , Chris Cannam , Richard Bown The moral right of the authors to claim authorship of this work has been asserted. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See the file COPYING included with this distribution for more information. */ #include #include "misc/Debug.h" #include #include #include #include #include #include #include "Midi.h" #include "MidiFile.h" #include "Segment.h" #include "NotationTypes.h" #include "BaseProperties.h" #include "SegmentNotationHelper.h" #include "SegmentPerformanceHelper.h" #include "CompositionTimeSliceAdapter.h" #include "AnalysisTypes.h" #include "Track.h" #include "Instrument.h" #include "Quantizer.h" #include "Studio.h" #include "MidiTypes.h" #include "Profiler.h" //#define MIDI_DEBUG 1 namespace Rosegarden { using std::string; using std::ifstream; using std::stringstream; using std::cerr; using std::endl; using std::ends; using std::ios; MidiFile::MidiFile(Studio *studio): SoundFile(std::string("unnamed.mid")), m_timingDivision(0), m_format(MIDI_FILE_NOT_LOADED), m_numberOfTracks(0), m_containsTimeChanges(false), m_trackByteCount(0), m_decrementCount(false), m_studio(studio) {} MidiFile::MidiFile(const std::string &fn, Studio *studio): SoundFile(fn), m_timingDivision(0), m_format(MIDI_FILE_NOT_LOADED), m_numberOfTracks(0), m_containsTimeChanges(false), m_trackByteCount(0), m_decrementCount(false), m_studio(studio) {} // Make sure we clear away the m_midiComposition // MidiFile::~MidiFile() { clearMidiComposition(); } // A couple of convenience functions. Watch the byte conversions out // of the STL strings. // // long MidiFile::midiBytesToLong(const string& bytes) { if (bytes.length() != 4) { #ifdef MIDI_DEBUG std::cerr << "WARNING: Wrong length for long data (" << bytes.length() << ", should be 4)" << endl; #endif throw (Exception("Wrong length for long data in MIDI stream")); } long longRet = ((long)(((MidiByte)bytes[0]) << 24)) | ((long)(((MidiByte)bytes[1]) << 16)) | ((long)(((MidiByte)bytes[2]) << 8)) | ((long)((MidiByte)(bytes[3]))); std::cerr << "midiBytesToLong(" << int((MidiByte)bytes[0]) << "," << int((MidiByte)bytes[1]) << "," << int((MidiByte)bytes[2]) << "," << int((MidiByte)bytes[3]) << ") -> " << longRet << std::endl; return longRet; } int MidiFile::midiBytesToInt(const string& bytes) { if (bytes.length() != 2) { #ifdef MIDI_DEBUG std::cerr << "WARNING: Wrong length for int data (" << bytes.length() << ", should be 2)" << endl; #endif throw (Exception("Wrong length for int data in MIDI stream")); } int intRet = ((int)(((MidiByte)bytes[0]) << 8)) | ((int)(((MidiByte)bytes[1]))); return (intRet); } // Gets a single byte from the MIDI byte stream. For each track // section we can read only a specified number of bytes held in // m_trackByteCount. // MidiByte MidiFile::getMidiByte(ifstream* midiFile) { static int bytesGot = 0; // purely for progress reporting purposes if (midiFile->eof()) { throw(Exception("End of MIDI file encountered while reading")); } if (m_decrementCount && m_trackByteCount <= 0) { throw(Exception("Attempt to get more bytes than expected on Track")); } char byte; if (midiFile->read(&byte, 1)) { --m_trackByteCount; // update a progress dialog if we have one // ++bytesGot; if (bytesGot % 2000 == 0) { emit setProgress((int)(double(midiFile->tellg()) / double(m_fileSize) * 20.0)); kapp->processEvents(50); } return (MidiByte)byte; } throw(Exception("Attempt to read past MIDI file end")); } // Gets a specified number of bytes from the MIDI byte stream. For // each track section we can read only a specified number of bytes // held in m_trackByteCount. // string MidiFile::getMidiBytes(ifstream* midiFile, unsigned long numberOfBytes) { string stringRet; char fileMidiByte; static int bytesGot = 0; // purely for progress reporting purposes if (midiFile->eof()) { #ifdef MIDI_DEBUG std::cerr << "MIDI file EOF - got " << stringRet.length() << " bytes out of " << numberOfBytes << endl; #endif throw(Exception("End of MIDI file encountered while reading")); } if (m_decrementCount && (numberOfBytes > (unsigned long)m_trackByteCount)) { #ifdef MIDI_DEBUG std::cerr << "Attempt to get more bytes than allowed on Track (" << numberOfBytes << " > " << m_trackByteCount << endl; #endif //!!! Investigate -- I'm seeing this on new-notation-quantization // branch: load glazunov.rg, run Interpret on first segment, export // and attempt to import again throw(Exception("Attempt to get more bytes than expected on Track")); } while (stringRet.length() < numberOfBytes && midiFile->read(&fileMidiByte, 1)) { stringRet += fileMidiByte; } // if we've reached the end of file without fulfilling the // quota then panic as our parsing has performed incorrectly // if (stringRet.length() < numberOfBytes) { stringRet = ""; #ifdef MIDI_DEBUG cerr << "Attempt to read past file end - got " << stringRet.length() << " bytes out of " << numberOfBytes << endl; #endif throw(Exception("Attempt to read past MIDI file end")); } // decrement the byte count if (m_decrementCount) m_trackByteCount -= stringRet.length(); // update a progress dialog if we have one // bytesGot += numberOfBytes; if (bytesGot % 2000 == 0) { emit setProgress((int)(double(midiFile->tellg()) / double(m_fileSize) * 20.0)); kapp->processEvents(50); } return stringRet; } // Get a long number of variable length from the MIDI byte stream. // // long MidiFile::getNumberFromMidiBytes(ifstream* midiFile, int firstByte) { long longRet = 0; MidiByte midiByte; if (firstByte >= 0) { midiByte = (MidiByte)firstByte; } else if (midiFile->eof()) { return longRet; } else { midiByte = getMidiByte(midiFile); } longRet = midiByte; if (midiByte & 0x80 ) { longRet &= 0x7F; do { midiByte = getMidiByte(midiFile); longRet = (longRet << 7) + (midiByte & 0x7F); } while (!midiFile->eof() && (midiByte & 0x80)); } return longRet; } // Seeks to the next track in the midi file and sets the number // of bytes to be read in the counter m_trackByteCount. // bool MidiFile::skipToNextTrack(ifstream *midiFile) { string buffer, buffer2; m_trackByteCount = -1; m_decrementCount = false; while (!midiFile->eof() && (m_decrementCount == false )) { buffer = getMidiBytes(midiFile, 4); #if (__GNUC__ < 3) if (buffer.compare(MIDI_TRACK_HEADER, 0, 4) == 0) #else if (buffer.compare(0, 4, MIDI_TRACK_HEADER) == 0) #endif { m_trackByteCount = midiBytesToLong(getMidiBytes(midiFile, 4)); m_decrementCount = true; } } if ( m_trackByteCount == -1 ) // we haven't found a track return (false); else return (true); } // Read in a MIDI file. The parsing process throws string // exceptions back up here if we run into trouble which we // can then pass back out to whoever called us using a nice // bool. // // bool MidiFile::open() { bool retOK = true; m_error = ""; #ifdef MIDI_DEBUG std::cerr << "MidiFile::open() : fileName = " << m_fileName.c_str() << endl; #endif // Open the file ifstream *midiFile = new ifstream(m_fileName.c_str(), ios::in | ios::binary); try { if (*midiFile) { // Set file size so we can count it off // midiFile->seekg(0, std::ios::end); m_fileSize = midiFile->tellg(); midiFile->seekg(0, std::ios::beg); // Parse the MIDI header first. The first 14 bytes of the file. if (!parseHeader(getMidiBytes(midiFile, 14))) { m_format = MIDI_FILE_NOT_LOADED; m_error = "Not a MIDI file."; return (false); } m_containsTimeChanges = false; TrackId i = 0; for (unsigned int j = 0; j < m_numberOfTracks; ++j) { //#ifdef MIDI_DEBUG std::cerr << "Parsing Track " << j << endl; //#endif if (!skipToNextTrack(midiFile)) { #ifdef MIDI_DEBUG cerr << "Couldn't find Track " << j << endl; #endif m_error = "File corrupted or in non-standard format?"; m_format = MIDI_FILE_NOT_LOADED; return (false); } #ifdef MIDI_DEBUG std::cerr << "Track has " << m_trackByteCount << " bytes" << std::endl; #endif // Run through the events taking them into our internal // representation. if (!parseTrack(midiFile, i)) { //#ifdef MIDI_DEBUG std::cerr << "Track " << j << " parsing failed" << endl; //#endif m_error = "File corrupted or in non-standard format?"; m_format = MIDI_FILE_NOT_LOADED; return (false); } ++i; // j is the source track number, i the destination } m_numberOfTracks = i; } else { m_error = "File not found or not readable."; m_format = MIDI_FILE_NOT_LOADED; return (false); } // Close the file now midiFile->close(); } catch (Exception e) { #ifdef MIDI_DEBUG std::cerr << "MidiFile::open() - caught exception - " << e.getMessage() << endl; #endif m_error = e.getMessage(); retOK = false; } return (retOK); } // Parse and ensure the MIDI Header is legitimate // // bool MidiFile::parseHeader(const string &midiHeader) { if (midiHeader.size() < 14) { #ifdef MIDI_DEBUG std::cerr << "MidiFile::parseHeader() - file header undersized" << endl; #endif return (false); } #if (__GNUC__ < 3) if (midiHeader.compare(MIDI_FILE_HEADER, 0, 4) != 0) #else if (midiHeader.compare(0, 4, MIDI_FILE_HEADER) != 0) #endif { #ifdef MIDI_DEBUG std::cerr << "MidiFile::parseHeader()" << "- file header not found or malformed" << endl; #endif return (false); } if (midiBytesToLong(midiHeader.substr(4, 4)) != 6L) { #ifdef MIDI_DEBUG std::cerr << "MidiFile::parseHeader()" << " - header length incorrect" << endl; #endif return (false); } m_format = (MIDIFileFormatType) midiBytesToInt(midiHeader.substr(8, 2)); m_numberOfTracks = midiBytesToInt(midiHeader.substr(10, 2)); m_timingDivision = midiBytesToInt(midiHeader.substr(12, 2)); if ( m_format == MIDI_SEQUENTIAL_TRACK_FILE ) { #ifdef MIDI_DEBUG std::cerr << "MidiFile::parseHeader()" << "- can't load sequential track file" << endl; #endif return (false); } #ifdef MIDI_DEBUG if ( m_timingDivision < 0 ) { std::cerr << "MidiFile::parseHeader()" << " - file uses SMPTE timing" << endl; } #endif return (true); } // Extract the contents from a MIDI file track and places it into // our local map of MIDI events. // // bool MidiFile::parseTrack(ifstream* midiFile, TrackId &lastTrackNum) { MidiByte midiByte, metaEventCode, data1, data2; MidiByte eventCode = 0x80; std::string metaMessage; unsigned int messageLength; unsigned long deltaTime; unsigned long accumulatedTime = 0; // The trackNum passed in to this method is the default track for // all events provided they're all on the same channel. If we find // events on more than one channel, we increment trackNum and record // the mapping from channel to trackNum in this channelTrackMap. // We then return the new trackNum by reference so the calling // method knows we've got more tracks than expected. // This would be a vector but TrackId is unsigned // and we need -1 to indicate "not yet used" std::vector channelTrackMap(16, -1); // This is used to store the last absolute time found on each track, // allowing us to modify delta-times correctly when separating events // out from one to multiple tracks // std::map trackTimeMap; // Meta-events don't have a channel, so we place them in a fixed // track number instead TrackId metaTrack = lastTrackNum; // Remember the last non-meta status byte (-1 if we haven't seen one) int runningStatus = -1; bool firstTrack = true; std::cerr << "Parse track: last track number is " << lastTrackNum << std::endl; while (!midiFile->eof() && ( m_trackByteCount > 0 ) ) { if (eventCode < 0x80) { #ifdef MIDI_DEBUG cerr << "WARNING: Invalid event code " << eventCode << " in MIDI file" << endl; #endif throw (Exception("Invalid event code found")); } deltaTime = getNumberFromMidiBytes(midiFile); #ifdef MIDI_DEBUG cerr << "read delta time " << deltaTime << endl; #endif // Get a single byte midiByte = getMidiByte(midiFile); if (!(midiByte & MIDI_STATUS_BYTE_MASK)) { if (runningStatus < 0) { throw (Exception("Running status used for first event in track")); } eventCode = (MidiByte)runningStatus; data1 = midiByte; #ifdef MIDI_DEBUG std::cerr << "using running status (byte " << int(midiByte) << " found)" << std::endl; #endif } else { #ifdef MIDI_DEBUG std::cerr << "have new event code " << int(midiByte) << std::endl; #endif eventCode = midiByte; data1 = getMidiByte(midiFile); } if (eventCode == MIDI_FILE_META_EVENT) // meta events { // metaEventCode = getMidiByte(midiFile); metaEventCode = data1; messageLength = getNumberFromMidiBytes(midiFile); #ifdef MIDI_DEBUG std::cerr << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found" << std::endl; #endif metaMessage = getMidiBytes(midiFile, messageLength); if (metaEventCode == MIDI_TIME_SIGNATURE || metaEventCode == MIDI_SET_TEMPO) { m_containsTimeChanges = true; } long gap = accumulatedTime - trackTimeMap[metaTrack]; accumulatedTime += deltaTime; deltaTime += gap; trackTimeMap[metaTrack] = accumulatedTime; MidiEvent *e = new MidiEvent(deltaTime, MIDI_FILE_META_EVENT, metaEventCode, metaMessage); m_midiComposition[metaTrack].push_back(e); } else // the rest { runningStatus = eventCode; MidiEvent *midiEvent; int channel = (eventCode & MIDI_CHANNEL_NUM_MASK); if (channelTrackMap[channel] == -1) { if (!firstTrack) { ++lastTrackNum; } else { firstTrack = false; } std::cerr << "MidiFile: new channel map entry: channel " << channel << " -> track " << lastTrackNum << std::endl; channelTrackMap[channel] = lastTrackNum; m_trackChannelMap[lastTrackNum] = channel; } TrackId trackNum = channelTrackMap[channel]; { static int prevTrackNum = -1, prevChannel = -1; if (prevTrackNum != (int) trackNum || prevChannel != (int) channel) { std::cerr << "MidiFile: track number for channel " << channel << " is " << trackNum << std::endl; prevTrackNum = trackNum; prevChannel = channel; } } // accumulatedTime is abs time of last event on any track; // trackTimeMap[trackNum] is that of last event on this track long gap = accumulatedTime - trackTimeMap[trackNum]; accumulatedTime += deltaTime; deltaTime += gap; trackTimeMap[trackNum] = accumulatedTime; switch (eventCode & MIDI_MESSAGE_TYPE_MASK) { case MIDI_NOTE_ON: case MIDI_NOTE_OFF: case MIDI_POLY_AFTERTOUCH: case MIDI_CTRL_CHANGE: data2 = getMidiByte(midiFile); // create and store our event midiEvent = new MidiEvent(deltaTime, eventCode, data1, data2); /* std::cerr << "MIDI event for channel " << channel << " (track " << trackNum << ")" << std::endl; midiEvent->print(); */ m_midiComposition[trackNum].push_back(midiEvent); break; case MIDI_PITCH_BEND: data2 = getMidiByte(midiFile); // create and store our event midiEvent = new MidiEvent(deltaTime, eventCode, data1, data2); m_midiComposition[trackNum].push_back(midiEvent); break; case MIDI_PROG_CHANGE: case MIDI_CHNL_AFTERTOUCH: // create and store our event std::cerr << "Program change or channel aftertouch: time " << deltaTime << ", code " << (int)eventCode << ", data " << (int) data1 << " going to track " << trackNum << std::endl; midiEvent = new MidiEvent(deltaTime, eventCode, data1); m_midiComposition[trackNum].push_back(midiEvent); break; case MIDI_SYSTEM_EXCLUSIVE: messageLength = getNumberFromMidiBytes(midiFile, data1); #ifdef MIDI_DEBUG std::cerr << "SysEx of " << messageLength << " bytes found" << std::endl; #endif metaMessage = getMidiBytes(midiFile, messageLength); if (MidiByte(metaMessage[metaMessage.length() - 1]) != MIDI_END_OF_EXCLUSIVE) { #ifdef MIDI_DEBUG std::cerr << "MidiFile::parseTrack() - " << "malformed or unsupported SysEx type" << std::endl; #endif continue; } // chop off the EOX // length fixed by Pedro Lopez-Cabanillas (20030523) // metaMessage = metaMessage.substr(0, metaMessage.length() - 1); midiEvent = new MidiEvent(deltaTime, MIDI_SYSTEM_EXCLUSIVE, metaMessage); m_midiComposition[trackNum].push_back(midiEvent); break; case MIDI_END_OF_EXCLUSIVE: #ifdef MIDI_DEBUG std::cerr << "MidiFile::parseTrack() - " << "Found a stray MIDI_END_OF_EXCLUSIVE" << std::endl; #endif break; default: #ifdef MIDI_DEBUG std::cerr << "MidiFile::parseTrack()" << " - Unsupported MIDI Event Code: " << (int)eventCode << endl; #endif break; } } } return (true); } // borrowed from ALSA pcm_timer.c // static unsigned long gcd(unsigned long a, unsigned long b) { unsigned long r; if (a < b) { r = a; a = b; b = r; } while ((r = a % b) != 0) { a = b; b = r; } return b; } // If we wanted to abstract the MidiFile class to make it more useful to // other applications (and formats) we'd make this method and its twin // pure virtual. // bool MidiFile::convertToRosegarden(Composition &composition, ConversionType type) { Profiler profiler("MidiFile::convertToRosegarden"); MidiTrack::iterator midiEvent; Segment *rosegardenSegment; Segment *conductorSegment = 0; Event *rosegardenEvent; string trackName; // Time conversions // timeT rosegardenTime = 0; timeT rosegardenDuration = 0; timeT maxTime = 0; // To create rests // timeT endOfLastNote; // Event specific vars // int numerator = 4; int denominator = 4; timeT segmentTime; // keys int accidentals; bool isMinor; bool isSharp; if (type == CONVERT_REPLACE) composition.clear(); timeT origin = 0; if (type == CONVERT_APPEND && composition.getDuration() > 0) { origin = composition.getBarEndForTime(composition.getDuration()); } TrackId compTrack = 0; for (Composition::iterator ci = composition.begin(); ci != composition.end(); ++ci) { if ((*ci)->getTrack() >= compTrack) compTrack = (*ci)->getTrack() + 1; } Track *track = 0; // precalculate the timing factor // // [cc] -- attempt to avoid floating-point rounding errors timeT crotchetTime = Note(Note::Crotchet).getDuration(); int divisor = m_timingDivision ? m_timingDivision : 96; unsigned long multiplier = crotchetTime; int g = (int)gcd(crotchetTime, divisor); multiplier /= g; divisor /= g; timeT maxRawTime = LONG_MAX; if (multiplier > divisor) maxRawTime = (maxRawTime / multiplier) * divisor; bool haveTimeSignatures = false; InstrumentId compInstrument = MidiInstrumentBase; // Clear down the assigned Instruments we already have // if (type == CONVERT_REPLACE) { m_studio->unassignAllInstruments(); } std::vector addedSegments; #ifdef MIDI_DEBUG std::cerr << "NUMBER OF TRACKS = " << m_numberOfTracks << endl; std::cerr << "MIDI COMP SIZE = " << m_midiComposition.size() << endl; #endif for (TrackId i = 0; i < m_numberOfTracks; i++ ) { segmentTime = 0; trackName = string("Imported MIDI"); // progress - 20% total in file import itself and then 80% // split over these tracks emit setProgress(20 + (int)((80.0 * double(i) / double(m_numberOfTracks)))); kapp->processEvents(50); // Convert the deltaTime to an absolute time since // the start of the segment. The addTime method // returns the sum of the current Midi Event delta // time plus the argument. // for (midiEvent = m_midiComposition[i].begin(); midiEvent != m_midiComposition[i].end(); ++midiEvent) { segmentTime = (*midiEvent)->addTime(segmentTime); } // Consolidate NOTE ON and NOTE OFF events into a NOTE ON with // a duration. // consolidateNoteOffEvents(i); if (m_trackChannelMap.find(i) != m_trackChannelMap.end()) { compInstrument = MidiInstrumentBase + m_trackChannelMap[i]; } else { compInstrument = MidiInstrumentBase; } rosegardenSegment = new Segment; rosegardenSegment->setTrack(compTrack); rosegardenSegment->setStartTime(0); track = new Track(compTrack, // id compInstrument, // instrument compTrack, // position trackName, // name false); // muted std::cerr << "New Rosegarden track: id = " << compTrack << ", instrument = " << compInstrument << ", name = " << trackName << std::endl; // rest creation token needs to be reset here // endOfLastNote = 0; int msb = -1, lsb = -1; // for bank selects Instrument *instrument = 0; for (midiEvent = m_midiComposition[i].begin(); midiEvent != m_midiComposition[i].end(); midiEvent++) { rosegardenEvent = 0; // [cc] -- avoid floating-point where possible timeT rawTime = (*midiEvent)->getTime(); if (rawTime < maxRawTime) { rosegardenTime = origin + timeT((rawTime * multiplier) / divisor); } else { rosegardenTime = origin + timeT((double(rawTime) * multiplier) / double(divisor) + 0.01); } rosegardenDuration = timeT(((*midiEvent)->getDuration() * multiplier) / divisor); #ifdef MIDI_DEBUG std::cerr << "MIDI file import: origin " << origin << ", event time " << rosegardenTime << ", duration " << rosegardenDuration << ", event type " << (int)(*midiEvent)->getMessageType() << ", previous max time " << maxTime << ", potential max time " << (rosegardenTime + rosegardenDuration) << ", ev raw time " << (*midiEvent)->getTime() << ", crotchet " << crotchetTime << ", multiplier " << multiplier << ", divisor " << divisor << std::endl; #endif if (rosegardenTime + rosegardenDuration > maxTime) { maxTime = rosegardenTime + rosegardenDuration; } // timeT fillFromTime = rosegardenTime; if (rosegardenSegment->empty()) { // fillFromTime = composition.getBarStartForTime(rosegardenTime); endOfLastNote = composition.getBarStartForTime(rosegardenTime); } if ((*midiEvent)->isMeta()) { switch ((*midiEvent)->getMetaEventCode()) { case MIDI_TEXT_EVENT: { std::string text = (*midiEvent)->getMetaMessage(); rosegardenEvent = Text(text).getAsEvent(rosegardenTime); } break; case MIDI_LYRIC: { std::string text = (*midiEvent)->getMetaMessage(); // std::cerr << "lyric event: text=\"" // << text << "\", time=" << rosegardenTime << std::endl; rosegardenEvent = Text(text, Text::Lyric). getAsEvent(rosegardenTime); } break; case MIDI_TEXT_MARKER: { std::string text = (*midiEvent)->getMetaMessage(); composition.addMarker(new Marker (rosegardenTime, text, "")); } break; case MIDI_COPYRIGHT_NOTICE: if (type == CONVERT_REPLACE) { composition.setCopyrightNote((*midiEvent)-> getMetaMessage()); } break; case MIDI_TRACK_NAME: track->setLabel((*midiEvent)->getMetaMessage()); break; case MIDI_INSTRUMENT_NAME: rosegardenSegment->setLabel((*midiEvent)->getMetaMessage()); break; case MIDI_END_OF_TRACK: { timeT trackEndTime = rosegardenTime; if (trackEndTime <= 0) { trackEndTime = crotchetTime * 4 * numerator / denominator; } if (endOfLastNote < trackEndTime) { //If there's nothing in the segment yet, then we //shouldn't fill with rests because we don't want //to cause the otherwise empty segment to be created if (rosegardenSegment->size() > 0) { rosegardenSegment->fillWithRests(trackEndTime); } } } break; case MIDI_SET_TEMPO: { MidiByte m0 = (*midiEvent)->getMetaMessage()[0]; MidiByte m1 = (*midiEvent)->getMetaMessage()[1]; MidiByte m2 = (*midiEvent)->getMetaMessage()[2]; long tempo = (((m0 << 8) + m1) << 8) + m2; if (tempo != 0) { double qpm = 60000000.0 / double(tempo); tempoT rgt(Composition::getTempoForQpm(qpm)); std::cout << "MidiFile: converted MIDI tempo " << tempo << " to Rosegarden tempo " << rgt << std::endl; composition.addTempoAtTime(rosegardenTime, rgt); } } break; case MIDI_TIME_SIGNATURE: numerator = (int) (*midiEvent)->getMetaMessage()[0]; denominator = 1 << ((int)(*midiEvent)->getMetaMessage()[1]); // NB. a MIDI time signature also has // metamessage[2] and [3], containing some timing data if (numerator == 0) numerator = 4; if (denominator == 0) denominator = 4; composition.addTimeSignature (rosegardenTime, TimeSignature(numerator, denominator)); haveTimeSignatures = true; break; case MIDI_KEY_SIGNATURE: // get the details accidentals = (int) (*midiEvent)->getMetaMessage()[0]; isMinor = (int) (*midiEvent)->getMetaMessage()[1]; isSharp = accidentals < 0 ? false : true; accidentals = accidentals < 0 ? -accidentals : accidentals; // create the key event // try { rosegardenEvent = Rosegarden::Key (accidentals, isSharp, isMinor). getAsEvent(rosegardenTime); } catch (...) { #ifdef MIDI_DEBUG std::cerr << "MidiFile::convertToRosegarden - " << " badly formed key signature" << std::endl; #endif break; } break; case MIDI_SEQUENCE_NUMBER: case MIDI_CHANNEL_PREFIX_OR_PORT: case MIDI_CUE_POINT: case MIDI_CHANNEL_PREFIX: case MIDI_SEQUENCER_SPECIFIC: case MIDI_SMPTE_OFFSET: default: #ifdef MIDI_DEBUG std::cerr << "MidiFile::convertToRosegarden - " << "unsupported META event code " << (int)((*midiEvent)->getMetaEventCode()) << endl; #endif break; } } else switch ((*midiEvent)->getMessageType()) { case MIDI_NOTE_ON: // A zero velocity here is a virtual "NOTE OFF" // so we ignore this event // if ((*midiEvent)->getVelocity() == 0) break; endOfLastNote = rosegardenTime + rosegardenDuration; //std::cerr << "MidiFile::convertToRosegarden: note at " << rosegardenTime << ", midi time " << (*midiEvent)->getTime() << std::endl; // create and populate event rosegardenEvent = new Event(Note::EventType, rosegardenTime, rosegardenDuration); rosegardenEvent->set (BaseProperties::PITCH, (*midiEvent)->getPitch()); rosegardenEvent->set (BaseProperties::VELOCITY, (*midiEvent)->getVelocity()); break; // We ignore any NOTE OFFs here as we've already // converted NOTE ONs to have duration // case MIDI_NOTE_OFF: continue; break; case MIDI_PROG_CHANGE: // Attempt to turn the prog change we've found into an // Instrument. Send the program number and whether or // not we're on the percussion channel. // // Note that we make no attempt to do the right // thing with program changes during a track -- we // just save them as events. Only the first is // used to select the instrument. If it's at time // zero, it's not saved as an event. // // std::cerr << "Program change found" << std::endl; if (!instrument) { bool percussion = (*midiEvent)->getChannelNumber() == MIDI_PERCUSSION_CHANNEL; int program = (*midiEvent)->getData1(); if (type == CONVERT_REPLACE) { instrument = m_studio->getInstrumentById(compInstrument); if (instrument) { instrument->setPercussion(percussion); instrument->setSendProgramChange(true); instrument->setProgramChange(program); instrument->setSendBankSelect(msb >= 0 || lsb >= 0); if (instrument->sendsBankSelect()) { instrument->setMSB(msb >= 0 ? msb : 0); instrument->setLSB(lsb >= 0 ? lsb : 0); } } } else { // not CONVERT_REPLACE instrument = m_studio->assignMidiProgramToInstrument (program, msb, lsb, percussion); } } // assign it here if (instrument) { track->setInstrument(instrument->getId()); // We used to set the segment name from the instrument // here, but now we do them all at the end only if the // segment has no other name set (e.g. from instrument // meta event) if ((*midiEvent)->getTime() == 0) break; // no insert } // did we have a bank select? if so, insert that too if (msb >= 0) { rosegardenSegment->insert (Controller(MIDI_CONTROLLER_BANK_MSB, msb). getAsEvent(rosegardenTime)); } if (lsb >= 0) { rosegardenSegment->insert (Controller(MIDI_CONTROLLER_BANK_LSB, msb). getAsEvent(rosegardenTime)); } rosegardenEvent = ProgramChange((*midiEvent)->getData1()). getAsEvent(rosegardenTime); break; case MIDI_CTRL_CHANGE: // If it's a bank select, interpret it (or remember // for later insertion) instead of just inserting it // as a Rosegarden event if ((*midiEvent)->getData1() == MIDI_CONTROLLER_BANK_MSB) { msb = (*midiEvent)->getData2(); break; } if ((*midiEvent)->getData1() == MIDI_CONTROLLER_BANK_LSB) { lsb = (*midiEvent)->getData2(); break; } // If it's something we can use as an instrument // parameter, and it's at time zero, and we already // have an instrument, then apply it to the instrument // instead of inserting if (instrument && (*midiEvent)->getTime() == 0) { if ((*midiEvent)->getData1() == MIDI_CONTROLLER_VOLUME) { instrument->setVolume((*midiEvent)->getData2()); break; } if ((*midiEvent)->getData1() == MIDI_CONTROLLER_PAN) { instrument->setPan((*midiEvent)->getData2()); break; } if ((*midiEvent)->getData1() == MIDI_CONTROLLER_ATTACK) { instrument->setControllerValue(MIDI_CONTROLLER_ATTACK, (*midiEvent)->getData2()); break; } if ((*midiEvent)->getData1() == MIDI_CONTROLLER_RELEASE) { instrument->setControllerValue(MIDI_CONTROLLER_RELEASE, (*midiEvent)->getData2()); break; } if ((*midiEvent)->getData1() == MIDI_CONTROLLER_FILTER) { instrument->setControllerValue(MIDI_CONTROLLER_FILTER, (*midiEvent)->getData2()); break; } if ((*midiEvent)->getData1() == MIDI_CONTROLLER_RESONANCE) { instrument->setControllerValue(MIDI_CONTROLLER_RESONANCE, (*midiEvent)->getData2()); break; } if ((*midiEvent)->getData1() == MIDI_CONTROLLER_CHORUS) { instrument->setControllerValue(MIDI_CONTROLLER_CHORUS, (*midiEvent)->getData2()); break; } if ((*midiEvent)->getData1() == MIDI_CONTROLLER_REVERB) { instrument->setControllerValue(MIDI_CONTROLLER_REVERB, (*midiEvent)->getData2()); break; } } rosegardenEvent = Controller((*midiEvent)->getData1(), (*midiEvent)->getData2()). getAsEvent(rosegardenTime); break; case MIDI_PITCH_BEND: rosegardenEvent = PitchBend((*midiEvent)->getData2(), (*midiEvent)->getData1()). getAsEvent(rosegardenTime); break; case MIDI_SYSTEM_EXCLUSIVE: rosegardenEvent = SystemExclusive((*midiEvent)->getMetaMessage()). getAsEvent(rosegardenTime); break; case MIDI_POLY_AFTERTOUCH: rosegardenEvent = KeyPressure((*midiEvent)->getData1(), (*midiEvent)->getData2()). getAsEvent(rosegardenTime); break; case MIDI_CHNL_AFTERTOUCH: rosegardenEvent = ChannelPressure((*midiEvent)->getData1()). getAsEvent(rosegardenTime); break; default: #ifdef MIDI_DEBUG std::cerr << "MidiFile::convertToRosegarden - " << "Unsupported event code = " << (int)(*midiEvent)->getMessageType() << std::endl; #endif break; } if (rosegardenEvent) { // if (fillFromTime < rosegardenTime) { // rosegardenSegment->fillWithRests(fillFromTime, rosegardenTime); // } if (endOfLastNote < rosegardenTime) { rosegardenSegment->fillWithRests(endOfLastNote, rosegardenTime); } rosegardenSegment->insert(rosegardenEvent); } } if (rosegardenSegment->size() > 0) { // if all we have is key signatures and rests, take this // to be a conductor segment and don't insert it // bool keySigsOnly = true; bool haveKeySig = false; for (Segment::iterator i = rosegardenSegment->begin(); i != rosegardenSegment->end(); ++i) { if (!(*i)->isa(Rosegarden::Key::EventType) && !(*i)->isa(Note::EventRestType)) { keySigsOnly = false; break; } else if ((*i)->isa(Rosegarden::Key::EventType)) { haveKeySig = true; } } if (keySigsOnly) { conductorSegment = rosegardenSegment; continue; } else if (!haveKeySig && conductorSegment) { // copy across any key sigs from the conductor segment timeT segmentStartTime = rosegardenSegment->getStartTime(); timeT earliestEventEndTime = segmentStartTime; for (Segment::iterator i = conductorSegment->begin(); i != conductorSegment->end(); ++i) { if ((*i)->getAbsoluteTime() + (*i)->getDuration() < earliestEventEndTime) { earliestEventEndTime = (*i)->getAbsoluteTime() + (*i)->getDuration(); } rosegardenSegment->insert(new Event(**i)); } if (earliestEventEndTime < segmentStartTime) { rosegardenSegment->fillWithRests(earliestEventEndTime, segmentStartTime); } } #ifdef MIDI_DEBUG std::cerr << "MIDI import: adding segment with start time " << rosegardenSegment->getStartTime() << " and end time " << rosegardenSegment->getEndTime() << std::endl; if (rosegardenSegment->getEndTime() == 2880) { std::cerr << "events:" << std::endl; for (Segment::iterator i = rosegardenSegment->begin(); i != rosegardenSegment->end(); ++i) { std::cerr << "type = " << (*i)->getType() << std::endl; std::cerr << "time = " << (*i)->getAbsoluteTime() << std::endl; std::cerr << "duration = " << (*i)->getDuration() << std::endl; } } #endif // add the Segment to the Composition and increment the // Rosegarden segment number // composition.addTrack(track); composition.addSegment(rosegardenSegment); addedSegments.push_back(rosegardenSegment); compTrack++; } else { delete rosegardenSegment; rosegardenSegment = 0; delete track; track = 0; } } if (type == CONVERT_REPLACE || maxTime > composition.getEndMarker()) { composition.setEndMarker(composition.getBarEndForTime(maxTime)); } for (std::vector::iterator i = addedSegments.begin(); i != addedSegments.end(); ++i) { Segment *s = *i; if (s) { timeT duration = s->getEndMarkerTime() - s->getStartTime(); /* std::cerr << "duration = " << duration << " (start " << s->getStartTime() << ", end " << s->getEndTime() << ", marker " << s->getEndMarkerTime() << ")" << std::endl; */ if (duration == 0) { s->setEndMarkerTime(s->getStartTime() + Note(Note::Crotchet).getDuration()); } Instrument *instr = m_studio->getInstrumentFor(s); if (instr) { if (s->getLabel() == "") { s->setLabel(m_studio->getSegmentName(instr->getId())); } } } } return true; } // Takes a Composition and turns it into internal MIDI representation // that can then be written out to file. // // For the moment we should watch to make sure that multiple Segment // (parts) don't equate to multiple segments in the MIDI Composition. // // This is a two pass operation - firstly convert the RG Composition // into MIDI events and insert anything extra we need (i.e. NOTE OFFs) // with absolute times before then processing all timings into delta // times. // // void MidiFile::convertToMidi(Composition &comp) { MidiEvent *midiEvent; int conductorTrack = 0; timeT midiEventAbsoluteTime; MidiByte midiVelocity; MidiByte midiChannel = 0; // [cc] int rather than floating point // m_timingDivision = 480; //!!! make this configurable timeT crotchetDuration = Note(Note::Crotchet).getDuration(); // Export as this format only // m_format = MIDI_SIMULTANEOUS_TRACK_FILE; // Clear out the MidiComposition internal store // clearMidiComposition(); // Insert the Rosegarden Signature Track here and any relevant // file META information - this will get written out just like // any other MIDI track. // midiEvent = new MidiEvent(0, MIDI_FILE_META_EVENT, MIDI_COPYRIGHT_NOTICE, comp.getCopyrightNote()); m_midiComposition[conductorTrack].push_back(midiEvent); midiEvent = new MidiEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT, "Created by Rosegarden"); m_midiComposition[conductorTrack].push_back(midiEvent); midiEvent = new MidiEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT, "http://www.rosegardenmusic.com/"); m_midiComposition[conductorTrack].push_back(midiEvent); // Insert tempo events // for (int i = 0; i < comp.getTempoChangeCount(); i++) // i=0 should be comp.getStart-something { std::pair tempo = comp.getTempoChange(i); midiEventAbsoluteTime = tempo.first * m_timingDivision / crotchetDuration; double qpm = Composition::getTempoQpm(tempo.second); long tempoValue = long(60000000.0 / qpm + 0.01); string tempoString; tempoString += (MidiByte) ( tempoValue >> 16 & 0xFF ); tempoString += (MidiByte) ( tempoValue >> 8 & 0xFF ); tempoString += (MidiByte) ( tempoValue & 0xFF ); midiEvent = new MidiEvent(midiEventAbsoluteTime, MIDI_FILE_META_EVENT, MIDI_SET_TEMPO, tempoString); m_midiComposition[conductorTrack].push_back(midiEvent); } // Insert time signatures (don't worry that the times might be out // of order with those of the tempo events -- we sort the track later) // for (int i = 0; i < comp.getTimeSignatureCount(); i++) { std::pair timeSig = comp.getTimeSignatureChange(i); midiEventAbsoluteTime = timeSig.first * m_timingDivision / crotchetDuration; string timeSigString; timeSigString += (MidiByte) (timeSig.second.getNumerator()); int denominator = timeSig.second.getDenominator(); int denPowerOf2 = 0; // Work out how many powers of two are in the denominator // while (denominator >>= 1) denPowerOf2++; timeSigString += (MidiByte) denPowerOf2; // The third byte is the number of MIDI clocks per beat. // There are 24 clocks per quarter-note (the MIDI clock // is tempo-independent and is not related to the timebase). // int cpb = 24 * timeSig.second.getBeatDuration() / crotchetDuration; timeSigString += (MidiByte) cpb; // And the fourth byte is always 8, for us (it expresses // the number of notated 32nd-notes in a MIDI quarter-note, // for applications that may want to notate and perform // in different units) // timeSigString += (MidiByte) 8; midiEvent = new MidiEvent(midiEventAbsoluteTime, MIDI_FILE_META_EVENT, MIDI_TIME_SIGNATURE, timeSigString); m_midiComposition[conductorTrack].push_back(midiEvent); } // Insert markers // fix for bug# Composition::markercontainer marks = comp.getMarkers(); for (unsigned int i = 0; i < marks.size(); i++) { midiEventAbsoluteTime = marks[i]->getTime() * m_timingDivision / crotchetDuration; midiEvent = new MidiEvent( midiEventAbsoluteTime, MIDI_FILE_META_EVENT, MIDI_TEXT_MARKER, marks[i]->getName() ); m_midiComposition[conductorTrack].push_back(midiEvent); } m_numberOfTracks = 1; std::map trackPosMap; // RG track pos -> MIDI track no // In pass one just insert all events including new NOTE OFFs at the right // absolute times. // for (Composition::const_iterator segment = comp.begin(); segment != comp.end(); ++segment) { // We use this later to get NOTE durations // SegmentPerformanceHelper helper(**segment); Track *track = comp.getTrackById((*segment)->getTrack()); if (track->isMuted()) continue; // Fix #1602023, map Rosegarden tracks to MIDI tracks, instead of // putting each segment out on a new track int trackPosition = track->getPosition(); bool firstSegmentThisTrack = false; if (trackPosMap.find(trackPosition) == trackPosMap.end()) { firstSegmentThisTrack = true; trackPosMap[trackPosition] = m_numberOfTracks++; } int trackNumber = trackPosMap[trackPosition]; MidiTrack &mtrack = m_midiComposition[trackNumber]; midiEvent = new MidiEvent(0, MIDI_FILE_META_EVENT, MIDI_TRACK_NAME, track->getLabel()); mtrack.push_back(midiEvent); // Get the Instrument // Instrument *instr = m_studio->getInstrumentById(track->getInstrument()); if (firstSegmentThisTrack) { MidiByte program = 0; midiChannel = 0; bool useBank = false; MidiByte lsb = 0; MidiByte msb = 0; if (instr) { midiChannel = instr->getMidiChannel(); program = instr->getProgramChange(); if (instr->sendsBankSelect()) { lsb = instr->getLSB(); msb = instr->getMSB(); useBank = true; } } if (useBank) { // insert a bank select if (msb != 0) { midiEvent = new MidiEvent(0, MIDI_CTRL_CHANGE | midiChannel, MIDI_CONTROLLER_BANK_MSB, msb); mtrack.push_back(midiEvent); } if (lsb != 0) { midiEvent = new MidiEvent(0, MIDI_CTRL_CHANGE | midiChannel, MIDI_CONTROLLER_BANK_LSB, lsb); mtrack.push_back(midiEvent); } } // insert a program change midiEvent = new MidiEvent(0, // time MIDI_PROG_CHANGE | midiChannel, program); mtrack.push_back(midiEvent); if (instr) { // MidiInstrument parameters: volume, pan, attack, // release, filter, resonance, chorus, reverb. Always // write these: the Instrument has an additional parameter // to record whether they should be sent, but it isn't // actually set anywhere so we have to ignore it. static int controllers[] = { MIDI_CONTROLLER_ATTACK, MIDI_CONTROLLER_RELEASE, MIDI_CONTROLLER_FILTER, MIDI_CONTROLLER_RESONANCE, MIDI_CONTROLLER_CHORUS, MIDI_CONTROLLER_REVERB }; mtrack.push_back (new MidiEvent(0, MIDI_CTRL_CHANGE | midiChannel, MIDI_CONTROLLER_VOLUME, instr->getVolume())); mtrack.push_back (new MidiEvent(0, MIDI_CTRL_CHANGE | midiChannel, MIDI_CONTROLLER_PAN, instr->getPan())); for (int i = 0; i < sizeof(controllers)/sizeof(controllers[0]); ++i) { try { mtrack.push_back (new MidiEvent (0, MIDI_CTRL_CHANGE | midiChannel, controllers[i], instr->getControllerValue(controllers[i]))); } catch (...) { /* do nothing */ } } } // if (instr) } // if (firstSegmentThisTrack) timeT segmentMidiDuration = ((*segment)->getEndMarkerTime() - (*segment)->getStartTime()) * m_timingDivision / crotchetDuration; for (Segment::iterator el = (*segment)->begin(); (*segment)->isBeforeEndMarker(el); ++el) { midiEventAbsoluteTime = (*el)->getAbsoluteTime() + (*segment)->getDelay(); timeT absoluteTimeLimit = midiEventAbsoluteTime; if ((*segment)->isRepeating()) { absoluteTimeLimit = ((*segment)->getRepeatEndTime() - 1) + (*segment)->getDelay(); } if ((*segment)->getRealTimeDelay() != RealTime::zeroTime) { RealTime evRT = comp.getElapsedRealTime(midiEventAbsoluteTime); timeT timeBeforeDelay = midiEventAbsoluteTime; midiEventAbsoluteTime = comp.getElapsedTimeForRealTime (evRT + (*segment)->getRealTimeDelay()); absoluteTimeLimit += (midiEventAbsoluteTime - timeBeforeDelay); } midiEventAbsoluteTime = midiEventAbsoluteTime * m_timingDivision / crotchetDuration; absoluteTimeLimit = absoluteTimeLimit * m_timingDivision / crotchetDuration; while (midiEventAbsoluteTime <= absoluteTimeLimit) { try { if ((*el)->isa(Note::EventType)) { if ((*el)->has(BaseProperties::VELOCITY)) midiVelocity = (*el)->get (BaseProperties::VELOCITY); else midiVelocity = 127; // Get the sounding time for the matching NOTE_OFF. // We use SegmentPerformanceHelper::getSoundingDuration() // to work out the tied duration of the NOTE. timeT soundingDuration = helper.getSoundingDuration(el); if (soundingDuration > 0) { timeT midiEventEndTime = midiEventAbsoluteTime + soundingDuration * m_timingDivision / crotchetDuration; long pitch = 60; (*el)->get (BaseProperties::PITCH, pitch); pitch += (*segment)->getTranspose(); // insert the NOTE_ON at the appropriate channel // midiEvent = new MidiEvent(midiEventAbsoluteTime, MIDI_NOTE_ON | midiChannel, pitch, midiVelocity); mtrack.push_back(midiEvent); // insert the matching NOTE OFF // midiEvent = new MidiEvent(midiEventEndTime, MIDI_NOTE_OFF | midiChannel, pitch, 127); // full volume silence mtrack.push_back(midiEvent); } } else if ((*el)->isa(PitchBend::EventType)) { PitchBend pb(**el); midiEvent = new MidiEvent(midiEventAbsoluteTime, MIDI_PITCH_BEND | midiChannel, pb.getLSB(), pb.getMSB()); mtrack.push_back(midiEvent); } else if ((*el)->isa(Rosegarden::Key::EventType)) { Rosegarden::Key key(**el); int accidentals = key.getAccidentalCount(); if (!key.isSharp()) accidentals = -accidentals; // stack out onto the meta string // std::string metaMessage; metaMessage += MidiByte(accidentals); metaMessage += MidiByte(key.isMinor()); midiEvent = new MidiEvent(midiEventAbsoluteTime, MIDI_FILE_META_EVENT, MIDI_KEY_SIGNATURE, metaMessage); //mtrack.push_back(midiEvent); } else if ((*el)->isa(Controller::EventType)) { Controller c(**el); midiEvent = new MidiEvent(midiEventAbsoluteTime, MIDI_CTRL_CHANGE | midiChannel, c.getNumber(), c.getValue()); mtrack.push_back(midiEvent); } else if ((*el)->isa(ProgramChange::EventType)) { ProgramChange pc(**el); midiEvent = new MidiEvent(midiEventAbsoluteTime, MIDI_PROG_CHANGE | midiChannel, pc.getProgram()); mtrack.push_back(midiEvent); } else if ((*el)->isa(SystemExclusive::EventType)) { SystemExclusive s(**el); std::string data = s.getRawData(); // check for closing EOX and add one if none found // if (MidiByte(data[data.length() - 1]) != MIDI_END_OF_EXCLUSIVE) { data += MIDI_END_OF_EXCLUSIVE; } // construct plain SYSEX event // midiEvent = new MidiEvent(midiEventAbsoluteTime, MIDI_SYSTEM_EXCLUSIVE, data); mtrack.push_back(midiEvent); } else if ((*el)->isa(ChannelPressure::EventType)) { ChannelPressure cp(**el); midiEvent = new MidiEvent(midiEventAbsoluteTime, MIDI_CHNL_AFTERTOUCH | midiChannel, cp.getPressure()); mtrack.push_back(midiEvent); } else if ((*el)->isa(KeyPressure::EventType)) { KeyPressure kp(**el); midiEvent = new MidiEvent(midiEventAbsoluteTime, MIDI_POLY_AFTERTOUCH | midiChannel, kp.getPitch(), kp.getPressure()); mtrack.push_back(midiEvent); } else if ((*el)->isa(Text::EventType)) { Text text(**el); std::string metaMessage = text.getText(); MidiByte midiTextType = MIDI_TEXT_EVENT; if (text.getTextType() == Text::Lyric) { midiTextType = MIDI_LYRIC; } if (text.getTextType() != Text::Annotation) { // (we don't write annotations) midiEvent = new MidiEvent(midiEventAbsoluteTime, MIDI_FILE_META_EVENT, midiTextType, metaMessage); mtrack.push_back(midiEvent); } } else if ((*el)->isa(Note::EventRestType)) { // skip legitimately } else { /* cerr << "MidiFile::convertToMidi - " << "unsupported MidiType \"" << (*el)->getType() << "\" at export" << std::endl; */ } } catch (MIDIValueOutOfRange r) { #ifdef MIDI_DEBUG std::cerr << "MIDI value out of range at " << (*el)->getAbsoluteTime() << std::endl; #endif } catch (Event::NoData d) { #ifdef MIDI_DEBUG std::cerr << "Caught Event::NoData at " << (*el)->getAbsoluteTime() << ", message is:" << std::endl << d.getMessage() << std::endl; #endif } catch (Event::BadType b) { #ifdef MIDI_DEBUG std::cerr << "Caught Event::BadType at " << (*el)->getAbsoluteTime() << ", message is:" << std::endl << b.getMessage() << std::endl; #endif } catch (SystemExclusive::BadEncoding e) { #ifdef MIDI_DEBUG std::cerr << "Caught bad SysEx encoding at " << (*el)->getAbsoluteTime() << std::endl; #endif } if (segmentMidiDuration > 0) { midiEventAbsoluteTime += segmentMidiDuration; } else break; } } } // Now gnash through the MIDI events and turn the absolute times // into delta times. // // MidiTrack::iterator it; timeT deltaTime, lastMidiTime; for (TrackId i = 0; i < m_numberOfTracks; i++) { lastMidiTime = 0; // First sort the track with the MidiEvent comparator. Use // stable_sort so that events with equal times are maintained // in their current order (important for e.g. bank-program // pairs, or the controllers at the start of the track which // should follow the program so we can treat them correctly // when re-reading). // std::stable_sort(m_midiComposition[i].begin(), m_midiComposition[i].end(), MidiEventCmp()); for (it = m_midiComposition[i].begin(); it != m_midiComposition[i].end(); it++) { deltaTime = (*it)->getTime() - lastMidiTime; lastMidiTime = (*it)->getTime(); (*it)->setTime(deltaTime); } // Insert end of track event (delta time = 0) // midiEvent = new MidiEvent(0, MIDI_FILE_META_EVENT, MIDI_END_OF_TRACK, ""); m_midiComposition[i].push_back(midiEvent); } return ; } // Convert an integer into a two byte representation and // write out to the MidiFile. // void MidiFile::intToMidiBytes(std::ofstream* midiFile, int number) { MidiByte upper; MidiByte lower; upper = (number & 0xFF00) >> 8; lower = (number & 0x00FF); *midiFile << (MidiByte) upper; *midiFile << (MidiByte) lower; } void MidiFile::longToMidiBytes(std::ofstream* midiFile, unsigned long number) { MidiByte upper1; MidiByte lower1; MidiByte upper2; MidiByte lower2; upper1 = (number & 0xff000000) >> 24; lower1 = (number & 0x00ff0000) >> 16; upper2 = (number & 0x0000ff00) >> 8; lower2 = (number & 0x000000ff); *midiFile << (MidiByte) upper1; *midiFile << (MidiByte) lower1; *midiFile << (MidiByte) upper2; *midiFile << (MidiByte) lower2; } // Turn a delta time into a MIDI time - overlapping into // a maximum of four bytes using the MSB as the carry on // flag. // std::string MidiFile::longToVarBuffer(unsigned long number) { std::string rS; long inNumber = number; long outNumber; // get the lowest 7 bits of the number outNumber = number & 0x7f; // Shift and test and move the numbers // on if we need them - setting the MSB // as we go. // while ((inNumber >>= 7 ) > 0) { outNumber <<= 8; outNumber |= 0x80; outNumber += (inNumber & 0x7f); } // Now move the converted number out onto the buffer // while (true) { rS += (MidiByte)(outNumber & 0xff); if (outNumber & 0x80) outNumber >>= 8; else break; } return rS; } // Write out the MIDI file header // bool MidiFile::writeHeader(std::ofstream* midiFile) { // Our identifying Header string // *midiFile << MIDI_FILE_HEADER.c_str(); // Write number of Bytes to follow // *midiFile << (MidiByte) 0x00; *midiFile << (MidiByte) 0x00; *midiFile << (MidiByte) 0x00; *midiFile << (MidiByte) 0x06; // Write File Format // *midiFile << (MidiByte) 0x00; *midiFile << (MidiByte) m_format; // Number of Tracks we're writing out // intToMidiBytes(midiFile, m_numberOfTracks); // Timing Division // intToMidiBytes(midiFile, m_timingDivision); return (true); } // Write a MIDI track to file // bool MidiFile::writeTrack(std::ofstream* midiFile, TrackId trackNumber) { bool retOK = true; MidiByte eventCode = 0; MidiTrack::iterator midiEvent; // First we write into the trackBuffer, then write it out to the // file with it's accompanying length. // string trackBuffer; long progressTotal = m_midiComposition[trackNumber].size(); long progressCount = 0; for (midiEvent = m_midiComposition[trackNumber].begin(); midiEvent != m_midiComposition[trackNumber].end(); midiEvent++) { // Write the time to the buffer in MIDI format // // trackBuffer += longToVarBuffer((*midiEvent)->getTime()); if ((*midiEvent)->isMeta()) { trackBuffer += MIDI_FILE_META_EVENT; trackBuffer += (*midiEvent)->getMetaEventCode(); // Variable length number field trackBuffer += longToVarBuffer((*midiEvent)-> getMetaMessage().length()); trackBuffer += (*midiEvent)->getMetaMessage(); } else { // Send the normal event code (with encoded channel information) // // Fix for 674731 by Pedro Lopez-Cabanillas (20030531) if (((*midiEvent)->getEventCode() != eventCode) || ((*midiEvent)->getEventCode() == MIDI_SYSTEM_EXCLUSIVE)) { trackBuffer += (*midiEvent)->getEventCode(); eventCode = (*midiEvent)->getEventCode(); } // Send the relevant data // switch ((*midiEvent)->getMessageType()) { case MIDI_NOTE_ON: case MIDI_NOTE_OFF: case MIDI_POLY_AFTERTOUCH: trackBuffer += (*midiEvent)->getData1(); trackBuffer += (*midiEvent)->getData2(); break; case MIDI_CTRL_CHANGE: trackBuffer += (*midiEvent)->getData1(); trackBuffer += (*midiEvent)->getData2(); break; case MIDI_PROG_CHANGE: trackBuffer += (*midiEvent)->getData1(); break; case MIDI_CHNL_AFTERTOUCH: trackBuffer += (*midiEvent)->getData1(); break; case MIDI_PITCH_BEND: trackBuffer += (*midiEvent)->getData1(); trackBuffer += (*midiEvent)->getData2(); break; case MIDI_SYSTEM_EXCLUSIVE: // write out message length trackBuffer += longToVarBuffer((*midiEvent)->getMetaMessage().length()); // now the message trackBuffer += (*midiEvent)->getMetaMessage(); break; default: #ifdef MIDI_DEBUG std::cerr << "MidiFile::writeTrack()" << " - cannot write unsupported MIDI event" << endl; #endif break; } } // For the moment just keep the app updating until we work // out a good way of accounting for this write. // ++progressCount; if (progressCount % 500 == 0) { emit setProgress(progressCount * 100 / progressTotal); kapp->processEvents(500); } } // Now we write the track - First the standard header.. // *midiFile << MIDI_TRACK_HEADER.c_str(); // ..now the length of the buffer.. // longToMidiBytes(midiFile, (long)trackBuffer.length()); // ..then the buffer itself.. // *midiFile << trackBuffer; return (retOK); } // Writes out a MIDI file from the internal Midi representation // bool MidiFile::write() { bool retOK = true; std::ofstream *midiFile = new std::ofstream(m_fileName.c_str(), ios::out | ios::binary); if (!(*midiFile)) { #ifdef MIDI_DEBUG std::cerr << "MidiFile::write() - can't write file" << endl; #endif m_format = MIDI_FILE_NOT_LOADED; return false; } // Write out the Header // writeHeader(midiFile); // And now the tracks // for (TrackId i = 0; i < m_numberOfTracks; i++ ) if (!writeTrack(midiFile, i)) retOK = false; midiFile->close(); if (!retOK) m_format = MIDI_FILE_NOT_LOADED; return (retOK); } // Delete dead NOTE OFF and NOTE ON/Zero Velocty Events after // reading them and modifying their relevant NOTE ONs // bool MidiFile::consolidateNoteOffEvents(TrackId track) { MidiTrack::iterator nOE, mE = m_midiComposition[track].begin(); bool notesOnTrack = false; bool noteOffFound; for (;mE != m_midiComposition[track].end(); mE++) { if ((*mE)->getMessageType() == MIDI_NOTE_ON && (*mE)->getVelocity() > 0) { // We've found a note - flag it // if (!notesOnTrack) notesOnTrack = true; noteOffFound = false; for (nOE = mE; nOE != m_midiComposition[track].end(); nOE++) { if (((*nOE)->getChannelNumber() == (*mE)->getChannelNumber()) && ((*nOE)->getPitch() == (*mE)->getPitch()) && ((*nOE)->getMessageType() == MIDI_NOTE_OFF || ((*nOE)->getMessageType() == MIDI_NOTE_ON && (*nOE)->getVelocity() == 0x00))) { (*mE)->setDuration((*nOE)->getTime() - (*mE)->getTime()); delete *nOE; m_midiComposition[track].erase(nOE); noteOffFound = true; break; } } // If no matching NOTE OFF has been found then set // Event duration to length of Segment // if (noteOffFound == false) { --nOE; // avoid crash due to nOE == track.end() (*mE)->setDuration((*nOE)->getTime() - (*mE)->getTime()); } } } return notesOnTrack; } // Clear down the MidiFile Composition // void MidiFile::clearMidiComposition() { for (MidiComposition::iterator ci = m_midiComposition.begin(); ci != m_midiComposition.end(); ++ci) { //std::cerr << "MidiFile::clearMidiComposition: track " << ci->first << std::endl; for (MidiTrack::iterator ti = ci->second.begin(); ti != ci->second.end(); ++ti) { delete *ti; } ci->second.clear(); } m_midiComposition.clear(); m_trackChannelMap.clear(); } // Doesn't do anything yet - doesn't need to. We need to satisfy // the pure virtual function in the base class. // void MidiFile::close() {} } #include "MidiFile.moc"