# HG changeset patch # User Chris Cannam # Date 1191498758 0 # Node ID 73537d900d4b8a223375837d0eb9ff16a07dba91 # Parent 5877d68815c76872ea6a07c64f5a9077f3757942 * Add MIDI file export (closes FR#1643721) diff -r 5877d68815c7 -r 73537d900d4b data/data.pro --- 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 \ diff -r 5877d68815c7 -r 73537d900d4b data/fileio/FileFinder.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; diff -r 5877d68815c7 -r 73537d900d4b data/fileio/FileFinder.h --- 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 diff -r 5877d68815c7 -r 73537d900d4b data/fileio/MIDIEvent.h --- /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 +#include +#include + +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 diff -r 5877d68815c7 -r 73537d900d4b data/fileio/MIDIFileReader.cpp --- 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; } diff -r 5877d68815c7 -r 73537d900d4b data/fileio/MIDIFileReader.h --- 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 MIDITrack; typedef std::map MIDIComposition; diff -r 5877d68815c7 -r 73537d900d4b data/fileio/MIDIFileWriter.cpp --- /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 +#include + +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 ¬es = + static_cast *>(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; +} + diff -r 5877d68815c7 -r 73537d900d4b data/fileio/MIDIFileWriter.h --- /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 + +#include +#include +#include + +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 MIDITrack; + typedef std::map 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 diff -r 5877d68815c7 -r 73537d900d4b data/model/SparseModel.h --- 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::setCompletion(int completion) { - std::cerr << "SparseModel::setCompletion(" << completion << ")" << std::endl; +// std::cerr << "SparseModel::setCompletion(" << completion << ")" << std::endl; if (m_completion != completion) { m_completion = completion; diff -r 5877d68815c7 -r 73537d900d4b data/model/SparseValueModel.h --- 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::m_points; using SparseModel::modelChanged; + using SparseModel::getPoints; virtual float getValueMinimum() const { return m_valueMinimum; } virtual float getValueMaximum() const { return m_valueMaximum; }