summaryrefslogtreecommitdiffstats
path: root/src/sound/MidiFile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/sound/MidiFile.cpp')
-rw-r--r--src/sound/MidiFile.cpp2261
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"