Created
February 4, 2015 00:49
-
-
Save badosu/120eaf72dc3847049ce0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/include/ExportFilter.h b/include/ExportFilter.h | |
new file mode 100644 | |
index 0000000..857ed1b | |
--- /dev/null | |
+++ b/include/ExportFilter.h | |
@@ -0,0 +1,64 @@ | |
+/* | |
+ * ExportFilter.h - declaration of class ExportFilter, the base-class for all | |
+ * file export filters | |
+ * | |
+ * Copyright (c) 2006-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net> | |
+ * | |
+ * This file is part of LMMS - http://lmms.io | |
+ * | |
+ * 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. | |
+ * | |
+ * This program is distributed in the hope that it will be useful, | |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
+ * General Public License for more details. | |
+ * | |
+ * You should have received a copy of the GNU General Public | |
+ * License along with this program (see COPYING); if not, write to the | |
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
+ * Boston, MA 02110-1301 USA. | |
+ * | |
+ */ | |
+ | |
+#ifndef EXPORT_FILTER_H | |
+#define EXPORT_FILTER_H | |
+ | |
+#include <QtCore/QFile> | |
+ | |
+#include "TrackContainer.h" | |
+#include "Plugin.h" | |
+ | |
+ | |
+class EXPORT ExportFilter : public Plugin | |
+{ | |
+public: | |
+ ExportFilter( const Descriptor * _descriptor ) : Plugin( _descriptor, NULL ) {} | |
+ virtual ~ExportFilter() {} | |
+ | |
+ | |
+ virtual bool tryExport( const TrackContainer::TrackList &tracks, int tempo, const QString &filename ) = 0; | |
+protected: | |
+ | |
+ virtual void saveSettings( QDomDocument &, QDomElement & ) | |
+ { | |
+ } | |
+ | |
+ virtual void loadSettings( const QDomElement & ) | |
+ { | |
+ } | |
+ | |
+ virtual QString nodeName() const | |
+ { | |
+ return "import_filter"; | |
+ } | |
+ | |
+ | |
+private: | |
+ | |
+} ; | |
+ | |
+ | |
+#endif | |
diff --git a/include/Song.h b/include/Song.h | |
index 5da0981..487e856 100644 | |
--- a/include/Song.h | |
+++ b/include/Song.h | |
@@ -268,6 +268,7 @@ public slots: | |
void importProject(); | |
void exportProject(bool multiExport=false); | |
void exportProjectTracks(); | |
+ void exportProjectMidi(); | |
void startExport(); | |
void stopExport(); | |
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt | |
index d7b4cf1..56212ef 100644 | |
--- a/plugins/CMakeLists.txt | |
+++ b/plugins/CMakeLists.txt | |
@@ -20,6 +20,7 @@ ADD_SUBDIRECTORY(LadspaEffect) | |
ADD_SUBDIRECTORY(lb302) | |
#ADD_SUBDIRECTORY(lb303) | |
ADD_SUBDIRECTORY(MidiImport) | |
+ADD_SUBDIRECTORY(MidiExport) | |
ADD_SUBDIRECTORY(MultitapEcho) | |
ADD_SUBDIRECTORY(monstro) | |
ADD_SUBDIRECTORY(nes) | |
diff --git a/plugins/MidiExport/CMakeLists.txt b/plugins/MidiExport/CMakeLists.txt | |
new file mode 100644 | |
index 0000000..1d19f08 | |
--- /dev/null | |
+++ b/plugins/MidiExport/CMakeLists.txt | |
@@ -0,0 +1,4 @@ | |
+INCLUDE(BuildPlugin) | |
+ | |
+BUILD_PLUGIN(midiexport MidiExport.cpp MidiExport.h MidiFile.hpp | |
+ MOCFILES MidiExport.h) | |
diff --git a/plugins/MidiExport/MidiExport.cpp b/plugins/MidiExport/MidiExport.cpp | |
new file mode 100644 | |
index 0000000..03ef3a4 | |
--- /dev/null | |
+++ b/plugins/MidiExport/MidiExport.cpp | |
@@ -0,0 +1,183 @@ | |
+/* | |
+ * MidiExport.cpp - support for importing MIDI files | |
+ * | |
+ * Author: Mohamed Abdel Maksoud <mohamed at amaksoud.com> | |
+ * | |
+ * This file is part of LMMS - http://lmms.io | |
+ * | |
+ * 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. | |
+ * | |
+ * This program is distributed in the hope that it will be useful, | |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
+ * General Public License for more details. | |
+ * | |
+ * You should have received a copy of the GNU General Public | |
+ * License along with this program (see COPYING); if not, write to the | |
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
+ * Boston, MA 02110-1301 USA. | |
+ * | |
+ */ | |
+ | |
+ | |
+#include <QDomDocument> | |
+#include <QDir> | |
+#include <QApplication> | |
+#include <QMessageBox> | |
+#include <QProgressDialog> | |
+ | |
+#include "MidiExport.h" | |
+#include "Engine.h" | |
+#include "TrackContainer.h" | |
+#include "InstrumentTrack.h" | |
+ | |
+ | |
+extern "C" | |
+{ | |
+ | |
+Plugin::Descriptor PLUGIN_EXPORT midiexport_plugin_descriptor = | |
+{ | |
+ STRINGIFY( PLUGIN_NAME ), | |
+ "MIDI Export", | |
+ QT_TRANSLATE_NOOP( "pluginBrowser", | |
+ "Filter for exporting MIDI-files from LMMS" ), | |
+ "Mohamed Abdel Maksoud <mohamed at amaksoud.com>", | |
+ 0x0100, | |
+ Plugin::ExportFilter, | |
+ NULL, | |
+ NULL, | |
+ NULL | |
+} ; | |
+ | |
+} | |
+ | |
+ | |
+MidiExport::MidiExport() : ExportFilter( &midiexport_plugin_descriptor) | |
+{ | |
+} | |
+ | |
+ | |
+ | |
+ | |
+MidiExport::~MidiExport() | |
+{ | |
+} | |
+ | |
+ | |
+ | |
+bool MidiExport::tryExport( const TrackContainer::TrackList &tracks, int tempo, const QString &filename ) | |
+{ | |
+ QFile f(filename); | |
+ f.open(QIODevice::WriteOnly); | |
+ QDataStream midiout(&f); | |
+ | |
+ InstrumentTrack* instTrack; | |
+ QDomElement element; | |
+ | |
+ | |
+ int nTracks = 0; | |
+ const int BUFFER_SIZE = 50*1024; | |
+ uint8_t buffer[BUFFER_SIZE]; | |
+ uint32_t size; | |
+ | |
+ foreach( Track* track, tracks ) if( track->type() == Track::InstrumentTrack ) nTracks++; | |
+ | |
+ // midi header | |
+ MidiFile::MIDIHeader header(nTracks); | |
+ size = header.writeToBuffer(buffer); | |
+ midiout.writeRawData((char *)buffer, size); | |
+ | |
+ // midi tracks | |
+ foreach( Track* track, tracks ) | |
+ { | |
+ DataFile dataFile( DataFile::SongProject ); | |
+ MidiFile::MIDITrack<BUFFER_SIZE> mtrack; | |
+ | |
+ if( track->type() != Track::InstrumentTrack ) continue; | |
+ | |
+ //qDebug() << "exporting " << track->name(); | |
+ | |
+ | |
+ mtrack.addName(track->name().toStdString(), 0); | |
+ //mtrack.addProgramChange(0, 0); | |
+ mtrack.addTempo(tempo, 0); | |
+ | |
+ instTrack = dynamic_cast<InstrumentTrack *>( track ); | |
+ element = instTrack->saveState( dataFile, dataFile.content() ); | |
+ | |
+ // instrumentTrack | |
+ // - instrumentTrack | |
+ // - pattern | |
+ int base_pitch = 0; | |
+ double base_volume = 1.0; | |
+ int base_time = 0; | |
+ | |
+ | |
+ for(QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) | |
+ { | |
+ //QDomText txt = n.toText(); | |
+ //qDebug() << ">> child node " << n.nodeName(); | |
+ | |
+ if (n.nodeName() == "instrumenttrack") | |
+ { | |
+ // TODO interpret pan="0" fxch="0" usemasterpitch="1" pitchrange="1" pitch="0" basenote="57" | |
+ QDomElement it = n.toElement(); | |
+ base_pitch = it.attribute("pitch", "0").toInt(); | |
+ base_volume = it.attribute("volume", "100").toDouble()/100.0; | |
+ } | |
+ | |
+ if (n.nodeName() == "pattern") | |
+ { | |
+ base_time = n.toElement().attribute("pos", "0").toInt(); | |
+ // TODO interpret steps="12" muted="0" type="1" name="Piano1" len="2592" | |
+ for(QDomNode nn = n.firstChild(); !nn.isNull(); nn = nn.nextSibling()) | |
+ { | |
+ QDomElement note = nn.toElement(); | |
+ if (note.attribute("len", "0") == "0" || note.attribute("vol", "0") == "0") continue; | |
+ #if 0 | |
+ qDebug() << ">>>> key " << note.attribute( "key", "0" ) | |
+ << " " << note.attribute("len", "0") << " @" | |
+ << note.attribute("pos", "0"); | |
+ #endif | |
+ mtrack.addNote( | |
+ note.attribute("key", "0").toInt()+base_pitch | |
+ , 100 * base_volume * (note.attribute("vol", "100").toDouble()/100) | |
+ , (base_time+note.attribute("pos", "0").toDouble())/48 | |
+ , (note.attribute("len", "0")).toDouble()/48); | |
+ } | |
+ } | |
+ | |
+ } | |
+ size = mtrack.writeToBuffer(buffer); | |
+ midiout.writeRawData((char *)buffer, size); | |
+ } // for each track | |
+ | |
+ return true; | |
+ | |
+} | |
+ | |
+ | |
+ | |
+ | |
+void MidiExport::error() | |
+{ | |
+ //qDebug() << "MidiExport error: " << m_error ; | |
+} | |
+ | |
+ | |
+ | |
+extern "C" | |
+{ | |
+ | |
+// necessary for getting instance out of shared lib | |
+Plugin * PLUGIN_EXPORT lmms_plugin_main( Model *, void * _data ) | |
+{ | |
+ return new MidiExport(); | |
+} | |
+ | |
+ | |
+} | |
+ | |
diff --git a/plugins/MidiExport/MidiExport.h b/plugins/MidiExport/MidiExport.h | |
new file mode 100644 | |
index 0000000..d829a8b | |
--- /dev/null | |
+++ b/plugins/MidiExport/MidiExport.h | |
@@ -0,0 +1,58 @@ | |
+/* | |
+ * MidiExport.h - support for Exporting MIDI-files | |
+ * | |
+ * Author: Mohamed Abdel Maksoud <mohamed at amaksoud.com> | |
+ * | |
+ * This file is part of LMMS - http://lmms.io | |
+ * | |
+ * 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. | |
+ * | |
+ * This program is distributed in the hope that it will be useful, | |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
+ * General Public License for more details. | |
+ * | |
+ * You should have received a copy of the GNU General Public | |
+ * License along with this program (see COPYING); if not, write to the | |
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
+ * Boston, MA 02110-1301 USA. | |
+ * | |
+ */ | |
+ | |
+#ifndef _MIDI_EXPORT_H | |
+#define _MIDI_EXPORT_H | |
+ | |
+#include <QString> | |
+ | |
+#include "ExportFilter.h" | |
+#include "MidiFile.hpp" | |
+ | |
+ | |
+ | |
+class MidiExport: public ExportFilter | |
+{ | |
+// Q_OBJECT | |
+public: | |
+ MidiExport( ); | |
+ ~MidiExport(); | |
+ | |
+ virtual PluginView * instantiateView( QWidget * ) | |
+ { | |
+ return( NULL ); | |
+ } | |
+ | |
+ virtual bool tryExport( const TrackContainer::TrackList &tracks, int tempo, const QString &filename ); | |
+ | |
+private: | |
+ | |
+ | |
+ void error( void ); | |
+ | |
+ | |
+} ; | |
+ | |
+ | |
+#endif | |
diff --git a/plugins/MidiExport/MidiFile.hpp b/plugins/MidiExport/MidiFile.hpp | |
new file mode 100644 | |
index 0000000..33955e1 | |
--- /dev/null | |
+++ b/plugins/MidiExport/MidiFile.hpp | |
@@ -0,0 +1,320 @@ | |
+#ifndef MIDIFILE_HPP | |
+#define MIDIFILE_HPP | |
+ | |
+/** | |
+ * Name: MidiFile.hpp | |
+ * Purpose: C++ re-write of the python module MidiFile.py | |
+ * Author: Mohamed Abdel Maksoud <mohamed at amaksoud.com> | |
+ *----------------------------------------------------------------------------- | |
+ * Name: MidiFile.py | |
+ * Purpose: MIDI file manipulation utilities | |
+ * | |
+ * Author: Mark Conway Wirt <emergentmusics) at (gmail . com> | |
+ * | |
+ * Created: 2008/04/17 | |
+ * Copyright: (c) 2009 Mark Conway Wirt | |
+ * License: Please see License.txt for the terms under which this | |
+ * software is distributed. | |
+ *----------------------------------------------------------------------------- | |
+ */ | |
+ | |
+#include <string.h> | |
+#include <stdint.h> | |
+#include <string> | |
+#include <vector> | |
+#include <set> | |
+#include <algorithm> | |
+#include <assert.h> | |
+ | |
+using std::string; | |
+using std::vector; | |
+using std::set; | |
+ | |
+namespace MidiFile | |
+{ | |
+ | |
+const int TICKSPERBEAT = 128; | |
+ | |
+ | |
+int writeVarLength(uint32_t val, uint8_t *buffer) | |
+{ | |
+ /* | |
+ Accept an input, and write a MIDI-compatible variable length stream | |
+ | |
+ The MIDI format is a little strange, and makes use of so-called variable | |
+ length quantities. These quantities are a stream of bytes. If the most | |
+ significant bit is 1, then more bytes follow. If it is zero, then the | |
+ byte in question is the last in the stream | |
+ */ | |
+ int size = 0; | |
+ uint8_t result, little_endian[4]; | |
+ result = val & 0x7F; | |
+ little_endian[size++] = result; | |
+ val = val >> 7; | |
+ while (val > 0) | |
+ { | |
+ result = val & 0x7F; | |
+ result = result | 0x80; | |
+ little_endian[size++] = result; | |
+ val = val >> 7; | |
+ } | |
+ for (int i=0; i<size; i++) | |
+ { | |
+ buffer[i] = little_endian[size-i-1]; | |
+ } | |
+ | |
+ return size; | |
+} | |
+ | |
+int writeBigEndian4(uint32_t val, uint8_t *buf) | |
+{ | |
+ buf[0] = val >> 24; | |
+ buf[1] = val >> 16 & 0xff; | |
+ buf[2] = val >> 8 & 0xff; | |
+ buf[3] = val & 0xff; | |
+ return 4; | |
+} | |
+ | |
+int writeBigEndian2(uint16_t val, uint8_t *buf) | |
+{ | |
+ buf[0] = val >> 8 & 0xff; | |
+ buf[1] = val & 0xff; | |
+ return 2; | |
+} | |
+ | |
+ | |
+class MIDIHeader | |
+{ | |
+ // Class to encapsulate the MIDI header structure. | |
+ uint16_t numTracks; | |
+ uint16_t ticksPerBeat; | |
+ | |
+ public: | |
+ | |
+ MIDIHeader(uint16_t nTracks, uint16_t ticksPB=TICKSPERBEAT): numTracks(nTracks), ticksPerBeat(ticksPB) {} | |
+ | |
+ inline int writeToBuffer(uint8_t *buffer, int start=0) const | |
+ { | |
+ // chunk ID | |
+ buffer[start++] = 'M'; buffer[start++] = 'T'; buffer[start++] = 'h'; buffer[start++] = 'd'; | |
+ // chunk size (6 bytes always) | |
+ buffer[start++] = 0; buffer[start++] = 0; buffer[start++] = 0; buffer[start++] = 0x06; | |
+ // format: 1 (multitrack) | |
+ buffer[start++] = 0; buffer[start++] = 0x01; | |
+ | |
+ start += writeBigEndian2(numTracks, buffer+start); | |
+ | |
+ start += writeBigEndian2(ticksPerBeat, buffer+start); | |
+ | |
+ return start; | |
+ } | |
+ | |
+}; | |
+ | |
+ | |
+struct Event | |
+{ | |
+ uint32_t time; | |
+ uint32_t tempo; | |
+ string trackName; | |
+ enum {NOTE_ON, NOTE_OFF, TEMPO, PROG_CHANGE, TRACK_NAME} type; | |
+ // TODO make a union to save up space | |
+ uint8_t pitch; | |
+ uint8_t programNumber; | |
+ uint8_t duration; | |
+ uint8_t volume; | |
+ uint8_t channel; | |
+ | |
+ Event() {time=tempo=pitch=programNumber=duration=volume=channel=0; trackName="";} | |
+ | |
+ inline int writeToBuffer(uint8_t *buffer) const | |
+ { | |
+ uint8_t code, fourbytes[4]; | |
+ int size=0; | |
+ switch (type) | |
+ { | |
+ case NOTE_ON: | |
+ code = 0x9 << 4 | channel; | |
+ size += writeVarLength(time, buffer+size); | |
+ buffer[size++] = code; | |
+ buffer[size++] = pitch; | |
+ buffer[size++] = volume; | |
+ break; | |
+ case NOTE_OFF: | |
+ code = 0x8 << 4 | channel; | |
+ size += writeVarLength(time, buffer+size); | |
+ buffer[size++] = code; | |
+ buffer[size++] = pitch; | |
+ buffer[size++] = volume; | |
+ break; | |
+ case TEMPO: | |
+ code = 0xFF; | |
+ size += writeVarLength(time, buffer+size); | |
+ buffer[size++] = code; | |
+ buffer[size++] = 0x51; | |
+ buffer[size++] = 0x03; | |
+ writeBigEndian4(int(60000000.0 / tempo), fourbytes); | |
+ | |
+ //printf("tempo of %x translates to ", tempo); | |
+ for (int i=0; i<3; i++) printf("%02x ", fourbytes[i+1]); | |
+ printf("\n"); | |
+ buffer[size++] = fourbytes[1]; | |
+ buffer[size++] = fourbytes[2]; | |
+ buffer[size++] = fourbytes[3]; | |
+ break; | |
+ case PROG_CHANGE: | |
+ code = 0xC << 4 | channel; | |
+ size += writeVarLength(time, buffer+size); | |
+ buffer[size++] = code; | |
+ buffer[size++] = programNumber; | |
+ break; | |
+ case TRACK_NAME: | |
+ size += writeVarLength(time, buffer+size); | |
+ buffer[size++] = 0xFF; | |
+ buffer[size++] = 0x03; | |
+ size += writeVarLength(trackName.size(), buffer+size); | |
+ trackName.copy((char *)(&buffer[size]), trackName.size()); | |
+ size += trackName.size(); | |
+// buffer[size++] = '\0'; | |
+// buffer[size++] = '\0'; | |
+ | |
+ break; | |
+ } | |
+ return size; | |
+ } // writeEventsToBuffer | |
+ | |
+ | |
+ // events are sorted by their time | |
+ inline bool operator < (const Event& b) const { | |
+ if (this->time < b.time) return true; | |
+#if 0 | |
+ if (this->type < b.type) return true; | |
+ if (this->pitch < b.pitch) return true; | |
+ if (this->duration < b.duration) return true; | |
+ if (this->volume < b.volume) return true; | |
+ #if 1 | |
+ if (this->programNumber < b.programNumber) return true; | |
+ if (this->channel < b.channel) return true; | |
+ if (this->trackName < b.trackName) return true; | |
+ #endif | |
+#endif | |
+ return false; | |
+ } | |
+}; | |
+ | |
+template<const int MAX_TRACK_SIZE> | |
+class MIDITrack | |
+{ | |
+ // A class that encapsulates a MIDI track | |
+ // Nested class definitions. | |
+ vector<Event> events; | |
+ | |
+ public: | |
+ uint8_t channel; | |
+ | |
+ MIDITrack(): channel(0) {} | |
+ | |
+ inline void addEvent(const Event &e) | |
+ { | |
+ Event E = e; | |
+ events.push_back(E); | |
+ } | |
+ | |
+ inline void addNote(uint8_t pitch, uint8_t volume, double time, double duration) | |
+ { | |
+ Event event; event.channel = channel; | |
+ event.volume = volume; | |
+ | |
+ event.type = Event::NOTE_ON; event.pitch = pitch; event.time= (uint32_t) (time * TICKSPERBEAT); | |
+ addEvent(event); | |
+ | |
+ event.type = Event::NOTE_OFF; event.pitch = pitch; event.time=(uint32_t) ((time+duration) * TICKSPERBEAT); | |
+ addEvent(event); | |
+ | |
+ //printf("note: %d-%d\n", (uint32_t) time * TICKSPERBEAT, (uint32_t)((time+duration) * TICKSPERBEAT)); | |
+ } | |
+ | |
+ inline void addName(const string &name, uint32_t time) | |
+ { | |
+ Event event; event.channel = channel; | |
+ event.type = Event::TRACK_NAME; event.time=time; event.trackName = name; | |
+ addEvent(event); | |
+ } | |
+ | |
+ inline void addProgramChange(uint8_t prog, uint32_t time) | |
+ { | |
+ Event event; event.channel = channel; | |
+ event.type = Event::PROG_CHANGE; event.time=time; event.programNumber = prog; | |
+ addEvent(event); | |
+ } | |
+ | |
+ inline void addTempo(uint8_t tempo, uint32_t time) | |
+ { | |
+ Event event; event.channel = channel; | |
+ event.type = Event::TEMPO; event.time=time; event.tempo = tempo; | |
+ addEvent(event); | |
+ } | |
+ | |
+ inline int writeMIDIToBuffer(uint8_t *buffer, int start=0) const | |
+ { | |
+ // Write the meta data and note data to the packed MIDI stream. | |
+ // Process the events in the eventList | |
+ | |
+ start += writeEventsToBuffer(buffer, start); | |
+ | |
+ // Write MIDI close event. | |
+ buffer[start++] = 0x00; | |
+ buffer[start++] = 0xFF; | |
+ buffer[start++] = 0x2F; | |
+ buffer[start++] = 0x00; | |
+ | |
+ // return the entire length of the data and write to the header | |
+ | |
+ return start; | |
+ } | |
+ | |
+ inline int writeEventsToBuffer(uint8_t *buffer, int start=0) const | |
+ { | |
+ // Write the events in MIDIEvents to the MIDI stream. | |
+ vector<Event> _events = events; | |
+ std::sort(_events.begin(), _events.end()); | |
+ vector<Event>::const_iterator it; | |
+ uint32_t time_last = 0, tmp; | |
+ for (it = _events.begin(); it!=_events.end(); ++it) | |
+ { | |
+ Event e = *it; | |
+ if (e.time < time_last){ | |
+ printf("error: e.time=%d time_last=%d\n", e.time, time_last); | |
+ assert(false); | |
+ } | |
+ tmp = e.time; | |
+ e.time -= time_last; | |
+ time_last = tmp; | |
+ start += e.writeToBuffer(buffer+start); | |
+ if (start >= MAX_TRACK_SIZE) { | |
+ break; | |
+ } | |
+ } | |
+ return start; | |
+ } | |
+ | |
+ inline int writeToBuffer(uint8_t *buffer, int start=0) const | |
+ { | |
+ uint8_t eventsBuffer[MAX_TRACK_SIZE]; | |
+ uint32_t events_size = writeMIDIToBuffer(eventsBuffer); | |
+ //printf(">> track %lu events took 0x%x bytes\n", events.size(), events_size); | |
+ | |
+ // chunk ID | |
+ buffer[start++] = 'M'; buffer[start++] = 'T'; buffer[start++] = 'r'; buffer[start++] = 'k'; | |
+ // chunk size | |
+ start += writeBigEndian4(events_size, buffer+start); | |
+ // copy events data | |
+ memmove(buffer+start, eventsBuffer, events_size); | |
+ start += events_size; | |
+ return start; | |
+ } | |
+}; | |
+ | |
+}; // namespace | |
+ | |
+#endif | |
diff --git a/src/core/Song.cpp b/src/core/Song.cpp | |
index 7198416..5a3b762 100644 | |
--- a/src/core/Song.cpp | |
+++ b/src/core/Song.cpp | |
@@ -47,6 +47,7 @@ | |
#include "FxMixerView.h" | |
#include "GuiApplication.h" | |
#include "ImportFilter.h" | |
+#include "ExportFilter.h" | |
#include "InstrumentTrack.h" | |
#include "MainWindow.h" | |
#include "FileDialog.h" | |
@@ -1275,6 +1276,63 @@ void Song::exportProject(bool multiExport) | |
} | |
+void Song::exportProjectMidi() | |
+{ | |
+ if( isEmpty() ) | |
+ { | |
+ QMessageBox::information( gui->mainWindow(), | |
+ tr( "Empty project" ), | |
+ tr( "This project is empty so exporting makes " | |
+ "no sense. Please put some items into " | |
+ "Song Editor first!" ) ); | |
+ return; | |
+ } | |
+ | |
+ FileDialog efd( gui->mainWindow() ); | |
+ | |
+ efd.setFileMode( FileDialog::AnyFile ); | |
+ | |
+ QStringList types; | |
+ types << tr("MIDI File (*.mid)"); | |
+ efd.setNameFilters( types ); | |
+ QString base_filename; | |
+ if( !m_fileName.isEmpty() ) | |
+ { | |
+ efd.setDirectory( QFileInfo( m_fileName ).absolutePath() ); | |
+ base_filename = QFileInfo( m_fileName ).completeBaseName(); | |
+ } | |
+ else | |
+ { | |
+ efd.setDirectory( ConfigManager::inst()->userProjectsDir() ); | |
+ base_filename = tr( "untitled" ); | |
+ } | |
+ efd.selectFile( base_filename + ".mid" ); | |
+ efd.setWindowTitle( tr( "Select file for project-export..." ) ); | |
+ | |
+ efd.setAcceptMode( FileDialog::AcceptSave ); | |
+ | |
+ | |
+ if( efd.exec() == QDialog::Accepted && !efd.selectedFiles().isEmpty() && !efd.selectedFiles()[0].isEmpty() ) | |
+ { | |
+ const QString suffix = ".mid"; | |
+ | |
+ const QString export_filename = efd.selectedFiles()[0] + suffix; | |
+ | |
+ // NOTE start midi export | |
+ | |
+ // instantiate midi export plugin | |
+ TrackContainer::TrackList tracks; | |
+ tracks += Engine::getSong()->tracks(); | |
+ tracks += Engine::getBBTrackContainer()->tracks(); | |
+ ExportFilter *exf = dynamic_cast<ExportFilter *> (Plugin::instantiate("midiexport", NULL, NULL)); | |
+ if (exf==NULL) { | |
+ qDebug() << "failed to load midi export filter!"; | |
+ return; | |
+ } | |
+ exf->tryExport(tracks, Engine::getSong()->getTempo(), export_filename); | |
+ } | |
+} | |
+ | |
void Song::updateFramesPerTick() | |
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp | |
index b8ddd60..b37c3a7 100644 | |
--- a/src/gui/MainWindow.cpp | |
+++ b/src/gui/MainWindow.cpp | |
@@ -273,6 +273,12 @@ void MainWindow::finalize() | |
SLOT( exportProjectTracks() ), | |
Qt::CTRL + Qt::SHIFT + Qt::Key_E ); | |
+ project_menu->addAction( embed::getIconPixmap( "midi_file" ), | |
+ tr( "E&xport MIDI..." ), | |
+ Engine::getSong(), | |
+ SLOT( exportProjectMidi() ), | |
+ Qt::CTRL + Qt::Key_M ); | |
+ | |
project_menu->addSeparator(); | |
project_menu->addAction( embed::getIconPixmap( "exit" ), tr( "&Quit" ), | |
qApp, SLOT( closeAllWindows() ), |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment