You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rosegarden/src/sound/MidiFile.cpp

2254 lines
71 KiB

/*
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 <tdeapplication.h>
#include <fstream>
#include <string>
#include <cstdio>
#include <algorithm>
#include <sstream>
#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
#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"