diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c8c48eeb..7969fd9b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,7 @@ SET(PROJECT_COPYRIGHT "2008-${PROJECT_YEAR} ${PROJECT_AUTHOR}") SET(VERSION_MAJOR "1") SET(VERSION_MINOR "2") SET(VERSION_RELEASE "0") -SET(VERSION_STAGE "rc3") +SET(VERSION_STAGE "rc4") SET(VERSION_BUILD "0") SET(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_RELEASE}") IF(VERSION_STAGE) 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 46fb26c0d..6962ad403 100644 --- a/cmake/linux/lmms.desktop +++ b/cmake/linux/lmms.desktop @@ -10,7 +10,7 @@ Comment[ca]=Producció fàcil de música per a tothom! Comment[fr]=Production facile de musique pour tout le monde ! Comment[pl]=Prosta produkcja muzyki dla każdego! 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/BufferManager.h b/include/BufferManager.h index db1895fd7..845f5fad4 100644 --- a/include/BufferManager.h +++ b/include/BufferManager.h @@ -30,9 +30,6 @@ #include "export.h" #include "lmms_basics.h" -const int BM_INITIAL_BUFFERS = 512; -//const int BM_INCREMENT = 64; - class EXPORT BufferManager { public: @@ -46,17 +43,6 @@ public: const f_cnt_t offset = 0 ); #endif static void release( sampleFrame * buf ); - static void refresh(); -// static void extend( int c ); - -private: - static sampleFrame ** s_available; - static AtomicInt s_availableIndex; - - static sampleFrame ** s_released; - static AtomicInt s_releasedIndex; -// static QReadWriteLock s_mutex; - static int s_size; }; #endif 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/MainApplication.h b/include/MainApplication.h index 2f65ba538..8d5df9f86 100644 --- a/include/MainApplication.h +++ b/include/MainApplication.h @@ -25,13 +25,22 @@ #ifndef MAINAPPLICATION_H #define MAINAPPLICATION_H +#include "lmmsconfig.h" + #include +#ifdef LMMS_BUILD_WIN32 +#include +#endif + class MainApplication : public QApplication { public: MainApplication(int& argc, char** argv); bool event(QEvent* event); +#ifdef LMMS_BUILD_WIN32 + bool winEventFilter(MSG* msg, long* result); +#endif inline QString& queuedFile() { return m_queuedFile; diff --git a/include/MainWindow.h b/include/MainWindow.h index 1a58f868c..7ba2ac630 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -249,11 +249,4 @@ signals: } ; -class AutoSaveThread : public QThread -{ - Q_OBJECT -public: - void run(); -} ; - #endif diff --git a/include/MemoryHelper.h b/include/MemoryHelper.h index f3a2e20a2..7bd31bf2b 100644 --- a/include/MemoryHelper.h +++ b/include/MemoryHelper.h @@ -31,7 +31,7 @@ class MemoryHelper { public: - static void* alignedMalloc( int ); + static void* alignedMalloc( size_t ); static void alignedFree( void* ); diff --git a/include/MemoryManager.h b/include/MemoryManager.h index aa8ed5cb4..ef6c0abbf 100644 --- a/include/MemoryManager.h +++ b/include/MemoryManager.h @@ -42,7 +42,7 @@ struct MemoryPool { void * m_pool; char * m_free; - int m_chunks; + size_t m_chunks; QMutex m_mutex; MemoryPool() : @@ -51,10 +51,10 @@ struct MemoryPool m_chunks( 0 ) {} - MemoryPool( int chunks ) : + MemoryPool( size_t chunks ) : m_chunks( chunks ) { - m_free = (char*) MemoryHelper::alignedMalloc( chunks ); + m_free = reinterpret_cast( MemoryHelper::alignedMalloc( chunks ) ); memset( m_free, 1, chunks ); } @@ -103,6 +103,25 @@ private: static QMutex s_pointerMutex; }; +template +struct MmAllocator +{ + typedef T value_type; + template struct rebind { typedef MmAllocator other; }; + + T* allocate( std::size_t n ) + { + return reinterpret_cast( MemoryManager::alloc( sizeof(T) * n ) ); + } + + void deallocate( T* p, std::size_t ) + { + MemoryManager::free( p ); + } + + typedef std::vector > vector; +}; + #define MM_OPERATORS \ public: \ @@ -124,7 +143,7 @@ static void operator delete[] ( void * ptr ) \ } // for use in cases where overriding new/delete isn't a possibility -#define MM_ALLOC( type, count ) (type*) MemoryManager::alloc( sizeof( type ) * count ) +#define MM_ALLOC( type, count ) reinterpret_cast( MemoryManager::alloc( sizeof( type ) * count ) ) // and just for symmetry... #define MM_FREE( ptr ) MemoryManager::free( ptr ) diff --git a/include/PlayHandle.h b/include/PlayHandle.h index 1093607fb..329a8f766 100644 --- a/include/PlayHandle.h +++ b/include/PlayHandle.h @@ -28,6 +28,8 @@ #include #include +#include "MemoryManager.h" + #include "ThreadableJob.h" #include "lmms_basics.h" @@ -142,20 +144,17 @@ public: void releaseBuffer(); - sampleFrame * buffer() - { - return m_playHandleBuffer; - } + sampleFrame * buffer(); private: Type m_type; f_cnt_t m_offset; QThread* m_affinity; QMutex m_processingLock; - sampleFrame * m_playHandleBuffer; + sampleFrame* m_playHandleBuffer; + bool m_bufferReleased; bool m_usesBuffer; AudioPort * m_audioPort; - } ; diff --git a/include/RemotePlugin.h b/include/RemotePlugin.h index b3ac4b676..2d8114875 100644 --- a/include/RemotePlugin.h +++ b/include/RemotePlugin.h @@ -621,6 +621,11 @@ public: fetchAndProcessNextMessage(); } } + + static bool isMainThreadWaiting() + { + return waitDepthCounter() > 0; + } #endif virtual bool processMessage( const message & _m ) = 0; @@ -657,6 +662,14 @@ protected: private: +#ifndef BUILD_REMOTE_PLUGIN_CLIENT + static int & waitDepthCounter() + { + static int waitDepth = 0; + return waitDepth; + } +#endif + #ifdef SYNC_WITH_SHM_FIFO shmFifo * m_in; shmFifo * m_out; @@ -1089,6 +1102,26 @@ RemotePluginBase::message RemotePluginBase::waitForMessage( _busy_waiting = QThread::currentThread() == QCoreApplication::instance()->thread(); } + + struct WaitDepthCounter + { + WaitDepthCounter( int & depth, bool busy ) : + m_depth( depth ), + m_busy( busy ) + { + if( m_busy ) { ++m_depth; } + } + + ~WaitDepthCounter() + { + if( m_busy ) { --m_depth; } + } + + int & m_depth; + bool m_busy; + }; + + WaitDepthCounter wdc( waitDepthCounter(), _busy_waiting ); #endif while( !isInvalid() ) { 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 e28e586e0..ff807f354 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/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 0b6ac4632..ec87e8780 100644 --- a/plugins/vestige/vestige.cpp +++ b/plugins/vestige/vestige.cpp @@ -32,6 +32,7 @@ #include #include +#include "BufferManager.h" #include "ConfigManager.h" #include "Engine.h" #include "gui_templates.h" @@ -282,16 +283,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/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 67ce71ea8..09f8569e9 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -1385,20 +1385,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 ) @@ -1406,9 +1393,10 @@ void RemoteVstPlugin::loadChunkFromFile( const std::string & _file, int _len ) fprintf( stderr, "Error loading chunk from file.\n" ); } close( fd ); - pluginDispatch( 24, 0, _len, chunk ); - delete[] buf; + pluginDispatch( effSetChunk, 0, _len, chunk ); + + delete[] chunk; } diff --git a/src/core/BufferManager.cpp b/src/core/BufferManager.cpp index 572bff7d9..2df7bcaa9 100644 --- a/src/core/BufferManager.cpp +++ b/src/core/BufferManager.cpp @@ -1,6 +1,7 @@ /* * BufferManager.cpp - A buffer caching/memory management system * + * Copyright (c) 2017 Lukas W * Copyright (c) 2014 Vesa Kivimäki * Copyright (c) 2006-2014 Tobias Doerffel * @@ -25,56 +26,28 @@ #include "BufferManager.h" +#include "Engine.h" +#include "Mixer.h" #include "MemoryManager.h" -sampleFrame ** BufferManager::s_available; -AtomicInt BufferManager::s_availableIndex = 0; -sampleFrame ** BufferManager::s_released; -AtomicInt BufferManager::s_releasedIndex = 0; -//QReadWriteLock BufferManager::s_mutex; -int BufferManager::s_size; - +static fpp_t framesPerPeriod; void BufferManager::init( fpp_t framesPerPeriod ) { - s_available = MM_ALLOC( sampleFrame*, BM_INITIAL_BUFFERS ); - s_released = MM_ALLOC( sampleFrame*, BM_INITIAL_BUFFERS ); - - int c = framesPerPeriod * BM_INITIAL_BUFFERS; - sampleFrame * b = MM_ALLOC( sampleFrame, c ); - - for( int i = 0; i < BM_INITIAL_BUFFERS; ++i ) - { - s_available[ i ] = b; - b += framesPerPeriod; - } - s_availableIndex = BM_INITIAL_BUFFERS - 1; - s_size = BM_INITIAL_BUFFERS; + ::framesPerPeriod = framesPerPeriod; } sampleFrame * BufferManager::acquire() { - if( s_availableIndex < 0 ) - { - qFatal( "BufferManager: out of buffers" ); - } - - int i = s_availableIndex.fetchAndAddOrdered( -1 ); - sampleFrame * b = s_available[ i ]; - - //qDebug( "acquired buffer: %p - index %d", b, i ); - return b; + return MM_ALLOC( sampleFrame, ::framesPerPeriod ); } - -void BufferManager::clear( sampleFrame * ab, const f_cnt_t frames, - const f_cnt_t offset ) +void BufferManager::clear( sampleFrame *ab, const f_cnt_t frames, const f_cnt_t offset ) { memset( ab + offset, 0, sizeof( *ab ) * frames ); } - #ifndef LMMS_DISABLE_SURROUND void BufferManager::clear( surroundSampleFrame * ab, const f_cnt_t frames, const f_cnt_t offset ) @@ -86,43 +59,6 @@ void BufferManager::clear( surroundSampleFrame * ab, const f_cnt_t frames, void BufferManager::release( sampleFrame * buf ) { - if (buf == nullptr) return; - int i = s_releasedIndex.fetchAndAddOrdered( 1 ); - s_released[ i ] = buf; - //qDebug( "released buffer: %p - index %d", buf, i ); + MM_FREE( buf ); } - -void BufferManager::refresh() // non-threadsafe, hence it's called periodically from mixer at a time when no other threads can interfere -{ - if( s_releasedIndex == 0 ) return; - //qDebug( "refresh: %d buffers", int( s_releasedIndex ) ); - - int j = s_availableIndex; - for( int i = 0; i < s_releasedIndex; ++i ) - { - ++j; - s_available[ j ] = s_released[ i ]; - } - s_availableIndex = j; - s_releasedIndex = 0; -} - - -/* // non-extensible for now -void BufferManager::extend( int c ) -{ - s_size += c; - sampleFrame ** tmp = MM_ALLOC( sampleFrame*, s_size ); - MM_FREE( s_available ); - s_available = tmp; - - int cc = c * Engine::mixer()->framesPerPeriod(); - sampleFrame * b = MM_ALLOC( sampleFrame, cc ); - - for( int i = 0; i < c; ++i ) - { - s_available[ s_availableIndex.fetchAndAddOrdered( 1 ) + 1 ] = b; - b += Engine::mixer()->framesPerPeriod(); - } -}*/ diff --git a/src/core/MemoryHelper.cpp b/src/core/MemoryHelper.cpp index 023572bb9..eb5a24d44 100644 --- a/src/core/MemoryHelper.cpp +++ b/src/core/MemoryHelper.cpp @@ -30,7 +30,7 @@ * Allocate a number of bytes and return them. * @param byteNum is the number of bytes */ -void* MemoryHelper::alignedMalloc( int byteNum ) +void* MemoryHelper::alignedMalloc( size_t byteNum ) { char *ptr, *ptr2, *aligned_ptr; int align_mask = ALIGN_SIZE - 1; diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index 4ff732959..53cacbe63 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -482,9 +482,6 @@ const surroundSampleFrame * Mixer::renderNextBuffer() Controller::triggerFrameCounter(); AutomatableModel::incrementPeriodCounter(); - // refresh buffer pool - BufferManager::refresh(); - s_renderingThread = false; m_profiler.finishPeriod( processingSampleRate(), m_framesPerPeriod ); 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/PlayHandle.cpp b/src/core/PlayHandle.cpp index f4b1f0aa2..5481ea3e2 100644 --- a/src/core/PlayHandle.cpp +++ b/src/core/PlayHandle.cpp @@ -24,16 +24,21 @@ #include "PlayHandle.h" #include "BufferManager.h" +#include "Engine.h" +#include "Mixer.h" #include +#include +#include -PlayHandle::PlayHandle( const Type type, f_cnt_t offset ) : - m_type( type ), - m_offset( offset ), - m_affinity( QThread::currentThread() ), - m_playHandleBuffer( NULL ), - m_usesBuffer( true ) +PlayHandle::PlayHandle(const Type type, f_cnt_t offset) : + m_type(type), + m_offset(offset), + m_affinity(QThread::currentThread()), + m_playHandleBuffer(BufferManager::acquire()), + m_bufferReleased(true), + m_usesBuffer(true) { } @@ -48,8 +53,8 @@ void PlayHandle::doProcessing() { if( m_usesBuffer ) { - if( ! m_playHandleBuffer ) m_playHandleBuffer = BufferManager::acquire(); - play( m_playHandleBuffer ); + m_bufferReleased = false; + play( buffer() ); } else { @@ -60,6 +65,10 @@ void PlayHandle::doProcessing() void PlayHandle::releaseBuffer() { - BufferManager::release( m_playHandleBuffer ); - m_playHandleBuffer = NULL; + m_bufferReleased = true; } + +sampleFrame* PlayHandle::buffer() +{ + return m_bufferReleased ? nullptr : reinterpret_cast(m_playHandleBuffer); +}; diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 515d5a67f..94c6950b1 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -1435,14 +1435,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/core/audio/AudioPort.cpp b/src/core/audio/AudioPort.cpp index 4f779bb45..868f9f64f 100644 --- a/src/core/audio/AudioPort.cpp +++ b/src/core/audio/AudioPort.cpp @@ -36,7 +36,7 @@ AudioPort::AudioPort( const QString & _name, bool _has_effect_chain, FloatModel * volumeModel, FloatModel * panningModel, BoolModel * mutedModel ) : m_bufferUsage( false ), - m_portBuffer( NULL ), + m_portBuffer( BufferManager::acquire() ), m_extOutputEnabled( false ), m_nextFxChannel( 0 ), m_name( "unnamed port" ), @@ -57,6 +57,7 @@ AudioPort::~AudioPort() setExtOutputEnabled( false ); Engine::mixer()->removeAudioPort( this ); delete m_effects; + BufferManager::release( m_portBuffer ); } @@ -110,8 +111,7 @@ void AudioPort::doProcessing() const fpp_t fpp = Engine::mixer()->framesPerPeriod(); - // get a buffer for processing and clear it - m_portBuffer = BufferManager::acquire(); + // clear the buffer BufferManager::clear( m_portBuffer, fpp ); //qDebug( "Playhandles: %d", m_playHandles.size() ); @@ -225,8 +225,6 @@ void AudioPort::doProcessing() // TODO: improve the flow here - convert to pull model m_bufferUsage = false; } - - BufferManager::release( m_portBuffer ); // release buffer, we don't need it anymore } diff --git a/src/gui/GuiApplication.cpp b/src/gui/GuiApplication.cpp index 448dd740e..788d38cba 100644 --- a/src/gui/GuiApplication.cpp +++ b/src/gui/GuiApplication.cpp @@ -41,6 +41,7 @@ #include #include +#include #include #include @@ -54,6 +55,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/MainApplication.cpp b/src/gui/MainApplication.cpp index d31cf3058..767eaa8fe 100644 --- a/src/gui/MainApplication.cpp +++ b/src/gui/MainApplication.cpp @@ -62,3 +62,27 @@ bool MainApplication::event(QEvent* event) return QApplication::event(event); } } + +#ifdef LMMS_BUILD_WIN32 +bool MainApplication::winEventFilter(MSG* msg, long* result) +{ + switch(msg->message) + { + case WM_STYLECHANGING: + if(msg->wParam == GWL_EXSTYLE) + { + // Prevent plugins making the main window transparent + STYLESTRUCT * style = reinterpret_cast(msg->lParam); + if(!(style->styleOld & WS_EX_LAYERED)) + { + style->styleNew &= ~WS_EX_LAYERED; + } + *result = 0; + return true; + } + return false; + default: + return false; + } +} +#endif diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index f65c96066..b91fe3ef2 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -54,6 +54,7 @@ #include "PluginView.h" #include "ProjectJournal.h" #include "ProjectNotes.h" +#include "RemotePlugin.h" #include "SetupDialog.h" #include "SideBar.h" #include "SongEditor.h" @@ -300,12 +301,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)) @@ -1536,14 +1536,14 @@ void MainWindow::browseHelp() void MainWindow::autoSave() { if( !Engine::getSong()->isExporting() && + !Engine::getSong()->isLoadingProject() && + !RemotePluginBase::isMainThreadWaiting() && !QApplication::mouseButtons() && - ( ConfigManager::inst()->value( "ui", - "enablerunningautosave" ).toInt() || - ! Engine::getSong()->isPlaying() ) ) + ( ConfigManager::inst()->value( "ui", + "enablerunningautosave" ).toInt() || + ! Engine::getSong()->isPlaying() ) ) { - AutoSaveThread * ast = new AutoSaveThread(); - connect( ast, SIGNAL( finished() ), ast, SLOT( deleteLater() ) ); - ast->start(); + Engine::getSong()->saveProjectFile(ConfigManager::inst()->recoveryFile()); autoSaveTimerReset(); // Reset timer } else @@ -1555,11 +1555,3 @@ 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..6aaf3eb75 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -157,7 +157,8 @@ InstrumentTrack::~InstrumentTrack() void InstrumentTrack::processAudioBuffer( sampleFrame* buf, const fpp_t frames, NotePlayHandle* n ) { // we must not play the sound if this InstrumentTrack is muted... - if( isMuted() || ( n && n->isBbTrackMuted() ) || ! m_instrument ) + if( isMuted() || ( Engine::getSong()->playMode() != Song::Mode_PlayPattern && + n && n->isBbTrackMuted() ) || ! m_instrument ) { return; } @@ -237,6 +238,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 +279,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 +314,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; } } @@ -586,7 +614,10 @@ bool InstrumentTrack::play( const MidiTime & _start, const fpp_t _frames, { TrackContentObject * tco = getTCO( _tco_num ); tcos.push_back( tco ); - bb_track = BBTrack::findBBTrack( _tco_num ); + if (trackContainer() == (TrackContainer*)Engine::getBBTrackContainer()) + { + bb_track = BBTrack::findBBTrack( _tco_num ); + } } else { diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 3fe8035d9..bf595f543 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -594,7 +594,10 @@ bool SampleTrack::play( const MidiTime & _start, const fpp_t _frames, return false; } tcos.push_back( getTCO( _tco_num ) ); - bb_track = BBTrack::findBBTrack( _tco_num ); + if (trackContainer() == (TrackContainer*)Engine::getBBTrackContainer()) + { + bb_track = BBTrack::findBBTrack( _tco_num ); + } } else {