From 1ea8ba51a9e706de8d0aafd93188803c6df32256 Mon Sep 17 00:00:00 2001 From: Hyunin Song Date: Wed, 19 Jul 2017 01:55:59 +0900 Subject: [PATCH 1/8] Re-enable midi export --- plugins/CMakeLists.txt | 2 +- src/gui/MainWindow.cpp | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 8a464e3886d..24c15e391bd 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -56,7 +56,7 @@ IF("${PLUGIN_LIST}" STREQUAL "") LadspaEffect lb302 MidiImport - # MidiExport - temporarily disabled, MIDI export is broken + MidiExport MultitapEcho monstro nes diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 8c2898da382..d11b6a29717 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -300,12 +300,11 @@ void MainWindow::finalize() SLOT( exportProjectTracks() ), Qt::CTRL + Qt::SHIFT + Qt::Key_E ); - // temporarily disabled broken MIDI export - /*project_menu->addAction( embed::getIconPixmap( "midi_file" ), + project_menu->addAction( embed::getIconPixmap( "midi_file" ), tr( "Export &MIDI..." ), Engine::getSong(), SLOT( exportProjectMidi() ), - Qt::CTRL + Qt::Key_M );*/ + Qt::CTRL + Qt::Key_M ); // Prevent dangling separator at end of menu per https://bugreports.qt.io/browse/QTBUG-40071 #if !(defined(LMMS_BUILD_APPLE) && (QT_VERSION >= 0x050000) && (QT_VERSION < 0x050600)) From 66a876bbf89630d47272f1b3d29b4252a89c1bba Mon Sep 17 00:00:00 2001 From: Hyunin Song Date: Wed, 19 Jul 2017 08:23:34 +0900 Subject: [PATCH 2/8] Modify the argument lists of tryExport(). --- include/ExportFilter.h | 3 ++- plugins/MidiExport/MidiExport.cpp | 43 ++++++++++++++++++------------- plugins/MidiExport/MidiExport.h | 7 +++-- src/core/Song.cpp | 7 ++--- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/include/ExportFilter.h b/include/ExportFilter.h index f27bc0c8295..a3b59f8e8b9 100644 --- a/include/ExportFilter.h +++ b/include/ExportFilter.h @@ -39,7 +39,8 @@ class EXPORT ExportFilter : public Plugin virtual ~ExportFilter() {} - virtual bool tryExport( const TrackContainer::TrackList &tracks, int tempo, const QString &filename ) = 0; + virtual bool tryExport(const TrackContainer::TrackList &tracks, + const TrackContainer::TrackList &tracksBB, int tempo, const QString &filename ) = 0; protected: virtual void saveSettings( QDomDocument &, QDomElement & ) diff --git a/plugins/MidiExport/MidiExport.cpp b/plugins/MidiExport/MidiExport.cpp index b838353d2c2..183be99bfef 100644 --- a/plugins/MidiExport/MidiExport.cpp +++ b/plugins/MidiExport/MidiExport.cpp @@ -68,7 +68,8 @@ MidiExport::~MidiExport() -bool MidiExport::tryExport( const TrackContainer::TrackList &tracks, int tempo, const QString &filename ) +bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, + const TrackContainer::TrackList &tracks_BB, int tempo, const QString &filename) { QFile f(filename); f.open(QIODevice::WriteOnly); @@ -79,7 +80,6 @@ bool MidiExport::tryExport( const TrackContainer::TrackList &tracks, int tempo, int nTracks = 0; - const int BUFFER_SIZE = 50*1024; uint8_t buffer[BUFFER_SIZE]; uint32_t size; @@ -132,22 +132,8 @@ bool MidiExport::tryExport( const TrackContainer::TrackList &tracks, int tempo, 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); - } + + writePattern(mtrack, n, base_pitch, base_volume, base_time); } } @@ -161,6 +147,27 @@ bool MidiExport::tryExport( const TrackContainer::TrackList &tracks, int tempo, +void MidiExport::writePattern(MidiFile::MIDITrack &mtrack, QDomNode n, int base_pitch, double base_volume, int base_time) +{ + // 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); + } +} + + void MidiExport::error() { diff --git a/plugins/MidiExport/MidiExport.h b/plugins/MidiExport/MidiExport.h index 279f369f6b3..27c07f053e9 100644 --- a/plugins/MidiExport/MidiExport.h +++ b/plugins/MidiExport/MidiExport.h @@ -31,6 +31,7 @@ #include "MidiFile.hpp" +const int BUFFER_SIZE = 50*1024; class MidiExport: public ExportFilter { @@ -44,10 +45,12 @@ class MidiExport: public ExportFilter return( NULL ); } - virtual bool tryExport( const TrackContainer::TrackList &tracks, int tempo, const QString &filename ); + virtual bool tryExport(const TrackContainer::TrackList &tracks, + const TrackContainer::TrackList &tracks_BB, int tempo, const QString &filename); private: - + void writePattern(MidiFile::MIDITrack & mtrack, QDomNode n, + int base_pitch, double base_volume, int base_time); void error( void ); diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 75b5adff689..6028d3583f6 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -1445,14 +1445,15 @@ void Song::exportProjectMidi() // instantiate midi export plugin TrackContainer::TrackList tracks; - tracks += Engine::getSong()->tracks(); - tracks += Engine::getBBTrackContainer()->tracks(); + TrackContainer::TrackList tracks_BB; + tracks = Engine::getSong()->tracks(); + tracks_BB = Engine::getBBTrackContainer()->tracks(); ExportFilter *exf = dynamic_cast (Plugin::instantiate("midiexport", NULL, NULL)); if (exf==NULL) { qDebug() << "failed to load midi export filter!"; return; } - exf->tryExport(tracks, Engine::getSong()->getTempo(), export_filename); + exf->tryExport(tracks, tracks_BB, Engine::getSong()->getTempo(), export_filename); } } From 25afaddff62b161ff7ec488842b621fc1da5f0f7 Mon Sep 17 00:00:00 2001 From: Hyunin Song Date: Fri, 28 Jul 2017 16:09:05 +0900 Subject: [PATCH 3/8] Fix MIDI export --- plugins/MidiExport/MidiExport.cpp | 248 ++++++++++++++++++++++++------ plugins/MidiExport/MidiExport.h | 41 ++++- plugins/MidiExport/MidiFile.hpp | 5 +- 3 files changed, 239 insertions(+), 55 deletions(-) diff --git a/plugins/MidiExport/MidiExport.cpp b/plugins/MidiExport/MidiExport.cpp index 183be99bfef..5c04d355ffa 100644 --- a/plugins/MidiExport/MidiExport.cpp +++ b/plugins/MidiExport/MidiExport.cpp @@ -1,7 +1,8 @@ /* - * MidiExport.cpp - support for importing MIDI files + * MidiExport.cpp - support for Exporting MIDI files * - * Author: Mohamed Abdel Maksoud + * Copyright (c) 2015 Mohamed Abdel Maksoud + * Copyright (c) 2017 Hyunjin Song * * This file is part of LMMS - https://lmms.io * @@ -30,8 +31,10 @@ #include #include "MidiExport.h" -#include "Engine.h" + +#include "lmms_math.h" #include "TrackContainer.h" +#include "BBTrack.h" #include "InstrumentTrack.h" @@ -76,94 +79,247 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, QDataStream midiout(&f); InstrumentTrack* instTrack; + BBTrack* bbTrack; QDomElement element; int nTracks = 0; + //int nTracks_BB = 0; uint8_t buffer[BUFFER_SIZE]; uint32_t size; - for( const Track* track : tracks ) if( track->type() == Track::InstrumentTrack ) nTracks++; + for(const Track* track : tracks) if (track->type() == Track::InstrumentTrack) nTracks++; + for(const Track* track : tracks_BB) if (track->type() == Track::InstrumentTrack) nTracks++; // midi header MidiFile::MIDIHeader header(nTracks); size = header.writeToBuffer(buffer); midiout.writeRawData((char *)buffer, size); + std::vector>> plists; + // midi tracks for( Track* track : tracks ) { - DataFile dataFile( DataFile::SongProject ); - MidiFile::MIDITrack mtrack; - - if( track->type() != Track::InstrumentTrack ) continue; - - //qDebug() << "exporting " << track->name(); - - + DataFile dataFile(DataFile::SongProject); + MTrack mtrack; + + if (track->type() == Track::InstrumentTrack) + { + + mtrack.addName(track->name().toStdString(), 0); + //mtrack.addProgramChange(0, 0); + mtrack.addTempo(tempo, 0); + + instTrack = dynamic_cast(track); + element = instTrack->saveState(dataFile, dataFile.content()); + + int base_pitch = 0; + double base_volume = 1.0; + int base_time = 0; + + MidiNoteVector pat; + + for(QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) + { + + if (n.nodeName() == "instrumenttrack") + { + 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(); + writePattern(pat, n, base_pitch, base_volume, base_time); + } + + } + ProcessBBNotes(pat); + writePatternToTrack(mtrack, pat); + size = mtrack.writeToBuffer(buffer); + midiout.writeRawData((char *)buffer, size); + } + + if (track->type() == Track::BBTrack) + { + bbTrack = dynamic_cast(track); + element = bbTrack->saveState(dataFile, dataFile.content()); + + std::vector> plist; + for(QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) + { + + if (n.nodeName() == "bbtco") + { + QDomElement it = n.toElement(); + int pos = it.attribute("pos", "0").toInt(); + int len = it.attribute("len", "0").toInt(); + plist.push_back(std::pair(pos, pos+len)); + } + } + std::sort(plist.begin(), plist.end()); + plists.push_back(plist); + + } + } // for each track + + // midi tracks in BB tracks + for( Track* track : tracks_BB ) + { + DataFile dataFile(DataFile::SongProject); + MTrack mtrack; + + std::vector>>::iterator itr = plists.begin(); + std::vector> st; + + if(track->type() != Track::InstrumentTrack) continue; + mtrack.addName(track->name().toStdString(), 0); //mtrack.addProgramChange(0, 0); mtrack.addTempo(tempo, 0); - - instTrack = dynamic_cast( track ); - element = instTrack->saveState( dataFile, dataFile.content() ); - - // instrumentTrack - // - instrumentTrack - // - pattern + + instTrack = dynamic_cast(track); + element = instTrack->saveState(dataFile, dataFile.content()); + 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_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(); - - writePattern(mtrack, n, base_pitch, base_volume, base_time); + std::vector> &plist = *itr; + + MidiNoteVector nv, pat; + writePattern(pat, n, base_pitch, base_volume, 0); + + // workaround for nested BBTCOs + int pos = 0; + int len = n.toElement().attribute("steps", "1").toInt() * 12; + for(std::vector>::iterator it = plist.begin(); it != plist.end(); ++it) + { + while(!st.empty() && st.back().second <= it->first) + { + writeBBPattern(pat, nv, len, st.back().first, pos, st.back().second); + pos = st.back().second; + st.pop_back(); + } + + if(!st.empty() && st.back().second <= it->second) + { + writeBBPattern(pat, nv, len, st.back().first, pos, it->first); + pos = it->first; + while(!st.empty() && st.back().second <= it->second) + { + st.pop_back(); + } + } + + st.push_back(*it); + pos = it->first; + } + + while(!st.empty()) + { + writeBBPattern(pat, nv, len, st.back().first, pos, st.back().second); + pos = st.back().second; + st.pop_back(); + } + + ProcessBBNotes(nv); + writePatternToTrack(mtrack, nv); + ++itr; } - } size = mtrack.writeToBuffer(buffer); midiout.writeRawData((char *)buffer, size); - } // for each track - + } + return true; } -void MidiExport::writePattern(MidiFile::MIDITrack &mtrack, QDomNode n, int base_pitch, double base_volume, int base_time) +void MidiExport::writePattern(MidiNoteVector &pat, QDomNode n, + int base_pitch, double base_volume, int base_time) { // 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); + if (note.attribute("len", "0") == "0") continue; + // TODO interpret pan="0" fxch="0" usemasterpitch="1" pitchrange="1" pitch="0" basenote="57" + MidiNote mnote; + mnote.pitch = note.attribute("key", "0").toInt() + base_pitch; + mnote.volume = qMin(qRound(base_volume * note.attribute("vol", "100").toDouble()), 127); + mnote.time = base_time + note.attribute("pos", "0").toInt(); + mnote.duration = note.attribute("len", "0").toInt(); + pat.push_back(mnote); + } +} + + + +void MidiExport::writePatternToTrack(MTrack &mtrack, MidiNoteVector &nv) +{ + for(MidiNoteIterator it = nv.begin(); it != nv.end(); ++it) + { + mtrack.addNote(it->pitch, it->volume, it->time / 48.0, it->duration / 48.0); + } +} + + + +void MidiExport::writeBBPattern(MidiNoteVector &src, MidiNoteVector &dst, + int len, int base, int start, int end) +{ + if (start >= end) { return; } + start -= base; + end -= base; + std::sort(src.begin(), src.end()); + for(MidiNoteIterator it = src.begin(); it != src.end(); ++it) + { + for(int time = it->time + ceil((start - it->time) / len) + * len; time < end; time += len) + { + MidiNote note; + note.duration = it->duration; + note.pitch = it->pitch; + note.time = base + time; + note.volume = it->volume; + dst.push_back(note); + } + } +} + + + +void MidiExport::ProcessBBNotes(MidiNoteVector &nv) +{ + std::sort(nv.begin(), nv.end()); + int cur = INT_MAX, next = INT_MAX; + for(MidiNoteVector::reverse_iterator it = nv.rbegin(); it != nv.rend(); ++it) + { + if (it->time < cur) + { + next = cur; + cur = it->time; + } + if(it->duration < 0) + { + it->duration = qMin(-it->duration, next - cur); + } } } diff --git a/plugins/MidiExport/MidiExport.h b/plugins/MidiExport/MidiExport.h index 27c07f053e9..ebcc3c3e5a8 100644 --- a/plugins/MidiExport/MidiExport.h +++ b/plugins/MidiExport/MidiExport.h @@ -1,7 +1,8 @@ /* * MidiExport.h - support for Exporting MIDI-files * - * Author: Mohamed Abdel Maksoud + * Copyright (c) 2015 Mohamed Abdel Maksoud + * Copyright (c) 2017 Hyunjin Song * * This file is part of LMMS - https://lmms.io * @@ -32,27 +33,51 @@ const int BUFFER_SIZE = 50*1024; +typedef MidiFile::MIDITrack MTrack; + +struct MidiNote +{ + int time; + uint8_t pitch; + int duration; + uint8_t volume; + + inline bool operator<(const MidiNote &b) const + { + return this->time < b.time; + } +} ; + +typedef std::vector MidiNoteVector; +typedef std::vector::iterator MidiNoteIterator; + + class MidiExport: public ExportFilter { // Q_OBJECT public: - MidiExport( ); + MidiExport(); ~MidiExport(); - virtual PluginView * instantiateView( QWidget * ) + virtual PluginView *instantiateView(QWidget *) { - return( NULL ); + return nullptr; } virtual bool tryExport(const TrackContainer::TrackList &tracks, - const TrackContainer::TrackList &tracks_BB, int tempo, const QString &filename); + const TrackContainer::TrackList &tracks_BB, + int tempo, const QString &filename); private: - void writePattern(MidiFile::MIDITrack & mtrack, QDomNode n, - int base_pitch, double base_volume, int base_time); + void writePattern(MidiNoteVector &pat, QDomNode n, + int base_pitch, double base_volume, int base_time); + void writePatternToTrack(MTrack &mtrack, MidiNoteVector &nv); + void writeBBPattern(MidiNoteVector &src, MidiNoteVector &dst, + int len, int base, int start, int end); + void ProcessBBNotes(MidiNoteVector &nv); - void error( void ); + void error(); } ; diff --git a/plugins/MidiExport/MidiFile.hpp b/plugins/MidiExport/MidiFile.hpp index 0e2bfbe5bd7..a1f91de2fea 100644 --- a/plugins/MidiExport/MidiFile.hpp +++ b/plugins/MidiExport/MidiFile.hpp @@ -156,8 +156,10 @@ struct Event 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]; @@ -186,7 +188,8 @@ struct Event // events are sorted by their time inline bool operator < (const Event& b) const { - return this->time < b.time; + return this->time < b.time || + (this->time == b.time && this->type > b.type); } }; From 349615d800f1a81f91880bab43b33a633317fa4a Mon Sep 17 00:00:00 2001 From: Hyunin Song Date: Mon, 31 Jul 2017 17:21:41 +0900 Subject: [PATCH 4/8] Consider master pitch, base note, instrument pitch in MIDI export. --- include/ExportFilter.h | 3 ++- plugins/MidiExport/MidiExport.cpp | 27 ++++++++++++++++++++------- plugins/MidiExport/MidiExport.h | 2 +- src/core/Song.cpp | 2 +- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/include/ExportFilter.h b/include/ExportFilter.h index a3b59f8e8b9..35416f49280 100644 --- a/include/ExportFilter.h +++ b/include/ExportFilter.h @@ -40,7 +40,8 @@ class EXPORT ExportFilter : public Plugin virtual bool tryExport(const TrackContainer::TrackList &tracks, - const TrackContainer::TrackList &tracksBB, int tempo, const QString &filename ) = 0; + const TrackContainer::TrackList &tracksBB, + int tempo, int masterPitch, const QString &filename ) = 0; protected: virtual void saveSettings( QDomDocument &, QDomElement & ) diff --git a/plugins/MidiExport/MidiExport.cpp b/plugins/MidiExport/MidiExport.cpp index 5c04d355ffa..60d0f438b1a 100644 --- a/plugins/MidiExport/MidiExport.cpp +++ b/plugins/MidiExport/MidiExport.cpp @@ -72,7 +72,8 @@ MidiExport::~MidiExport() bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, - const TrackContainer::TrackList &tracks_BB, int tempo, const QString &filename) + const TrackContainer::TrackList &tracks_BB, + int tempo, int masterPitch, const QString &filename) { QFile f(filename); f.open(QIODevice::WriteOnly); @@ -126,7 +127,13 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, if (n.nodeName() == "instrumenttrack") { QDomElement it = n.toElement(); - base_pitch = it.attribute("pitch", "0").toInt(); + base_pitch = qRound(it.attribute("pitch", "0").toInt() / 100.0); + // transpose +12 semitones, workaround for #1857 + base_pitch += (69 - it.attribute("basenote", "57").toInt()); + if (it.attribute("usemasterpitch", "1").toInt()) + { + base_pitch += masterPitch; + } base_volume = it.attribute("volume", "100").toDouble()/100.0; } @@ -184,7 +191,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, instTrack = dynamic_cast(track); element = instTrack->saveState(dataFile, dataFile.content()); - int base_pitch = 0; + int base_pitch = 0; double base_volume = 1.0; for(QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) @@ -192,8 +199,14 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, if (n.nodeName() == "instrumenttrack") { QDomElement it = n.toElement(); - base_pitch = it.attribute("pitch", "0").toInt(); - base_volume = it.attribute("volume", "100").toDouble()/100.0; + base_pitch = qRound(it.attribute("pitch", "0").toInt() / 100.0); + // transpose +12 semitones, workaround for #1857 + base_pitch += (69 - it.attribute("basenote", "57").toInt()); + if (it.attribute("usemasterpitch", "1").toInt()) + { + base_pitch += masterPitch; + } + base_volume = it.attribute("volume", "100").toDouble() / 100.0; } if (n.nodeName() == "pattern") @@ -259,9 +272,9 @@ void MidiExport::writePattern(MidiNoteVector &pat, QDomNode n, { QDomElement note = nn.toElement(); if (note.attribute("len", "0") == "0") continue; - // TODO interpret pan="0" fxch="0" usemasterpitch="1" pitchrange="1" pitch="0" basenote="57" + // TODO interpret pan="0" fxch="0" pitchrange="1" MidiNote mnote; - mnote.pitch = note.attribute("key", "0").toInt() + base_pitch; + mnote.pitch = qMax(0, qMin(127, note.attribute("key", "0").toInt() + base_pitch)); mnote.volume = qMin(qRound(base_volume * note.attribute("vol", "100").toDouble()), 127); mnote.time = base_time + note.attribute("pos", "0").toInt(); mnote.duration = note.attribute("len", "0").toInt(); diff --git a/plugins/MidiExport/MidiExport.h b/plugins/MidiExport/MidiExport.h index ebcc3c3e5a8..ea1f9f0efc2 100644 --- a/plugins/MidiExport/MidiExport.h +++ b/plugins/MidiExport/MidiExport.h @@ -67,7 +67,7 @@ class MidiExport: public ExportFilter virtual bool tryExport(const TrackContainer::TrackList &tracks, const TrackContainer::TrackList &tracks_BB, - int tempo, const QString &filename); + int tempo, int masterPitch, const QString &filename); private: void writePattern(MidiNoteVector &pat, QDomNode n, diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 6028d3583f6..927d07ee7d6 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -1453,7 +1453,7 @@ void Song::exportProjectMidi() qDebug() << "failed to load midi export filter!"; return; } - exf->tryExport(tracks, tracks_BB, Engine::getSong()->getTempo(), export_filename); + exf->tryExport(tracks, tracks_BB, getTempo(), m_masterPitchModel.value(), export_filename); } } From 369744e25fd4b7eec9b5830b7842bf1f75af1e64 Mon Sep 17 00:00:00 2001 From: Hyunin Song Date: Fri, 18 Aug 2017 16:34:53 +0900 Subject: [PATCH 5/8] Temporarily remove instrument pitch export, Code cleanup --- plugins/MidiExport/MidiExport.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/plugins/MidiExport/MidiExport.cpp b/plugins/MidiExport/MidiExport.cpp index 60d0f438b1a..0b047aae2b8 100644 --- a/plugins/MidiExport/MidiExport.cpp +++ b/plugins/MidiExport/MidiExport.cpp @@ -85,7 +85,6 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, int nTracks = 0; - //int nTracks_BB = 0; uint8_t buffer[BUFFER_SIZE]; uint32_t size; @@ -127,9 +126,8 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, if (n.nodeName() == "instrumenttrack") { QDomElement it = n.toElement(); - base_pitch = qRound(it.attribute("pitch", "0").toInt() / 100.0); // transpose +12 semitones, workaround for #1857 - base_pitch += (69 - it.attribute("basenote", "57").toInt()); + base_pitch = (69 - it.attribute("basenote", "57").toInt()); if (it.attribute("usemasterpitch", "1").toInt()) { base_pitch += masterPitch; @@ -179,7 +177,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, DataFile dataFile(DataFile::SongProject); MTrack mtrack; - std::vector>>::iterator itr = plists.begin(); + auto itr = plists.begin(); std::vector> st; if(track->type() != Track::InstrumentTrack) continue; @@ -199,9 +197,8 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, if (n.nodeName() == "instrumenttrack") { QDomElement it = n.toElement(); - base_pitch = qRound(it.attribute("pitch", "0").toInt() / 100.0); // transpose +12 semitones, workaround for #1857 - base_pitch += (69 - it.attribute("basenote", "57").toInt()); + base_pitch = (69 - it.attribute("basenote", "57").toInt()); if (it.attribute("usemasterpitch", "1").toInt()) { base_pitch += masterPitch; @@ -219,7 +216,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, // workaround for nested BBTCOs int pos = 0; int len = n.toElement().attribute("steps", "1").toInt() * 12; - for(std::vector>::iterator it = plist.begin(); it != plist.end(); ++it) + for(auto it = plist.begin(); it != plist.end(); ++it) { while(!st.empty() && st.back().second <= it->first) { @@ -286,7 +283,7 @@ void MidiExport::writePattern(MidiNoteVector &pat, QDomNode n, void MidiExport::writePatternToTrack(MTrack &mtrack, MidiNoteVector &nv) { - for(MidiNoteIterator it = nv.begin(); it != nv.end(); ++it) + for(auto it = nv.begin(); it != nv.end(); ++it) { mtrack.addNote(it->pitch, it->volume, it->time / 48.0, it->duration / 48.0); } @@ -301,7 +298,7 @@ void MidiExport::writeBBPattern(MidiNoteVector &src, MidiNoteVector &dst, start -= base; end -= base; std::sort(src.begin(), src.end()); - for(MidiNoteIterator it = src.begin(); it != src.end(); ++it) + for(auto it = src.begin(); it != src.end(); ++it) { for(int time = it->time + ceil((start - it->time) / len) * len; time < end; time += len) @@ -322,7 +319,7 @@ void MidiExport::ProcessBBNotes(MidiNoteVector &nv) { std::sort(nv.begin(), nv.end()); int cur = INT_MAX, next = INT_MAX; - for(MidiNoteVector::reverse_iterator it = nv.rbegin(); it != nv.rend(); ++it) + for(auto it = nv.rbegin(); it != nv.rend(); ++it) { if (it->time < cur) { From babffb5af6af81e08ff482e9309652cd474ee328 Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Thu, 24 Aug 2017 11:11:00 +0900 Subject: [PATCH 6/8] Fix conventions --- plugins/MidiExport/MidiExport.cpp | 38 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/plugins/MidiExport/MidiExport.cpp b/plugins/MidiExport/MidiExport.cpp index 0b047aae2b8..03d298d3a78 100644 --- a/plugins/MidiExport/MidiExport.cpp +++ b/plugins/MidiExport/MidiExport.cpp @@ -88,8 +88,8 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, uint8_t buffer[BUFFER_SIZE]; uint32_t size; - for(const Track* track : tracks) if (track->type() == Track::InstrumentTrack) nTracks++; - for(const Track* track : tracks_BB) if (track->type() == Track::InstrumentTrack) nTracks++; + for (const Track* track : tracks) if (track->type() == Track::InstrumentTrack) nTracks++; + for (const Track* track : tracks_BB) if (track->type() == Track::InstrumentTrack) nTracks++; // midi header MidiFile::MIDIHeader header(nTracks); @@ -99,7 +99,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, std::vector>> plists; // midi tracks - for( Track* track : tracks ) + for (Track* track : tracks) { DataFile dataFile(DataFile::SongProject); MTrack mtrack; @@ -120,7 +120,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, MidiNoteVector pat; - for(QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) + for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.nodeName() == "instrumenttrack") @@ -154,7 +154,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, element = bbTrack->saveState(dataFile, dataFile.content()); std::vector> plist; - for(QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) + for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.nodeName() == "bbtco") @@ -172,7 +172,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, } // for each track // midi tracks in BB tracks - for( Track* track : tracks_BB ) + for (Track* track : tracks_BB) { DataFile dataFile(DataFile::SongProject); MTrack mtrack; @@ -180,7 +180,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, auto itr = plists.begin(); std::vector> st; - if(track->type() != Track::InstrumentTrack) continue; + if (track->type() != Track::InstrumentTrack) continue; mtrack.addName(track->name().toStdString(), 0); //mtrack.addProgramChange(0, 0); @@ -192,7 +192,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, int base_pitch = 0; double base_volume = 1.0; - for(QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) + for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.nodeName() == "instrumenttrack") { @@ -216,20 +216,20 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, // workaround for nested BBTCOs int pos = 0; int len = n.toElement().attribute("steps", "1").toInt() * 12; - for(auto it = plist.begin(); it != plist.end(); ++it) + for (auto it = plist.begin(); it != plist.end(); ++it) { - while(!st.empty() && st.back().second <= it->first) + while (!st.empty() && st.back().second <= it->first) { writeBBPattern(pat, nv, len, st.back().first, pos, st.back().second); pos = st.back().second; st.pop_back(); } - if(!st.empty() && st.back().second <= it->second) + if (!st.empty() && st.back().second <= it->second) { writeBBPattern(pat, nv, len, st.back().first, pos, it->first); pos = it->first; - while(!st.empty() && st.back().second <= it->second) + while (!st.empty() && st.back().second <= it->second) { st.pop_back(); } @@ -239,7 +239,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, pos = it->first; } - while(!st.empty()) + while (!st.empty()) { writeBBPattern(pat, nv, len, st.back().first, pos, st.back().second); pos = st.back().second; @@ -265,7 +265,7 @@ void MidiExport::writePattern(MidiNoteVector &pat, QDomNode n, int base_pitch, double base_volume, int base_time) { // TODO interpret steps="12" muted="0" type="1" name="Piano1" len="2592" - for(QDomNode nn = n.firstChild(); !nn.isNull(); nn = nn.nextSibling()) + for (QDomNode nn = n.firstChild(); !nn.isNull(); nn = nn.nextSibling()) { QDomElement note = nn.toElement(); if (note.attribute("len", "0") == "0") continue; @@ -283,7 +283,7 @@ void MidiExport::writePattern(MidiNoteVector &pat, QDomNode n, void MidiExport::writePatternToTrack(MTrack &mtrack, MidiNoteVector &nv) { - for(auto it = nv.begin(); it != nv.end(); ++it) + for (auto it = nv.begin(); it != nv.end(); ++it) { mtrack.addNote(it->pitch, it->volume, it->time / 48.0, it->duration / 48.0); } @@ -298,9 +298,9 @@ void MidiExport::writeBBPattern(MidiNoteVector &src, MidiNoteVector &dst, start -= base; end -= base; std::sort(src.begin(), src.end()); - for(auto it = src.begin(); it != src.end(); ++it) + for (auto it = src.begin(); it != src.end(); ++it) { - for(int time = it->time + ceil((start - it->time) / len) + for (int time = it->time + ceil((start - it->time) / len) * len; time < end; time += len) { MidiNote note; @@ -319,14 +319,14 @@ void MidiExport::ProcessBBNotes(MidiNoteVector &nv) { std::sort(nv.begin(), nv.end()); int cur = INT_MAX, next = INT_MAX; - for(auto it = nv.rbegin(); it != nv.rend(); ++it) + for (auto it = nv.rbegin(); it != nv.rend(); ++it) { if (it->time < cur) { next = cur; cur = it->time; } - if(it->duration < 0) + if (it->duration < 0) { it->duration = qMin(-it->duration, next - cur); } From 0f24b4dfba33f46cdc97535ce3038498c125089e Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Thu, 24 Aug 2017 11:45:21 +0900 Subject: [PATCH 7/8] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 661b13121a6..0800d341e2c 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Features * Many powerful instrument and effect-plugins out of the box * Full user-defined track-based automation and computer-controlled automation sources * Compatible with many standards such as SoundFont2, VST(i), LADSPA, GUS Patches, and full MIDI support -* MIDI file importing +* MIDI file importing and exporting Building --------- From 0b9648d8cc19ee63ef068f270f036d90afd286dc Mon Sep 17 00:00:00 2001 From: Hyunin Song Date: Tue, 12 Sep 2017 00:20:18 +0900 Subject: [PATCH 8/8] Cut BB-Notes at the end of BB Pattern, Update authors --- plugins/MidiExport/MidiExport.cpp | 11 ++++++----- plugins/MidiExport/MidiExport.h | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/MidiExport/MidiExport.cpp b/plugins/MidiExport/MidiExport.cpp index 03d298d3a78..1e20e9d40f4 100644 --- a/plugins/MidiExport/MidiExport.cpp +++ b/plugins/MidiExport/MidiExport.cpp @@ -47,7 +47,8 @@ Plugin::Descriptor PLUGIN_EXPORT midiexport_plugin_descriptor = "MIDI Export", QT_TRANSLATE_NOOP( "pluginBrowser", "Filter for exporting MIDI-files from LMMS" ), - "Mohamed Abdel Maksoud ", + "Mohamed Abdel Maksoud and " + "Hyunjin Song ", 0x0100, Plugin::ExportFilter, NULL, @@ -142,7 +143,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, } } - ProcessBBNotes(pat); + ProcessBBNotes(pat, INT_MAX); writePatternToTrack(mtrack, pat); size = mtrack.writeToBuffer(buffer); midiout.writeRawData((char *)buffer, size); @@ -246,7 +247,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, st.pop_back(); } - ProcessBBNotes(nv); + ProcessBBNotes(nv, pos); writePatternToTrack(mtrack, nv); ++itr; } @@ -315,7 +316,7 @@ void MidiExport::writeBBPattern(MidiNoteVector &src, MidiNoteVector &dst, -void MidiExport::ProcessBBNotes(MidiNoteVector &nv) +void MidiExport::ProcessBBNotes(MidiNoteVector &nv, int cutPos) { std::sort(nv.begin(), nv.end()); int cur = INT_MAX, next = INT_MAX; @@ -328,7 +329,7 @@ void MidiExport::ProcessBBNotes(MidiNoteVector &nv) } if (it->duration < 0) { - it->duration = qMin(-it->duration, next - cur); + it->duration = qMin(qMin(-it->duration, next - cur), cutPos - it->time); } } } diff --git a/plugins/MidiExport/MidiExport.h b/plugins/MidiExport/MidiExport.h index ea1f9f0efc2..3c36eeb8f55 100644 --- a/plugins/MidiExport/MidiExport.h +++ b/plugins/MidiExport/MidiExport.h @@ -75,7 +75,7 @@ class MidiExport: public ExportFilter void writePatternToTrack(MTrack &mtrack, MidiNoteVector &nv); void writeBBPattern(MidiNoteVector &src, MidiNoteVector &dst, int len, int base, int start, int end); - void ProcessBBNotes(MidiNoteVector &nv); + void ProcessBBNotes(MidiNoteVector &nv, int cutPos); void error();