/*
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"