diff options
Diffstat (limited to 'src/sound/MidiFile.cpp')
-rw-r--r-- | src/sound/MidiFile.cpp | 2261 |
1 files changed, 2261 insertions, 0 deletions
diff --git a/src/sound/MidiFile.cpp b/src/sound/MidiFile.cpp new file mode 100644 index 0000000..76d5c85 --- /dev/null +++ b/src/sound/MidiFile.cpp @@ -0,0 +1,2261 @@ +// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <bownie@bownie.com> + + 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 <iostream> +#include "misc/Debug.h" +#include <kapplication.h> +#include <fstream> +#include <string> +#include <cstdio> +#include <algorithm> + +#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 + +#if (__GNUC__ < 3) +#include <strstream> +#define stringstream strstream +#else +#include <sstream> +#endif + +#include <kapp.h> + +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<TrackId> but TrackId is unsigned + // and we need -1 to indicate "not yet used" + std::vector<int> 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<int, unsigned long> 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<Segment *> 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 + <Int>(BaseProperties::PITCH, + (*midiEvent)->getPitch()); + rosegardenEvent->set + <Int>(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<Segment *>::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<timeT, tempoT> 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<timeT, TimeSignature> 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<int, int> 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 + <Int>(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 + <Int>(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" |