changeset 301:73537d900d4b

* Add MIDI file export (closes FR#1643721)
author Chris Cannam
date Thu, 04 Oct 2007 11:52:38 +0000
parents 5877d68815c7
children 726b32522e3f
files data/data.pro data/fileio/FileFinder.cpp data/fileio/FileFinder.h data/fileio/MIDIEvent.h data/fileio/MIDIFileReader.cpp data/fileio/MIDIFileReader.h data/fileio/MIDIFileWriter.cpp data/fileio/MIDIFileWriter.h data/model/SparseModel.h data/model/SparseValueModel.h
diffstat 10 files changed, 781 insertions(+), 199 deletions(-) [+]
line wrap: on
line diff
--- a/data/data.pro	Mon Oct 01 13:48:38 2007 +0000
+++ b/data/data.pro	Thu Oct 04 11:52:38 2007 +0000
@@ -31,7 +31,9 @@
            fileio/FileReadThread.h \
            fileio/MatchFileReader.h \
            fileio/MatrixFile.h \
+           fileio/MIDIEvent.h \
            fileio/MIDIFileReader.h \
+           fileio/MIDIFileWriter.h \
            fileio/MP3FileReader.h \
            fileio/OggVorbisFileReader.h \
            fileio/PlaylistFileReader.h \
@@ -74,6 +76,7 @@
            fileio/MatchFileReader.cpp \
            fileio/MatrixFile.cpp \
            fileio/MIDIFileReader.cpp \
+           fileio/MIDIFileWriter.cpp \
            fileio/MP3FileReader.cpp \
            fileio/OggVorbisFileReader.cpp \
            fileio/PlaylistFileReader.cpp \
--- a/data/fileio/FileFinder.cpp	Mon Oct 01 13:48:38 2007 +0000
+++ b/data/fileio/FileFinder.cpp	Thu Oct 04 11:52:38 2007 +0000
@@ -76,6 +76,11 @@
         filter = tr("All supported files (%1)\nSonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)").arg(DataFileReaderFactory::getKnownExtensions());
         break;
 
+    case LayerFileNoMidi:
+        settingsKey = "layerpath";
+        filter = tr("All supported files (%1)\nSonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nText files (*.txt)\nAll files (*.*)").arg(DataFileReaderFactory::getKnownExtensions());
+        break;
+
     case SessionOrAudioFile:
         settingsKey = "lastpath";
         filter = tr("All supported files (*.sv %1)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nAll files (*.*)")
@@ -194,6 +199,12 @@
     case LayerFile:
         settingsKey = "savelayerpath";
         title = tr("Select a file to export to");
+        filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)");
+        break;
+
+    case LayerFileNoMidi:
+        settingsKey = "savelayerpath";
+        title = tr("Select a file to export to");
         filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nText files (*.txt)\nAll files (*.*)");
         break;
 
