diff --git a/README.md b/README.md index 9c65c4be7..1061ecff6 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 --------- diff --git a/cmake/apple/lmms.plist.in b/cmake/apple/lmms.plist.in index 638b4af1a..10ff7a996 100644 --- a/cmake/apple/lmms.plist.in +++ b/cmake/apple/lmms.plist.in @@ -143,6 +143,8 @@ + NSPrincipalClass + NSApplication NSHighResolutionCapable True diff --git a/cmake/linux/lmms.desktop b/cmake/linux/lmms.desktop index 0d236d446..6094ccfe1 100644 --- a/cmake/linux/lmms.desktop +++ b/cmake/linux/lmms.desktop @@ -8,7 +8,7 @@ Comment=easy music production for everyone! Comment[ca]=Producció fàcil de música per a tothom! Comment[fr]=Production facile de musique pour tout le monde ! Icon=lmms -Exec=env QT_X11_NO_NATIVE_MENUBAR=1 lmms %f +Exec=env QT_X11_NO_NATIVE_MENUBAR=1 QT_AUTO_SCREEN_SCALE_FACTOR=1 lmms %f Terminal=false Type=Application Categories=Qt;AudioVideo;Audio;Midi; diff --git a/include/ExportFilter.h b/include/ExportFilter.h index f27bc0c82..35416f492 100644 --- a/include/ExportFilter.h +++ b/include/ExportFilter.h @@ -39,7 +39,9 @@ public: 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, int masterPitch, const QString &filename ) = 0; protected: virtual void saveSettings( QDomDocument &, QDomElement & ) diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index 6d2e42c3d..5ef604def 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -237,6 +237,7 @@ private: MidiPort m_midiPort; NotePlayHandle* m_notes[NumKeys]; + NotePlayHandleList m_sustainedNotes; int m_runningMidiNotes[NumKeys]; QMutex m_midiNotesMutex; diff --git a/include/MainWindow.h b/include/MainWindow.h index 69b430d33..1a58f868c 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -29,6 +29,7 @@ #include #include #include +#include #include "ConfigManager.h" #include "SubWindow.h" @@ -248,4 +249,11 @@ signals: } ; +class AutoSaveThread : public QThread +{ + Q_OBJECT +public: + void run(); +} ; + #endif diff --git a/include/RemotePlugin.h b/include/RemotePlugin.h index a23212018..3493a9e6f 100644 --- a/include/RemotePlugin.h +++ b/include/RemotePlugin.h @@ -425,6 +425,7 @@ enum RemoteMessageIDs IdChangeSharedMemoryKey, IdChangeInputCount, IdChangeOutputCount, + IdChangeInputOutputCount, IdShowUI, IdHideUI, IdToggleUI, @@ -934,6 +935,15 @@ public: sendMessage( message( IdChangeOutputCount ).addInt( _i ) ); } + void setInputOutputCount( int i, int o ) + { + m_inputCount = i; + m_outputCount = o; + sendMessage( message( IdChangeInputOutputCount ) + .addInt( i ) + .addInt( o ) ); + } + virtual int inputCount() const { return m_inputCount; @@ -1087,6 +1097,14 @@ RemotePluginBase::message RemotePluginBase::waitForMessage( const message & _wm, bool _busy_waiting ) { +#ifndef BUILD_REMOTE_PLUGIN_CLIENT + if( _busy_waiting ) + { + // No point processing events outside of the main thread + _busy_waiting = QThread::currentThread() == + QCoreApplication::instance()->thread(); + } +#endif while( !isInvalid() ) { #ifndef BUILD_REMOTE_PLUGIN_CLIENT diff --git a/include/aeffectx.h b/include/aeffectx.h index b398c88e3..138e356c1 100644 --- a/include/aeffectx.h +++ b/include/aeffectx.h @@ -101,6 +101,7 @@ const int effEditOpen = 14; const int effEditClose = 15; const int effEditIdle = 19; const int effEditTop = 20; +const int effSetChunk = 24; const int effProcessEvents = 25; const int effGetEffectName = 45; const int effGetVendorString = 47; diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index e010b11e8..6a60d73cf 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -72,7 +72,7 @@ IF("${PLUGIN_LIST}" STREQUAL "") LadspaEffect lb302 MidiImport - # MidiExport - temporarily disabled, MIDI export is broken + MidiExport MultitapEcho monstro nes diff --git a/plugins/MidiExport/MidiExport.cpp b/plugins/MidiExport/MidiExport.cpp index b838353d2..1e20e9d40 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" @@ -44,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, @@ -68,99 +72,269 @@ 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, int masterPitch, const QString &filename) { QFile f(filename); f.open(QIODevice::WriteOnly); QDataStream midiout(&f); InstrumentTrack* instTrack; + BBTrack* bbTrack; QDomElement element; int nTracks = 0; - const int BUFFER_SIZE = 50*1024; 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); - // midi tracks - for( Track* track : tracks ) - { - DataFile dataFile( DataFile::SongProject ); - MidiFile::MIDITrack mtrack; - - if( track->type() != Track::InstrumentTrack ) continue; + std::vector>> plists; + + // midi tracks + for (Track* track : tracks) + { + 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(); + // 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") + { + base_time = n.toElement().attribute("pos", "0").toInt(); + writePattern(pat, n, base_pitch, base_volume, base_time); + } + + } + ProcessBBNotes(pat, INT_MAX); + 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; + + auto itr = plists.begin(); + std::vector> st; + + 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( track ); - element = instTrack->saveState( dataFile, dataFile.content() ); - - // instrumentTrack - // - instrumentTrack - // - pattern - int base_pitch = 0; + + 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()) + + 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; + // 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") { - 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()) + 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 (auto it = plist.begin(); it != plist.end(); ++it) { - 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); + 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, pos); + writePatternToTrack(mtrack, nv); + ++itr; } - } size = mtrack.writeToBuffer(buffer); midiout.writeRawData((char *)buffer, size); - } // for each track - + } + return true; } +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") continue; + // TODO interpret pan="0" fxch="0" pitchrange="1" + MidiNote mnote; + 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(); + pat.push_back(mnote); + } +} + + + +void MidiExport::writePatternToTrack(MTrack &mtrack, MidiNoteVector &nv) +{ + for (auto 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 (auto 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, int cutPos) +{ + std::sort(nv.begin(), nv.end()); + int cur = INT_MAX, next = INT_MAX; + for (auto it = nv.rbegin(); it != nv.rend(); ++it) + { + if (it->time < cur) + { + next = cur; + cur = it->time; + } + if (it->duration < 0) + { + it->duration = qMin(qMin(-it->duration, next - cur), cutPos - it->time); + } + } +} + + void MidiExport::error() { diff --git a/plugins/MidiExport/MidiExport.h b/plugins/MidiExport/MidiExport.h index 279f369f6..3c36eeb8f 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 * @@ -31,25 +32,52 @@ #include "MidiFile.hpp" +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, int tempo, const QString &filename ); + virtual bool tryExport(const TrackContainer::TrackList &tracks, + const TrackContainer::TrackList &tracks_BB, + int tempo, int masterPitch, const QString &filename); private: - + 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, int cutPos); - void error( void ); + void error(); } ; diff --git a/plugins/MidiExport/MidiFile.hpp b/plugins/MidiExport/MidiFile.hpp index 0e2bfbe5b..a1f91de2f 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); } }; diff --git a/plugins/vestige/vestige.cpp b/plugins/vestige/vestige.cpp index 548cde1bb..59f29be9e 100644 --- a/plugins/vestige/vestige.cpp +++ b/plugins/vestige/vestige.cpp @@ -32,6 +32,7 @@ #include #include +#include "BufferManager.h" #include "Engine.h" #include "gui_templates.h" #include "InstrumentPlayHandle.h" @@ -285,16 +286,19 @@ void vestigeInstrument::loadFile( const QString & _file ) void vestigeInstrument::play( sampleFrame * _buf ) { m_pluginMutex.lock(); + + const fpp_t frames = Engine::mixer()->framesPerPeriod(); + if( m_plugin == NULL ) { + BufferManager::clear( _buf, frames ); + m_pluginMutex.unlock(); return; } m_plugin->process( NULL, _buf ); - const fpp_t frames = Engine::mixer()->framesPerPeriod(); - instrumentTrack()->processAudioBuffer( _buf, frames, NULL ); m_pluginMutex.unlock(); diff --git a/plugins/vibed/vibrating_string.h b/plugins/vibed/vibrating_string.h index 6d620ace0..c21ed43cf 100644 --- a/plugins/vibed/vibrating_string.h +++ b/plugins/vibed/vibrating_string.h @@ -130,7 +130,7 @@ private: offset = ( m_randomize / 2.0f - m_randomize ) * r; _dl->data[i] = _scale * - _values[_dl->length - i] + + _values[_dl->length - i - 1] + offset; } for( int i = _pick; i < _dl->length; i++ ) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index e29487839..8b594a4b2 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -44,6 +44,10 @@ #ifdef LMMS_BUILD_LINUX +#ifndef NOMINMAX +#define NOMINMAX +#endif + #ifndef O_BINARY #define O_BINARY 0 #endif @@ -60,6 +64,7 @@ #include #include +#include #include @@ -105,7 +110,7 @@ class RemoteVstPlugin; RemoteVstPlugin * __plugin = NULL; -DWORD __GuiThreadID = 0; +HWND __MessageHwnd = NULL; @@ -237,8 +242,58 @@ public: pthread_mutex_unlock( &m_pluginLock ); } + inline void lockShm() + { + pthread_mutex_lock( &m_shmLock ); + } + + inline void unlockShm() + { + pthread_mutex_unlock( &m_shmLock ); + } + + inline bool isShmValid() + { + return m_shmValid; + } + + inline void setShmIsValid( bool valid ) + { + m_shmValid = valid; + } + + inline bool isProcessing() const + { + return m_processing; + } + + inline void setProcessing( bool processing ) + { + m_processing = processing; + } + + inline void queueMessage( const message & m ) { + m_messageList.push( m ); + } + + inline bool shouldGiveIdle() const + { + return m_shouldGiveIdle; + } + + inline void setShouldGiveIdle( bool shouldGiveIdle ) + { + m_shouldGiveIdle = shouldGiveIdle; + } + + void idle(); + void processUIThreadMessages(); + static DWORD WINAPI processingThread( LPVOID _param ); - static DWORD WINAPI guiEventLoop( LPVOID _param ); + static bool setupMessageWindow(); + static DWORD WINAPI guiEventLoop(); + static LRESULT CALLBACK messageWndProc( HWND hwnd, UINT uMsg, + WPARAM wParam, LPARAM lParam ); private: @@ -297,11 +352,18 @@ private: bool m_registeredWindowClass; pthread_mutex_t m_pluginLock; + bool m_processing; + + std::queue m_messageList; + bool m_shouldGiveIdle; float * * m_inputs; float * * m_outputs; + pthread_mutex_t m_shmLock; + bool m_shmValid; + typedef std::vector VstMidiEventList; VstMidiEventList m_midiEvents; @@ -342,8 +404,13 @@ RemoteVstPlugin::RemoteVstPlugin( const char * socketPath ) : m_initialized( false ), m_registeredWindowClass( false ), m_pluginLock(), + m_processing( false ), + m_messageList(), + m_shouldGiveIdle( false ), m_inputs( NULL ), m_outputs( NULL ), + m_shmLock(), + m_shmValid( false ), m_midiEvents(), m_bpm( 0 ), m_currentSamplePos( 0 ), @@ -353,6 +420,7 @@ RemoteVstPlugin::RemoteVstPlugin( const char * socketPath ) : m_vstSyncData( NULL ) { pthread_mutex_init( &m_pluginLock, NULL ); + pthread_mutex_init( &m_shmLock, NULL ); __plugin = this; @@ -444,6 +512,7 @@ RemoteVstPlugin::~RemoteVstPlugin() delete[] m_inputs; delete[] m_outputs; + pthread_mutex_destroy( &m_shmLock ); pthread_mutex_destroy( &m_pluginLock ); } @@ -838,6 +907,16 @@ void RemoteVstPlugin::process( const sampleFrame * _in, sampleFrame * _out ) // now we're ready to fetch sound from VST-plugin + lock(); + lockShm(); + + if( !isShmValid() ) + { + unlockShm(); + unlock(); + return; + } + for( int i = 0; i < inputCount(); ++i ) { m_inputs[i] = &((float *) _in)[i * bufferSize()]; @@ -849,8 +928,6 @@ void RemoteVstPlugin::process( const sampleFrame * _in, sampleFrame * _out ) memset( m_outputs[i], 0, bufferSize() * sizeof( float ) ); } - lock(); - #ifdef OLD_VST_SDK if( m_plugin->flags & effFlagsCanReplacing ) { @@ -866,6 +943,7 @@ void RemoteVstPlugin::process( const sampleFrame * _in, sampleFrame * _out ) } #endif + unlockShm(); unlock(); m_currentSamplePos += bufferSize(); @@ -1351,20 +1429,7 @@ void RemoteVstPlugin::loadPresetFile( const std::string & _file ) void RemoteVstPlugin::loadChunkFromFile( const std::string & _file, int _len ) { - char * buf = NULL; - - void * chunk = NULL; - // various plugins need this in order to not crash when setting - // chunk (also we let the plugin allocate "safe" memory this way) - const int actualLen = pluginDispatch( 23, 0, 0, &chunk ); - - // allocated buffer big enough? - if( _len > actualLen ) - { - // no, then manually allocate a buffer - buf = new char[_len]; - chunk = buf; - } + char * chunk = new char[_len]; const int fd = open( _file.c_str(), O_RDONLY | O_BINARY ); if ( ::read( fd, chunk, _len ) != _len ) @@ -1372,9 +1437,10 @@ void RemoteVstPlugin::loadChunkFromFile( const std::string & _file, int _len ) fprintf( stderr, "Error loading chunk from file.\n" ); } close_check( fd ); - pluginDispatch( 24, 0, _len, chunk ); - delete[] buf; + pluginDispatch( effSetChunk, 0, _len, chunk ); + + delete[] chunk; } @@ -1382,14 +1448,19 @@ void RemoteVstPlugin::loadChunkFromFile( const std::string & _file, int _len ) void RemoteVstPlugin::updateInOutCount() { + lockShm(); + + setShmIsValid( false ); + + unlockShm(); + delete[] m_inputs; delete[] m_outputs; m_inputs = NULL; m_outputs = NULL; - setInputCount( inputCount() ); - setOutputCount( outputCount() ); + setInputOutputCount( inputCount(), outputCount() ); char buf[64]; sprintf( buf, "inputs: %d output: %d\n", inputCount(), outputCount() ); @@ -1462,8 +1533,7 @@ intptr_t RemoteVstPlugin::hostCallback( AEffect * _effect, int32_t _opcode, SHOW_CALLBACK ("amc: audioMasterIdle\n" ); // call application idle routine (this will // call effEditIdle for all open editors too) - PostThreadMessage( __GuiThreadID, - WM_USER, GiveIdle, 0 ); + PostMessage( __MessageHwnd, WM_USER, GiveIdle, 0 ); return 0; case audioMasterPinConnected: @@ -1764,8 +1834,7 @@ intptr_t RemoteVstPlugin::hostCallback( AEffect * _effect, int32_t _opcode, case audioMasterUpdateDisplay: SHOW_CALLBACK( "amc: audioMasterUpdateDisplay\n" ); // something has changed, update 'multi-fx' display - PostThreadMessage( __GuiThreadID, - WM_USER, GiveIdle, 0 ); + PostMessage( __MessageHwnd, WM_USER, GiveIdle, 0 ); return 0; #if kVstVersion > 2 @@ -1798,6 +1867,43 @@ intptr_t RemoteVstPlugin::hostCallback( AEffect * _effect, int32_t _opcode, +void RemoteVstPlugin::idle() +{ + if( isProcessing() ) + { + setShouldGiveIdle( true ); + return; + } + setProcessing( true ); + pluginDispatch( effEditIdle ); + setShouldGiveIdle( false ); + setProcessing( false ); + // We might have received a message whilst idling + processUIThreadMessages(); +} + + + + +void RemoteVstPlugin::processUIThreadMessages() +{ + setProcessing( true ); + while( m_messageList.size() ) + { + processMessage( m_messageList.front() ); + m_messageList.pop(); + if( shouldGiveIdle() ) + { + pluginDispatch( effEditIdle ); + setShouldGiveIdle( false ); + } + } + setProcessing( false ); +} + + + + DWORD WINAPI RemoteVstPlugin::processingThread( LPVOID _param ) { RemoteVstPlugin * _this = static_cast( _param ); @@ -1809,9 +1915,14 @@ DWORD WINAPI RemoteVstPlugin::processingThread( LPVOID _param ) { _this->processMessage( m ); } + else if( m.id == IdChangeSharedMemoryKey ) + { + _this->processMessage( m ); + _this->setShmIsValid( true ); + } else { - PostThreadMessage( __GuiThreadID, + PostMessage( __MessageHwnd, WM_USER, ProcessPluginMessage, (LPARAM) new message( m ) ); @@ -1819,7 +1930,7 @@ DWORD WINAPI RemoteVstPlugin::processingThread( LPVOID _param ) } // notify GUI thread about shutdown - PostThreadMessage( __GuiThreadID, WM_USER, ClosePlugin, 0 ); + PostMessage( __MessageHwnd, WM_USER, ClosePlugin, 0 ); return 0; } @@ -1827,68 +1938,44 @@ DWORD WINAPI RemoteVstPlugin::processingThread( LPVOID _param ) -DWORD WINAPI RemoteVstPlugin::guiEventLoop( LPVOID _param ) +bool RemoteVstPlugin::setupMessageWindow() { - RemoteVstPlugin * _this = static_cast( _param ); - HMODULE hInst = GetModuleHandle( NULL ); if( hInst == NULL ) { - _this->debugMessage( "guiEventLoop(): can't get " + __plugin->debugMessage( "setupMessageWindow(): can't get " "module handle\n" ); - return -1; + return false; } - HWND timerWindow = CreateWindowEx( 0, "LVSL", "dummy", + __MessageHwnd = CreateWindowEx( 0, "LVSL", "dummy", 0, 0, 0, 0, 0, NULL, NULL, hInst, NULL ); + SetWindowLongPtr( __MessageHwnd, GWLP_WNDPROC, + reinterpret_cast( RemoteVstPlugin::messageWndProc ) ); // install GUI update timer - SetTimer( timerWindow, 1000, 50, NULL ); + SetTimer( __MessageHwnd, 1000, 50, NULL ); + return true; +} + + + + +DWORD WINAPI RemoteVstPlugin::guiEventLoop() +{ MSG msg; - - bool quit = false; - while( quit == false && GetMessage( &msg, NULL, 0, 0 ) ) + while( GetMessage( &msg, NULL, 0, 0 ) > 0 ) { TranslateMessage( &msg ); if( msg.message == WM_SYSCOMMAND && msg.wParam == SC_CLOSE ) { - _this->destroyEditor(); + __plugin->destroyEditor(); continue; } DispatchMessage( &msg ); - - if( msg.message == WM_TIMER && _this->isInitialized() ) - { - // give plugin some idle-time for GUI-update - _this->pluginDispatch( effEditIdle ); - } - else if( msg.message == WM_USER ) - { - switch( msg.wParam ) - { - case ProcessPluginMessage: - { - message * m = (message *) msg.lParam; - _this->processMessage( *m ); - delete m; - break; - } - - case GiveIdle: - _this->pluginDispatch( effEditIdle ); - break; - - case ClosePlugin: - quit = true; - break; - - default: - break; - } - } } return 0; @@ -1897,6 +1984,49 @@ DWORD WINAPI RemoteVstPlugin::guiEventLoop( LPVOID _param ) +LRESULT CALLBACK RemoteVstPlugin::messageWndProc( HWND hwnd, UINT uMsg, + WPARAM wParam, LPARAM lParam ) +{ + if( uMsg == WM_TIMER && __plugin->isInitialized() ) + { + // give plugin some idle-time for GUI-update + __plugin->idle(); + return 0; + } + else if( uMsg == WM_USER ) + { + switch( wParam ) + { + case ProcessPluginMessage: + { + message * m = (message *) lParam; + __plugin->queueMessage( *m ); + delete m; + if( !__plugin->isProcessing() ) + { + __plugin->processUIThreadMessages(); + } + return 0; + } + + case GiveIdle: + __plugin->idle(); + return 0; + + case ClosePlugin: + PostQuitMessage(0); + return 0; + + default: + break; + } + } + return DefWindowProc( hwnd, uMsg, wParam, lParam ); +} + + + + int main( int _argc, char * * _argv ) { #ifdef SYNC_WITH_SHM_FIFO @@ -1944,7 +2074,10 @@ int main( int _argc, char * * _argv ) if( __plugin->isInitialized() ) { - __GuiThreadID = GetCurrentThreadId(); + if( RemoteVstPlugin::setupMessageWindow() == false ) + { + return -1; + } if( CreateThread( NULL, 0, RemoteVstPlugin::processingThread, __plugin, 0, NULL ) == NULL ) { @@ -1952,7 +2085,7 @@ int main( int _argc, char * * _argv ) "processingThread\n" ); return -1; } - RemoteVstPlugin::guiEventLoop( __plugin ); + RemoteVstPlugin::guiEventLoop(); } diff --git a/plugins/vst_base/VstPlugin.cpp b/plugins/vst_base/VstPlugin.cpp index a78116f49..bb4116cc6 100644 --- a/plugins/vst_base/VstPlugin.cpp +++ b/plugins/vst_base/VstPlugin.cpp @@ -394,7 +394,7 @@ int VstPlugin::currentProgram() { lock(); sendMessage( message( IdVstCurrentProgram ) ); - waitForMessage( IdVstCurrentProgram ); + waitForMessage( IdVstCurrentProgram, true ); unlock(); return m_currentProgram; @@ -406,7 +406,7 @@ const QMap & VstPlugin::parameterDump() { lock(); sendMessage( IdVstGetParameterDump ); - waitForMessage( IdVstParameterDump ); + waitForMessage( IdVstParameterDump, true ); unlock(); return m_parameterDump; @@ -533,7 +533,7 @@ void VstPlugin::openPreset( ) QSTR_TO_STDSTR( QDir::toNativeSeparators( ofd.selectedFiles()[0] ) ) ) ); - waitForMessage( IdLoadPresetFile ); + waitForMessage( IdLoadPresetFile, true ); unlock(); } } @@ -545,7 +545,7 @@ void VstPlugin::setProgram( int index ) { lock(); sendMessage( message( IdVstSetProgram ).addInt( index ) ); - waitForMessage( IdVstSetProgram ); + waitForMessage( IdVstSetProgram, true ); unlock(); } @@ -556,7 +556,7 @@ void VstPlugin::rotateProgram( int offset ) { lock(); sendMessage( message( IdVstRotateProgram ).addInt( offset ) ); - waitForMessage( IdVstRotateProgram ); + waitForMessage( IdVstRotateProgram, true ); unlock(); } @@ -567,7 +567,7 @@ void VstPlugin::loadProgramNames() { lock(); sendMessage( message( IdVstProgramNames ) ); - waitForMessage( IdVstProgramNames ); + waitForMessage( IdVstProgramNames, true ); unlock(); } @@ -604,7 +604,7 @@ void VstPlugin::savePreset( ) QSTR_TO_STDSTR( QDir::toNativeSeparators( fns ) ) ) ); - waitForMessage( IdSavePresetFile ); + waitForMessage( IdSavePresetFile, true ); unlock(); } } @@ -616,7 +616,7 @@ void VstPlugin::setParam( int i, float f ) { lock(); sendMessage( message( IdVstSetParameter ).addInt( i ).addFloat( f ) ); - //waitForMessage( IdVstSetParameter ); + //waitForMessage( IdVstSetParameter, true ); unlock(); } @@ -645,7 +645,7 @@ void VstPlugin::loadChunk( const QByteArray & _chunk ) QSTR_TO_STDSTR( QDir::toNativeSeparators( tf.fileName() ) ) ). addInt( _chunk.size() ) ); - waitForMessage( IdLoadSettingsFromFile ); + waitForMessage( IdLoadSettingsFromFile, true ); unlock(); } } @@ -664,7 +664,7 @@ QByteArray VstPlugin::saveChunk() addString( QSTR_TO_STDSTR( QDir::toNativeSeparators( tf.fileName() ) ) ) ); - waitForMessage( IdSaveSettingsToFile ); + waitForMessage( IdSaveSettingsToFile, true ); unlock(); a = tf.readAll(); } diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index 0dff48fc0..84d888fee 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -252,17 +252,8 @@ void NotePlayHandle::play( sampleFrame * _working_buffer ) if( m_released && (!instrumentTrack()->isSustainPedalPressed() || m_releaseStarted) ) { - if (m_releaseStarted == false) - { + m_releaseStarted = true; - if( m_origin == OriginMidiInput ) - { - setLength( MidiTime( static_cast( totalFramesPlayed() / Engine::framesPerTick() ) ) ); - m_instrumentTrack->midiNoteOff( *this ); - } - - m_releaseStarted = true; - } f_cnt_t todo = framesThisPeriod; // if this note is base-note for arpeggio, always set @@ -389,6 +380,16 @@ void NotePlayHandle::noteOff( const f_cnt_t _s ) MidiTime::fromFrames( _s, Engine::framesPerTick() ), _s ); } + + // inform attached components about MIDI finished (used for recording in Piano Roll) + if (!instrumentTrack()->isSustainPedalPressed()) + { + if( m_origin == OriginMidiInput ) + { + setLength( MidiTime( static_cast( totalFramesPlayed() / Engine::framesPerTick() ) ) ); + m_instrumentTrack->midiNoteOff( *this ); + } + } } diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index 064d77b2c..ac2cf3f91 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -495,6 +495,12 @@ bool RemotePlugin::processMessage( const message & _m ) resizeSharedProcessingMemory(); break; + case IdChangeInputOutputCount: + m_inputCount = _m.getInt( 0 ); + m_outputCount = _m.getInt( 1 ); + resizeSharedProcessingMemory(); + break; + case IdDebugMessage: fprintf( stderr, "RemotePlugin::DebugMessage: %s", _m.getString( 0 ).c_str() ); diff --git a/src/core/Song.cpp b/src/core/Song.cpp index a576bbcbd..01071668c 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -1444,14 +1444,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, getTempo(), m_masterPitchModel.value(), export_filename); } } diff --git a/src/gui/GuiApplication.cpp b/src/gui/GuiApplication.cpp index 5315a66a9..e82d95b9a 100644 --- a/src/gui/GuiApplication.cpp +++ b/src/gui/GuiApplication.cpp @@ -40,6 +40,7 @@ #include "SongEditor.h" #include +#include #include #include @@ -53,6 +54,11 @@ GuiApplication* GuiApplication::instance() GuiApplication::GuiApplication() { + // enable HiDPI scaling before showing anything (Qt 5.6+ only) + #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true); + #endif + // prompt the user to create the LMMS working directory (e.g. ~/lmms) if it doesn't exist if ( !ConfigManager::inst()->hasWorkingDir() && QMessageBox::question( NULL, diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 63b931823..9bb4bfea6 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)) @@ -1541,7 +1540,9 @@ void MainWindow::autoSave() "enablerunningautosave" ).toInt() || ! Engine::getSong()->isPlaying() ) ) { - Engine::getSong()->saveProjectFile(ConfigManager::inst()->recoveryFile()); + AutoSaveThread * ast = new AutoSaveThread(); + connect( ast, SIGNAL( finished() ), ast, SLOT( deleteLater() ) ); + ast->start(); autoSaveTimerReset(); // Reset timer } else @@ -1553,3 +1554,11 @@ void MainWindow::autoSave() } } } + + + + +void AutoSaveThread::run() +{ + Engine::getSong()->saveProjectFile(ConfigManager::inst()->recoveryFile()); +} diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 71cc43fb5..3c53e4d4c 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -237,6 +237,11 @@ MidiEvent InstrumentTrack::applyMasterKey( const MidiEvent& event ) void InstrumentTrack::processInEvent( const MidiEvent& event, const MidiTime& time, f_cnt_t offset ) { + if( Engine::getSong()->isExporting() ) + { + return; + } + bool eventHandled = false; switch( event.type() ) @@ -273,6 +278,12 @@ void InstrumentTrack::processInEvent( const MidiEvent& event, const MidiTime& ti // be deleted later automatically) Engine::mixer()->requestChangeInModel(); m_notes[event.key()]->noteOff( offset ); + if (isSustainPedalPressed() && + m_notes[event.key()]->origin() == + m_notes[event.key()]->OriginMidiInput) + { + m_sustainedNotes << m_notes[event.key()]; + } m_notes[event.key()] = NULL; Engine::mixer()->doneChangeInModel(); } @@ -302,8 +313,24 @@ void InstrumentTrack::processInEvent( const MidiEvent& event, const MidiTime& ti { m_sustainPedalPressed = true; } - else + else if (isSustainPedalPressed()) { + for (NotePlayHandle* nph : m_sustainedNotes) + { + if (nph && nph->isReleased()) + { + if( nph->origin() == + nph->OriginMidiInput) + { + nph->setLength( + MidiTime( static_cast( + nph->totalFramesPlayed() / + Engine::framesPerTick() ) ) ); + midiNoteOff( *nph ); + } + } + } + m_sustainedNotes.clear(); m_sustainPedalPressed = false; } }