@@ -271,6 +282,8 @@
                 expectedExtension = "txt";
             } else if (selectedFilter.contains(".csv")) {
                 expectedExtension = "csv";
+            } else if (selectedFilter.contains(".mid")) {
+                expectedExtension = "mid";
             }
             if (expectedExtension != "") {
                 path = QString("%1.%2").arg(path).arg(expectedExtension);
@@ -322,6 +335,10 @@
         settingsKey = "layerpath";
         break;
 
+    case LayerFileNoMidi:
+        settingsKey = "layerpath";
+        break;
+
     case SessionOrAudioFile:
         settingsKey = "lastpath";
         break;
--- a/data/fileio/FileFinder.h	Mon Oct 01 13:48:38 2007 +0000
+++ b/data/fileio/FileFinder.h	Thu Oct 04 11:52:38 2007 +0000
@@ -30,6 +30,7 @@
         SessionFile,
         AudioFile,
         LayerFile,
+        LayerFileNoMidi,
         SessionOrAudioFile,
         ImageFile,
         AnyFile
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/MIDIEvent.h	Thu Oct 04 11:52:38 2007 +0000
@@ -0,0 +1,228 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    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.
+*/
+
+
+/*
+   This is a modified version of a source file from the 
+   Rosegarden MIDI and audio sequencer and notation editor.
+   This file copyright 2000-2006 Richard Bown and Chris Cannam.
+*/
+
+#ifndef _MIDI_EVENT_H_
+#define _MIDI_EVENT_H_
+
+#include <QString>
+#include <string>
+#include <iostream>
+
+typedef unsigned char MIDIByte;
+
+namespace MIDIConstants
+{
+    static const char *const MIDI_FILE_HEADER         = "MThd";
+    static const char *const MIDI_TRACK_HEADER        = "MTrk";
+
+    static const MIDIByte MIDI_STATUS_BYTE_MASK       = 0x80;
+    static const MIDIByte MIDI_MESSAGE_TYPE_MASK      = 0xF0;
+    static const MIDIByte MIDI_CHANNEL_NUM_MASK       = 0x0F;
+
+    static const MIDIByte MIDI_NOTE_OFF               = 0x80;
+    static const MIDIByte MIDI_NOTE_ON                = 0x90;
+    static const MIDIByte MIDI_POLY_AFTERTOUCH        = 0xA0;
+    static const MIDIByte MIDI_CTRL_CHANGE            = 0xB0;
+    static const MIDIByte MIDI_PROG_CHANGE            = 0xC0;
+    static const MIDIByte MIDI_CHNL_AFTERTOUCH        = 0xD0;
+    static const MIDIByte MIDI_PITCH_BEND             = 0xE0;
+    static const MIDIByte MIDI_SELECT_CHNL_MODE       = 0xB0;
+    static const MIDIByte MIDI_SYSTEM_EXCLUSIVE       = 0xF0;
+    static const MIDIByte MIDI_TC_QUARTER_FRAME       = 0xF1;
+    static const MIDIByte MIDI_SONG_POSITION_PTR      = 0xF2;
+    static const MIDIByte MIDI_SONG_SELECT            = 0xF3;
+    static const MIDIByte MIDI_TUNE_REQUEST           = 0xF6;
+    static const MIDIByte MIDI_END_OF_EXCLUSIVE       = 0xF7;
+    static const MIDIByte MIDI_TIMING_CLOCK           = 0xF8;
+    static const MIDIByte MIDI_START                  = 0xFA;
+    static const MIDIByte MIDI_CONTINUE               = 0xFB;
+    static const MIDIByte MIDI_STOP                   = 0xFC;
+    static const MIDIByte MIDI_ACTIVE_SENSING         = 0xFE;
+    static const MIDIByte MIDI_SYSTEM_RESET           = 0xFF;
+    static const MIDIByte MIDI_SYSEX_NONCOMMERCIAL    = 0x7D;
+    static const MIDIByte MIDI_SYSEX_NON_RT           = 0x7E;
+    static const MIDIByte MIDI_SYSEX_RT               = 0x7F;
+    static const MIDIByte MIDI_SYSEX_RT_COMMAND       = 0x06;
+    static const MIDIByte MIDI_SYSEX_RT_RESPONSE      = 0x07;
+    static const MIDIByte MIDI_MMC_STOP               = 0x01;
+    static const MIDIByte MIDI_MMC_PLAY               = 0x02;
+    static const MIDIByte MIDI_MMC_DEFERRED_PLAY      = 0x03;
+    static const MIDIByte MIDI_MMC_FAST_FORWARD       = 0x04;
+    static const MIDIByte MIDI_MMC_REWIND             = 0x05;
+    static const MIDIByte MIDI_MMC_RECORD_STROBE      = 0x06;
+    static const MIDIByte MIDI_MMC_RECORD_EXIT        = 0x07;
+    static const MIDIByte MIDI_MMC_RECORD_PAUSE       = 0x08;
+    static const MIDIByte MIDI_MMC_PAUSE              = 0x08;
+    static const MIDIByte MIDI_MMC_EJECT              = 0x0A;
+    static const MIDIByte MIDI_MMC_LOCATE             = 0x44;
+    static const MIDIByte MIDI_FILE_META_EVENT        = 0xFF;
+    static const MIDIByte MIDI_SEQUENCE_NUMBER        = 0x00;
+    static const MIDIByte MIDI_TEXT_EVENT             = 0x01;
+    static const MIDIByte MIDI_COPYRIGHT_NOTICE       = 0x02;
+    static const MIDIByte MIDI_TRACK_NAME             = 0x03;
+    static const MIDIByte MIDI_INSTRUMENT_NAME        = 0x04;
+    static const MIDIByte MIDI_LYRIC                  = 0x05;
+    static const MIDIByte MIDI_TEXT_MARKER            = 0x06;
+    static const MIDIByte MIDI_CUE_POINT              = 0x07;
+    static const MIDIByte MIDI_CHANNEL_PREFIX         = 0x20;
+    static const MIDIByte MIDI_CHANNEL_PREFIX_OR_PORT = 0x21;
+    static const MIDIByte MIDI_END_OF_TRACK           = 0x2F;
+    static const MIDIByte MIDI_SET_TEMPO              = 0x51;
+    static const MIDIByte MIDI_SMPTE_OFFSET           = 0x54;
+    static const MIDIByte MIDI_TIME_SIGNATURE         = 0x58;
+    static const MIDIByte MIDI_KEY_SIGNATURE          = 0x59;
+    static const MIDIByte MIDI_SEQUENCER_SPECIFIC     = 0x7F;
+    static const MIDIByte MIDI_CONTROLLER_BANK_MSB      = 0x00;
+    static const MIDIByte MIDI_CONTROLLER_VOLUME        = 0x07;
+    static const MIDIByte MIDI_CONTROLLER_BANK_LSB      = 0x20;
+    static const MIDIByte MIDI_CONTROLLER_MODULATION    = 0x01;
+    static const MIDIByte MIDI_CONTROLLER_PAN           = 0x0A;
+    static const MIDIByte MIDI_CONTROLLER_SUSTAIN       = 0x40;
+    static const MIDIByte MIDI_CONTROLLER_RESONANCE     = 0x47;
+    static const MIDIByte MIDI_CONTROLLER_RELEASE       = 0x48;
+    static const MIDIByte MIDI_CONTROLLER_ATTACK        = 0x49;
+    static const MIDIByte MIDI_CONTROLLER_FILTER        = 0x4A;
+    static const MIDIByte MIDI_CONTROLLER_REVERB        = 0x5B;
+    static const MIDIByte MIDI_CONTROLLER_CHORUS        = 0x5D;
+    static const MIDIByte MIDI_CONTROLLER_NRPN_1        = 0x62;
+    static const MIDIByte MIDI_CONTROLLER_NRPN_2        = 0x63;
+    static const MIDIByte MIDI_CONTROLLER_RPN_1         = 0x64;
+    static const MIDIByte MIDI_CONTROLLER_RPN_2         = 0x65;
+    static const MIDIByte MIDI_CONTROLLER_SOUNDS_OFF    = 0x78;
+    static const MIDIByte MIDI_CONTROLLER_RESET         = 0x79;
+    static const MIDIByte MIDI_CONTROLLER_LOCAL         = 0x7A;
+    static const MIDIByte MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7B;
+    static const MIDIByte MIDI_PERCUSSION_CHANNEL       = 9;
+}
+
+class MIDIEvent
+{
+public:
+    MIDIEvent(unsigned long deltaTime,
+              MIDIByte eventCode,
+              MIDIByte data1 = 0,
+              MIDIByte data2 = 0) :
+	m_deltaTime(deltaTime),
+	m_duration(0),
+	m_eventCode(eventCode),
+	m_data1(data1),
+	m_data2(data2),
+	m_metaEventCode(0)
+    { }
+
+    MIDIEvent(unsigned long deltaTime,
+              MIDIByte eventCode,
+              MIDIByte metaEventCode,
+              const std::string &metaMessage) :
+	m_deltaTime(deltaTime),
+	m_duration(0),
+	m_eventCode(eventCode),
+	m_data1(0),
+	m_data2(0),
+	m_metaEventCode(metaEventCode),
+	m_metaMessage(metaMessage)
+    { }
+
+    MIDIEvent(unsigned long deltaTime,
+              MIDIByte eventCode,
+              const std::string &sysEx) :
+	m_deltaTime(deltaTime),
+	m_duration(0),
+	m_eventCode(eventCode),
+	m_data1(0),
+	m_data2(0),
+	m_metaEventCode(0),
+	m_metaMessage(sysEx)
+    { }
+
+    ~MIDIEvent() { }
+
+    void setTime(const unsigned long &time) { m_deltaTime = time; }
+    void setDuration(const unsigned long& duration) { m_duration = duration;}
+    unsigned long addTime(const unsigned long &time) {
+	m_deltaTime += time;
+	return m_deltaTime;
+    }
+
+    MIDIByte getMessageType() const
+        { return (m_eventCode & MIDIConstants::MIDI_MESSAGE_TYPE_MASK); }
+
+    MIDIByte getChannelNumber() const
+        { return (m_eventCode & MIDIConstants::MIDI_CHANNEL_NUM_MASK); }
+
+    unsigned long getTime() const { return m_deltaTime; }
+    unsigned long getDuration() const { return m_duration; }
+
+    MIDIByte getPitch() const { return m_data1; }
+    MIDIByte getVelocity() const { return m_data2; }
+    MIDIByte getData1() const { return m_data1; }
+    MIDIByte getData2() const { return m_data2; }
+    MIDIByte getEventCode() const { return m_eventCode; }
+
+    bool isMeta() const { return (m_eventCode == MIDIConstants::MIDI_FILE_META_EVENT); }
+
+    MIDIByte getMetaEventCode() const { return m_metaEventCode; }
+    std::string getMetaMessage() const { return m_metaMessage; }
+    void setMetaMessage(const std::string &meta) { m_metaMessage = meta; }
+
+    friend bool operator<(const MIDIEvent &a, const MIDIEvent &b);
+
+private:
+    MIDIEvent& operator=(const MIDIEvent);
+
+    unsigned long  m_deltaTime;
+    unsigned long  m_duration;
+    MIDIByte       m_eventCode;
+    MIDIByte       m_data1;         // or Note
+    MIDIByte       m_data2;         // or Velocity
+    MIDIByte       m_metaEventCode;
+    std::string    m_metaMessage;
+};
+
+// Comparator for sorting
+//
+struct MIDIEventCmp
+{
+    bool operator()(const MIDIEvent &mE1, const MIDIEvent &mE2) const
+    { return mE1.getTime() < mE2.getTime(); }
+
+    bool operator()(const MIDIEvent *mE1, const MIDIEvent *mE2) const
+    { return mE1->getTime() < mE2->getTime(); }
+};
+
+class MIDIException : virtual public std::exception
+{
+public:
+    MIDIException(QString message) throw() : m_message(message) {
+        std::cerr << "WARNING: MIDI exception: "
+		  << message.toLocal8Bit().data() << std::endl;
+    }
+    virtual ~MIDIException() throw() { }
+
+    virtual const char *what() const throw() {
+	return m_message.toLocal8Bit().data();
+    }
+
+protected:
+    QString m_message;
+};
+
+#endif
--- a/data/fileio/MIDIFileReader.cpp	Mon Oct 01 13:48:38 2007 +0000
+++ b/data/fileio/MIDIFileReader.cpp	Thu Oct 04 11:52:38 2007 +0000
@@ -28,6 +28,8 @@
 
 #include "MIDIFileReader.h"
 
+#include "MIDIEvent.h"
+
 #include "model/Model.h"
 #include "base/Pitch.h"
 #include "base/RealTime.h"
@@ -50,203 +52,10 @@
 using std::map;
 using std::set;
 
+using namespace MIDIConstants;
+
 //#define MIDI_DEBUG 1
 
-static const char *const MIDI_FILE_HEADER         = "MThd";
-static const char *const MIDI_TRACK_HEADER        = "MTrk";
-
-static const MIDIFileReader::MIDIByte MIDI_STATUS_BYTE_MASK       = 0x80;
-static const MIDIFileReader::MIDIByte MIDI_MESSAGE_TYPE_MASK      = 0xF0;
-static const MIDIFileReader::MIDIByte MIDI_CHANNEL_NUM_MASK       = 0x0F;
-static const MIDIFileReader::MIDIByte MIDI_NOTE_OFF               = 0x80;
-static const MIDIFileReader::MIDIByte MIDI_NOTE_ON                = 0x90;
-static const MIDIFileReader::MIDIByte MIDI_POLY_AFTERTOUCH        = 0xA0;
-static const MIDIFileReader::MIDIByte MIDI_CTRL_CHANGE            = 0xB0;
-static const MIDIFileReader::MIDIByte MIDI_PROG_CHANGE            = 0xC0;
-static const MIDIFileReader::MIDIByte MIDI_CHNL_AFTERTOUCH        = 0xD0;
-static const MIDIFileReader::MIDIByte MIDI_PITCH_BEND             = 0xE0;
-static const MIDIFileReader::MIDIByte MIDI_SELECT_CHNL_MODE       = 0xB0;
-static const MIDIFileReader::MIDIByte MIDI_SYSTEM_EXCLUSIVE       = 0xF0;
-static const MIDIFileReader::MIDIByte MIDI_TC_QUARTER_FRAME       = 0xF1;
-static const MIDIFileReader::MIDIByte MIDI_SONG_POSITION_PTR      = 0xF2;
-static const MIDIFileReader::MIDIByte MIDI_SONG_SELECT            = 0xF3;
-static const MIDIFileReader::MIDIByte MIDI_TUNE_REQUEST           = 0xF6;
-static const MIDIFileReader::MIDIByte MIDI_END_OF_EXCLUSIVE       = 0xF7;
-static const MIDIFileReader::MIDIByte MIDI_TIMING_CLOCK           = 0xF8;
-static const MIDIFileReader::MIDIByte MIDI_START                  = 0xFA;
-static const MIDIFileReader::MIDIByte MIDI_CONTINUE               = 0xFB;
-static const MIDIFileReader::MIDIByte MIDI_STOP                   = 0xFC;
-static const MIDIFileReader::MIDIByte MIDI_ACTIVE_SENSING         = 0xFE;
-static const MIDIFileReader::MIDIByte MIDI_SYSTEM_RESET           = 0xFF;
-static const MIDIFileReader::MIDIByte MIDI_SYSEX_NONCOMMERCIAL    = 0x7D;
-static const MIDIFileReader::MIDIByte MIDI_SYSEX_NON_RT           = 0x7E;
-static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT               = 0x7F;
-static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT_COMMAND       = 0x06;
-static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT_RESPONSE      = 0x07;
-static const MIDIFileReader::MIDIByte MIDI_MMC_STOP               = 0x01;
-static const MIDIFileReader::MIDIByte MIDI_MMC_PLAY               = 0x02;
-static const MIDIFileReader::MIDIByte MIDI_MMC_DEFERRED_PLAY      = 0x03;
-static const MIDIFileReader::MIDIByte MIDI_MMC_FAST_FORWARD       = 0x04;
-static const MIDIFileReader::MIDIByte MIDI_MMC_REWIND             = 0x05;
-static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_STROBE      = 0x06;
-static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_EXIT        = 0x07;
-static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_PAUSE       = 0x08;
-static const MIDIFileReader::MIDIByte MIDI_MMC_PAUSE              = 0x08;
-static const MIDIFileReader::MIDIByte MIDI_MMC_EJECT              = 0x0A;
-static const MIDIFileReader::MIDIByte MIDI_MMC_LOCATE             = 0x44;
-static const MIDIFileReader::MIDIByte MIDI_FILE_META_EVENT        = 0xFF;
-static const MIDIFileReader::MIDIByte MIDI_SEQUENCE_NUMBER        = 0x00;
-static const MIDIFileReader::MIDIByte MIDI_TEXT_EVENT             = 0x01;
-static const MIDIFileReader::MIDIByte MIDI_COPYRIGHT_NOTICE       = 0x02;
-static const MIDIFileReader::MIDIByte MIDI_TRACK_NAME             = 0x03;
-static const MIDIFileReader::MIDIByte MIDI_INSTRUMENT_NAME        = 0x04;
-static const MIDIFileReader::MIDIByte MIDI_LYRIC                  = 0x05;
-static const MIDIFileReader::MIDIByte MIDI_TEXT_MARKER            = 0x06;
-static const MIDIFileReader::MIDIByte MIDI_CUE_POINT              = 0x07;
-static const MIDIFileReader::MIDIByte MIDI_CHANNEL_PREFIX         = 0x20;
-static const MIDIFileReader::MIDIByte MIDI_CHANNEL_PREFIX_OR_PORT = 0x21;
-static const MIDIFileReader::MIDIByte MIDI_END_OF_TRACK           = 0x2F;
-static const MIDIFileReader::MIDIByte MIDI_SET_TEMPO              = 0x51;
-static const MIDIFileReader::MIDIByte MIDI_SMPTE_OFFSET           = 0x54;
-static const MIDIFileReader::MIDIByte MIDI_TIME_SIGNATURE         = 0x58;
-static const MIDIFileReader::MIDIByte MIDI_KEY_SIGNATURE          = 0x59;
-static const MIDIFileReader::MIDIByte MIDI_SEQUENCER_SPECIFIC     = 0x7F;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_BANK_MSB      = 0x00;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_VOLUME        = 0x07;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_BANK_LSB      = 0x20;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_MODULATION    = 0x01;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_PAN           = 0x0A;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_SUSTAIN       = 0x40;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RESONANCE     = 0x47;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RELEASE       = 0x48;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_ATTACK        = 0x49;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_FILTER        = 0x4A;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_REVERB        = 0x5B;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_CHORUS        = 0x5D;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_NRPN_1        = 0x62;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_NRPN_2        = 0x63;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RPN_1         = 0x64;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RPN_2         = 0x65;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_SOUNDS_OFF    = 0x78;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RESET         = 0x79;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_LOCAL         = 0x7A;
-static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7B;
-static const MIDIFileReader::MIDIByte MIDI_PERCUSSION_CHANNEL       = 9;
-
-class MIDIEvent
-{
-public:
-    typedef MIDIFileReader::MIDIByte MIDIByte;
-
-    MIDIEvent(unsigned long deltaTime,
-              MIDIByte eventCode,
-              MIDIByte data1 = 0,
-              MIDIByte data2 = 0) :
-	m_deltaTime(deltaTime),
-	m_duration(0),
-	m_eventCode(eventCode),
-	m_data1(data1),
-	m_data2(data2),
-	m_metaEventCode(0)
-    { }
-
-    MIDIEvent(unsigned long deltaTime,
-              MIDIByte eventCode,
-              MIDIByte metaEventCode,
-              const string &metaMessage) :
-	m_deltaTime(deltaTime),
-	m_duration(0),
-	m_eventCode(eventCode),
-	m_data1(0),
-	m_data2(0),
-	m_metaEventCode(metaEventCode),
-	m_metaMessage(metaMessage)
-    { }
-
-    MIDIEvent(unsigned long deltaTime,
-              MIDIByte eventCode,
-              const string &sysEx) :
-	m_deltaTime(deltaTime),
-	m_duration(0),
-	m_eventCode(eventCode),
-	m_data1(0),
-	m_data2(0),
-	m_metaEventCode(0),
-	m_metaMessage(sysEx)
-    { }
-
-    ~MIDIEvent() { }
-
-    void setTime(const unsigned long &time) { m_deltaTime = time; }
-    void setDuration(const unsigned long& duration) { m_duration = duration;}
-    unsigned long addTime(const unsigned long &time) {
-	m_deltaTime += time;
-	return m_deltaTime;
-    }
-
-    MIDIByte getMessageType() const
-        { return (m_eventCode & MIDI_MESSAGE_TYPE_MASK); }
-
-    MIDIByte getChannelNumber() const
-        { return (m_eventCode & MIDI_CHANNEL_NUM_MASK); }
-
-    unsigned long getTime() const { return m_deltaTime; }
-    unsigned long getDuration() const { return m_duration; }
-
-    MIDIByte getPitch() const { return m_data1; }
-    MIDIByte getVelocity() const { return m_data2; }
-    MIDIByte getData1() const { return m_data1; }
-    MIDIByte getData2() const { return m_data2; }
-    MIDIByte getEventCode() const { return m_eventCode; }
-
-    bool isMeta() const { return (m_eventCode == MIDI_FILE_META_EVENT); }
-
-    MIDIByte getMetaEventCode() const { return m_metaEventCode; }
-    string getMetaMessage() const { return m_metaMessage; }
-    void setMetaMessage(const string &meta) { m_metaMessage = meta; }
-
-    friend bool operator<(const MIDIEvent &a, const MIDIEvent &b);
-
-private:
-    MIDIEvent& operator=(const MIDIEvent);
-
-    unsigned long  m_deltaTime;
-    unsigned long  m_duration;
-    MIDIByte       m_eventCode;
-    MIDIByte       m_data1;         // or Note
-    MIDIByte       m_data2;         // or Velocity
-    MIDIByte       m_metaEventCode;
-    string         m_metaMessage;
-};
-
-// Comparator for sorting
-//
-struct MIDIEventCmp
-{
-    bool operator()(const MIDIEvent &mE1, const MIDIEvent &mE2) const
-    { return mE1.getTime() < mE2.getTime(); }
-
-    bool operator()(const MIDIEvent *mE1, const MIDIEvent *mE2) const
-    { return mE1->getTime() < mE2->getTime(); }
-};
-
-class MIDIException : virtual public std::exception
-{
-public:
-    MIDIException(QString message) throw() : m_message(message) {
-	cerr << "WARNING: MIDI exception: "
-		  << message.toLocal8Bit().data() << endl;
-    }
-    virtual ~MIDIException() throw() { }
-
-    virtual const char *what() const throw() {
-	return m_message.toLocal8Bit().data();
-    }
-
-protected:
-    QString m_message;
-};
-
 
 MIDIFileReader::MIDIFileReader(QString path,
 			       size_t mainModelSampleRate) :
@@ -325,7 +134,7 @@
 // section we can read only a specified number of bytes held in
 // m_trackByteCount.
 //
-MIDIFileReader::MIDIByte
+MIDIByte
 MIDIFileReader::getMIDIByte()
 {
     if (!m_midiFile) {
@@ -477,6 +286,7 @@
 	m_error = "File not found or not readable.";
 	m_format = MIDI_FILE_BAD_FORMAT;
 	delete m_midiFile;
+        m_midiFile = 0;
 	return false;
     }
 
--- a/data/fileio/MIDIFileReader.h	Mon Oct 01 13:48:38 2007 +0000
+++ b/data/fileio/MIDIFileReader.h	Thu Oct 04 11:52:38 2007 +0000
@@ -33,6 +33,8 @@
 
 class MIDIEvent;
 
+typedef unsigned char MIDIByte;
+
 class MIDIFileReader : public DataFileReader
 {
     Q_OBJECT
@@ -45,8 +47,6 @@
     virtual QString getError() const;
     virtual Model *load() const;
 
-    typedef unsigned char MIDIByte;
-
 protected:
     typedef std::vector<MIDIEvent *> MIDITrack;
     typedef std::map<unsigned int, MIDITrack> MIDIComposition;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/MIDIFileWriter.cpp	Thu Oct 04 11:52:38 2007 +0000
@@ -0,0 +1,431 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    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.
+*/
+
+
+/*
+   This is a modified version of a source file from the 
+   Rosegarden MIDI and audio sequencer and notation editor.
+   This file copyright 2000-2007 Richard Bown and Chris Cannam
+   and copyright 2007 QMUL.
+*/
+
+#include "MIDIFileWriter.h"
+
+#include "MIDIEvent.h"
+
+#include "model/NoteModel.h"
+
+#include "base/Pitch.h"
+
+#include <algorithm>
+#include <fstream>
+
+using std::ofstream;
+using std::string;
+using std::ios;
+
+using namespace MIDIConstants;
+
+MIDIFileWriter::MIDIFileWriter(QString path, NoteModel *model, float tempo) :
+    m_path(path),
+    m_model(model),
+    m_modelUsesHz(false),
+    m_tempo(tempo)
+{
+    if (model->getScaleUnits().toLower() == "hz") m_modelUsesHz = true;
+
+    if (!convert()) {
+        m_error = "Conversion from model to internal MIDI format failed";
+    }
+}
+
+MIDIFileWriter::~MIDIFileWriter()
+{
+    for (MIDIComposition::iterator i = m_midiComposition.begin();
+	 i != m_midiComposition.end(); ++i) {
+	
+	for (MIDITrack::iterator j = i->second.begin();
+	     j != i->second.end(); ++j) {
+	    delete *j;
+	}
+
+	i->second.clear();
+    }
+
+    m_midiComposition.clear();
+}
+
+bool
+MIDIFileWriter::isOK() const
+{
+    return m_error == "";
+}
+
+QString
+MIDIFileWriter::getError() const
+{
+    return m_error;
+}
+
+void
+MIDIFileWriter::write()
+{
+    writeComposition();
+}
+
+string
+MIDIFileWriter::intToMIDIBytes(int number) const
+{
+    MIDIByte upper;
+    MIDIByte lower;
+
+    upper = (number & 0xFF00) >> 8;
+    lower = (number & 0x00FF);
+
+    string rv;
+    rv += upper;
+    rv += lower;
+    return rv;
+}
+
+string
+MIDIFileWriter::longToMIDIBytes(unsigned long number) const
+{
+    MIDIByte upper1;
+    MIDIByte lower1;
+    MIDIByte upper2;
+    MIDIByte lower2;
+
+    upper1 = (number & 0xff000000) >> 24;
+    lower1 = (number & 0x00ff0000) >> 16;
+    upper2 = (number & 0x0000ff00) >> 8;
+    lower2 = (number & 0x000000ff);
+
+    string rv;
+    rv += upper1;
+    rv += lower1;
+    rv += upper2;
+    rv += lower2;
+    return rv;
+}
+
+// Turn a delta time into a MIDI time - overlapping into
+// a maximum of four bytes using the MSB as the carry on
+// flag.
+//
+string
+MIDIFileWriter::longToVarBuffer(unsigned long number) const
+{
+    string rv;
+
+    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) {
+        rv += (MIDIByte)(outNumber & 0xff);
+        if (outNumber & 0x80)
+            outNumber >>= 8;
+        else
+            break;
+    }
+
+    return rv;
+}
+
+bool
+MIDIFileWriter::writeHeader()
+{
+    *m_midiFile << MIDI_FILE_HEADER;
+
+    // Number of bytes in header
+    *m_midiFile << (MIDIByte) 0x00;
+    *m_midiFile << (MIDIByte) 0x00;
+    *m_midiFile << (MIDIByte) 0x00;
+    *m_midiFile << (MIDIByte) 0x06;
+
+    // File format
+    *m_midiFile << (MIDIByte) 0x00;
+    *m_midiFile << (MIDIByte) m_format;
+
+    *m_midiFile << intToMIDIBytes(m_numberOfTracks);
+
+    *m_midiFile << intToMIDIBytes(m_timingDivision);
+
+    return true;
+}
+
+bool
+MIDIFileWriter::writeTrack(int trackNumber)
+{
+    bool retOK = true;
+    MIDIByte eventCode = 0;
+    MIDITrack::iterator midiEvent;
+
+    // First we write into the trackBuffer, then write it out to the
+    // file with its accompanying length.
+    //
+    string trackBuffer;
+
+    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)
+            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:
+                break;
+            }
+        }
+    }
+
+    // Now we write the track - First the standard header..
+    //
+    *m_midiFile << MIDI_TRACK_HEADER;
+
+    // ..now the length of the buffer..
+    //
+    *m_midiFile << longToMIDIBytes((long)trackBuffer.length());
+
+    // ..then the buffer itself..
+    //
+    *m_midiFile << trackBuffer;
+
+    return retOK;
+}
+
+bool
+MIDIFileWriter::writeComposition()
+{
+    bool retOK = true;
+
+    m_midiFile =
+        new ofstream(m_path.toLocal8Bit().data(), ios::out | ios::binary);
+
+    if (!(*m_midiFile)) {
+        m_error = "Can't open file for writing.";
+        delete m_midiFile;
+        m_midiFile = 0;
+        return false;
+    }
+
+    if (!writeHeader()) {
+        retOK = false;
+    }
+
+    for (unsigned int i = 0; i < m_numberOfTracks; i++) {
+        if (!writeTrack(i)) {
+            retOK = false;
+        }
+    }
+
+    m_midiFile->close();
+    delete m_midiFile;
+    m_midiFile = 0;
+
+    if (!retOK) {
+        m_error = "MIDI file write failed";
+    }
+
+    return retOK;
+}
+
+bool
+MIDIFileWriter::convert()
+{
+    m_timingDivision = 480;
+    m_format = MIDI_SINGLE_TRACK_FILE;
+    m_numberOfTracks = 1;
+
+    int track = 0;
+    int midiChannel = 0;
+
+    MIDIEvent *event;
+
+    event = new MIDIEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT,
+                          "Exported from Sonic Visualiser");
+    m_midiComposition[track].push_back(event);
+
+    event = new MIDIEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT,
+                          "http://www.sonicvisualiser.org/");
+    m_midiComposition[track].push_back(event);
+
+    long tempoValue = long(60000000.0 / m_tempo + 0.01);
+    string tempoString;
+    tempoString += (MIDIByte)(tempoValue >> 16 & 0xFF);
+    tempoString += (MIDIByte)(tempoValue >> 8 & 0xFF);
+    tempoString += (MIDIByte)(tempoValue & 0xFF);
+
+    event = new MIDIEvent(0, MIDI_FILE_META_EVENT, MIDI_SET_TEMPO,
+                          tempoString);
+    m_midiComposition[track].push_back(event);
+
+    // Omit time signature
+
+    const NoteModel::PointList &notes =
+        static_cast<SparseModel<Note> *>(m_model)->getPoints();
+
+    for (NoteModel::PointList::const_iterator i = notes.begin();
+         i != notes.end(); ++i) {
+
+        long frame = i->frame;
+        float value = i->value;
+        size_t duration = i->duration;
+
+        int pitch;
+
+        if (m_modelUsesHz) {
+            pitch = Pitch::getPitchForFrequency(value);
+        } else {
+            pitch = lrintf(value);
+        }
+
+        if (pitch < 0) pitch = 0;
+        if (pitch > 127) pitch = 127;
+
+        // Convert frame to MIDI time
+
+        double seconds = double(frame) / double(m_model->getSampleRate());
+        double quarters = (seconds * m_tempo) / 60.0;
+        unsigned long midiTime = lrint(quarters * m_timingDivision);
+
+        // We don't support velocity in note models yet
+        int velocity = 100;
+
+        // Get the sounding time for the matching NOTE_OFF
+        seconds = double(frame + duration) / double(m_model->getSampleRate());
+        quarters = (seconds * m_tempo) / 60.0;
+        unsigned long endTime = lrint(quarters * m_timingDivision);
+
+        // At this point all the notes we insert have absolute times
+        // in the delta time fields.  We resolve these into delta
+        // times further down (can't do it until all the note offs are
+        // in place).
+
+        event = new MIDIEvent(midiTime,
+                              MIDI_NOTE_ON | midiChannel,
+                              pitch,
+                              velocity);
+        m_midiComposition[track].push_back(event);
+
+        event = new MIDIEvent(endTime,
+                              MIDI_NOTE_OFF | midiChannel,
+                              pitch,
+                              127); // loudest silence you can muster
+
+        m_midiComposition[track].push_back(event);
+    }
+    
+    // Now gnash through the MIDI events and turn the absolute times
+    // into delta times.
+    //
+    for (unsigned int i = 0; i < m_numberOfTracks; i++) {
+
+        unsigned long 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.
+        //
+        std::stable_sort(m_midiComposition[i].begin(),
+                         m_midiComposition[i].end(),
+                         MIDIEventCmp());
+
+        for (MIDITrack::iterator it = m_midiComposition[i].begin();
+             it != m_midiComposition[i].end(); it++) {
+            unsigned long deltaTime = (*it)->getTime() - lastMidiTime;
+            lastMidiTime = (*it)->getTime();
+            (*it)->setTime(deltaTime);
+        }
+
+        // Insert end of track event (delta time = 0)
+        //
+        event = new MIDIEvent(0, MIDI_FILE_META_EVENT,
+                              MIDI_END_OF_TRACK, "");
+
+        m_midiComposition[i].push_back(event);
+    }
+
+    return true;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/MIDIFileWriter.h	Thu Oct 04 11:52:38 2007 +0000
@@ -0,0 +1,91 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    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.
+*/
+
+
+/*
+   This is a modified version of a source file from the 
+   Rosegarden MIDI and audio sequencer and notation editor.
+   This file copyright 2000-2007 Richard Bown and Chris Cannam
+   and copyright 2007 QMUL.
+*/
+
+#ifndef _MIDI_FILE_WRITER_H_
+#define _MIDI_FILE_WRITER_H_
+
+#include "base/RealTime.h"
+
+#include <QString>
+
+#include <vector>
+#include <map>
+#include <fstream>
+
+class MIDIEvent;
+class NoteModel;
+
+/**
+ * Write a MIDI file.  This includes file write code for generic
+ * simultaneous-track MIDI files, but the conversion stage only
+ * supports a single-track MIDI file with fixed tempo, time signature
+ * and timing division.
+ */
+class MIDIFileWriter 
+{
+public:
+    MIDIFileWriter(QString path, NoteModel *model, float tempo = 120.f);
+    virtual ~MIDIFileWriter();
+
+    virtual bool isOK() const;
+    virtual QString getError() const;
+
+    virtual void write();
+
+protected:
+    typedef std::vector<MIDIEvent *> MIDITrack;
+    typedef std::map<unsigned int, MIDITrack> MIDIComposition;
+
+    typedef enum {
+	MIDI_SINGLE_TRACK_FILE          = 0x00,
+	MIDI_SIMULTANEOUS_TRACK_FILE    = 0x01,
+	MIDI_SEQUENTIAL_TRACK_FILE      = 0x02,
+	MIDI_FILE_BAD_FORMAT            = 0xFF
+    } MIDIFileFormatType;
+
+    std::string intToMIDIBytes(int number) const;
+    std::string longToMIDIBytes(unsigned long number) const;
+    std::string longToVarBuffer(unsigned long number) const;
+
+    unsigned long getMIDITimeForTime(RealTime t) const;
+
+    bool writeHeader();
+    bool writeTrack(int track);
+    bool writeComposition();
+    
+    bool convert();
+
+    QString             m_path;
+    NoteModel          *m_model;
+    bool                m_modelUsesHz;
+    float               m_tempo;
+    int                 m_timingDivision;   // pulses per quarter note
+    MIDIFileFormatType  m_format;
+    unsigned int        m_numberOfTracks;
+
+    MIDIComposition     m_midiComposition;
+
+    std::ofstream      *m_midiFile;
+    QString             m_error;
+};
+
+#endif
--- a/data/model/SparseModel.h	Mon Oct 01 13:48:38 2007 +0000
+++ b/data/model/SparseModel.h	Thu Oct 04 11:52:38 2007 +0000
@@ -516,7 +516,7 @@
 void
 SparseModel<PointType>::setCompletion(int completion)
 {
-    std::cerr << "SparseModel::setCompletion(" << completion << ")" << std::endl;
+//    std::cerr << "SparseModel::setCompletion(" << completion << ")" << std::endl;
 
     if (m_completion != completion) {
 	m_completion = completion;
--- a/data/model/SparseValueModel.h	Mon Oct 01 13:48:38 2007 +0000
+++ b/data/model/SparseValueModel.h	Thu Oct 04 11:52:38 2007 +0000
@@ -50,6 +50,7 @@
 
     using SparseModel<PointType>::m_points;
     using SparseModel<PointType>::modelChanged;
+    using SparseModel<PointType>::getPoints;
 
     virtual float getValueMinimum() const { return m_valueMinimum; }
     virtual float getValueMaximum() const { return m_valueMaximum; }