diff --git a/.gitmodules b/.gitmodules index ee6e7eac9..fa6980ac5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -40,9 +40,9 @@ [submodule "plugins/CarlaBase/carla"] path = plugins/CarlaBase/carla url = https://github.com/falktx/carla -[submodule "plugins/Sid/resid"] - path = plugins/Sid/resid - url = https://github.com/simonowen/resid +[submodule "plugins/Sid/resid/resid"] + path = plugins/Sid/resid/resid + url = https://github.com/libsidplayfp/resid [submodule "src/3rdparty/jack2"] path = src/3rdparty/jack2 url = https://github.com/jackaudio/jack2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b17d0e7c..4163ca5cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,13 @@ SET(LMMS_BINARY_DIR ${CMAKE_BINARY_DIR}) SET(LMMS_SOURCE_DIR ${CMAKE_SOURCE_DIR}) # CMAKE_POLICY Section +IF(COMMAND CMAKE_POLICY) + # TODO: Keep CMP0074 but remove this condition when cmake 3.12+ is guaranteed + IF(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.12) + # Needed for the SWH Ladspa plugins. See below. + CMAKE_POLICY(SET CMP0074 NEW) # find_package() uses _ROOT variables + ENDIF() +ENDIF(COMMAND CMAKE_POLICY) # Import of windows.h breaks min()/max() ADD_DEFINITIONS(-DNOMINMAX) @@ -70,6 +77,7 @@ OPTION(WANT_SOUNDIO "Include libsoundio support" ON) OPTION(WANT_SDL "Include SDL (Simple DirectMedia Layer) support" ON) OPTION(WANT_SF2 "Include SoundFont2 player plugin" ON) OPTION(WANT_GIG "Include GIG player plugin" ON) +option(WANT_SID "Include Sid instrument" ON) OPTION(WANT_STK "Include Stk (Synthesis Toolkit) support" ON) OPTION(WANT_SWH "Include Steve Harris's LADSPA plugins" ON) OPTION(WANT_TAP "Include Tom's Audio Processing LADSPA plugins" ON) @@ -204,6 +212,13 @@ CHECK_CXX_SOURCE_COMPILES( LMMS_HAVE_SF_COMPLEVEL ) +# check for perl +if(LMMS_BUILD_APPLE) + # Prefer system perl over Homebrew, MacPorts, etc + set(Perl_ROOT "/usr/bin") +endif() +find_package(Perl) + IF(WANT_LV2) IF(PKG_CONFIG_FOUND) PKG_CHECK_MODULES(LV2 lv2) @@ -266,8 +281,12 @@ ELSE(WANT_CMT) ENDIF(WANT_CMT) IF(WANT_SWH) - SET(LMMS_HAVE_SWH TRUE) - SET(STATUS_SWH "OK") + IF(PERL_FOUND) + SET(LMMS_HAVE_SWH TRUE) + SET(STATUS_SWH "OK") + ELSE() + SET(STATUS_SWH "Skipping, perl is missing") + ENDIF() ELSE(WANT_SWH) SET(STATUS_SWH "not built as requested") ENDIF(WANT_SWH) @@ -333,6 +352,16 @@ IF(WANT_SDL AND NOT LMMS_HAVE_SDL2) ENDIF() ENDIF() +# check for Sid +if(WANT_SID) + if(PERL_FOUND) + set(LMMS_HAVE_SID TRUE) + set(STATUS_SID "OK") + else() + set(STATUS_SID "not found, please install perl if you require the Sid instrument") + endif() +endif() + # check for Stk IF(WANT_STK) FIND_PACKAGE(STK) @@ -651,6 +680,9 @@ IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") ELSE(WIN32) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -DPIC") ENDIF(WIN32) +elseif(MSVC) + # Use UTF-8 as the source and execution character set + add_compile_options("/utf-8") ENDIF() # add enabled sanitizers @@ -797,6 +829,7 @@ MESSAGE( "* ZynAddSubFX instrument : ${STATUS_ZYN}\n" "* Carla Patchbay & Rack : ${STATUS_CARLA}\n" "* SoundFont2 player : ${STATUS_FLUIDSYNTH}\n" +"* Sid instrument : ${STATUS_SID}\n" "* Stk Mallets : ${STATUS_STK}\n" "* VST-instrument hoster : ${STATUS_VST}\n" "* VST-effect hoster : ${STATUS_VST}\n" diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index ac22a14ec..c73da5a2b 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -82,7 +82,6 @@ lmms--gui--TextFloat, lmms--gui--SimpleTextFloat { QMenu { border:1px solid #747474; background-color: #c9c9c9; - font-size:11px; } QMenu::separator { @@ -98,15 +97,12 @@ QMenu::item { QMenu::item:selected { color: white; - font-weight:bold; background-color: #747474; } QMenu::item:disabled { color: #747474; background-color: #c9c9c9; - font-size:12px; - font-weight: normal; padding: 4px 32px 4px 20px; } @@ -132,7 +128,7 @@ QMenu::indicator:selected { lmms--gui--FileBrowser QCheckBox { - font-size: 10px; + font-size: 8pt; color: white; } diff --git a/data/themes/default/style.css b/data/themes/default/style.css index 092332eee..854d4a4c3 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -9,7 +9,6 @@ QLabel, QTreeWidget, QListWidget, QGroupBox, QMenuBar { QTreeView { outline: none; - font-size: 12px; } QTreeWidget::item { @@ -42,7 +41,7 @@ QMdiArea { lmms--gui--FileBrowser QCheckBox { - font-size: 10px; + font-size: 8pt; color: white; } @@ -115,7 +114,6 @@ QSplashScreen QLabel { QMenu { border-top: 2px solid #08993E; background-color: #15191c; - font-size: 11px; } QMenu::separator { @@ -133,15 +131,12 @@ QMenu::item { QMenu::item:selected { color: #d1d8e4; - font-weight: normal; background-color: #21272b; } QMenu::item:disabled { color: #515459; background-color: #262b30; - font-size: 12px; - font-weight: normal; padding: 4px 32px 4px 20px; } diff --git a/include/DataFile.h b/include/DataFile.h index 137f0156f..dc82315ad 100644 --- a/include/DataFile.h +++ b/include/DataFile.h @@ -126,6 +126,8 @@ private: void upgrade_defaultTripleOscillatorHQ(); void upgrade_mixerRename(); void upgrade_bbTcoRename(); + void upgrade_sampleAndHold(); + void upgrade_midiCCIndexing(); // List of all upgrade methods static const std::vector UPGRADE_METHODS; diff --git a/include/FileBrowser.h b/include/FileBrowser.h index 3334a73f6..eafb827da 100644 --- a/include/FileBrowser.h +++ b/include/FileBrowser.h @@ -75,8 +75,7 @@ public: private slots: void reloadTree(); void expandItems( QTreeWidgetItem * item=nullptr, QList expandedDirs = QList() ); - // call with item=NULL to filter the entire tree - bool filterItems( const QString & filter, QTreeWidgetItem * item=nullptr ); + bool filterAndExpandItems(const QString & filter, QTreeWidgetItem * item = nullptr); void giveFocusToFilter(); private: @@ -84,6 +83,9 @@ private: void addItems( const QString & path ); + void saveDirectoriesStates(); + void restoreDirectoriesStates(); + FileBrowserTreeWidget * m_fileBrowserTreeWidget; QLineEdit * m_filterEdit; @@ -99,6 +101,8 @@ private: QCheckBox* m_showFactoryContent = nullptr; QString m_userDir; QString m_factoryDir; + QList m_savedExpandedDirs; + QString m_previousFilterValue; } ; @@ -115,7 +119,6 @@ public: //! that are expanded in the tree. QList expandedDirs( QTreeWidgetItem * item = nullptr ) const; - protected: void contextMenuEvent( QContextMenuEvent * e ) override; void mousePressEvent( QMouseEvent * me ) override; diff --git a/include/InstrumentPlayHandle.h b/include/InstrumentPlayHandle.h index bbf53d16c..dc744b4ff 100644 --- a/include/InstrumentPlayHandle.h +++ b/include/InstrumentPlayHandle.h @@ -26,62 +26,33 @@ #define LMMS_INSTRUMENT_PLAY_HANDLE_H #include "PlayHandle.h" -#include "Instrument.h" -#include "NotePlayHandle.h" #include "lmms_export.h" namespace lmms { +class Instrument; +class InstrumentTrack; + class LMMS_EXPORT InstrumentPlayHandle : public PlayHandle { public: - InstrumentPlayHandle( Instrument * instrument, InstrumentTrack* instrumentTrack ); + InstrumentPlayHandle(Instrument * instrument, InstrumentTrack* instrumentTrack); ~InstrumentPlayHandle() override = default; - - void play( sampleFrame * _working_buffer ) override - { - // ensure that all our nph's have been processed first - ConstNotePlayHandleList nphv = NotePlayHandle::nphsOfInstrumentTrack( m_instrument->instrumentTrack(), true ); - - bool nphsLeft; - do - { - nphsLeft = false; - for( const NotePlayHandle * constNotePlayHandle : nphv ) - { - NotePlayHandle * notePlayHandle = const_cast( constNotePlayHandle ); - if( notePlayHandle->state() != ThreadableJob::ProcessingState::Done && - !notePlayHandle->isFinished()) - { - nphsLeft = true; - notePlayHandle->process(); - } - } - } - while( nphsLeft ); - - m_instrument->play( _working_buffer ); - } + void play(sampleFrame * working_buffer) override; bool isFinished() const override { return false; } - bool isFromTrack( const Track* _track ) const override - { - return m_instrument->isFromTrack( _track ); - } - + bool isFromTrack(const Track* track) const override; private: Instrument* m_instrument; - -} ; - +}; } // namespace lmms diff --git a/include/LfoController.h b/include/LfoController.h index 2e2c45dac..d850b5c1c 100644 --- a/include/LfoController.h +++ b/include/LfoController.h @@ -86,6 +86,7 @@ protected: sample_t (*m_sampleFunction)( const float ); private: + float m_heldSample; std::shared_ptr m_userDefSampleBuffer; protected slots: diff --git a/include/MidiController.h b/include/MidiController.h index c4ef49590..9f49627ac 100644 --- a/include/MidiController.h +++ b/include/MidiController.h @@ -48,6 +48,7 @@ class MidiController : public Controller, public MidiEventProcessor { Q_OBJECT public: + static constexpr int NONE = -1; MidiController( Model * _parent ); ~MidiController() override = default; diff --git a/include/SampleClip.h b/include/SampleClip.h index 2aa2c8c9d..92dbf6c54 100644 --- a/include/SampleClip.h +++ b/include/SampleClip.h @@ -79,8 +79,8 @@ public: void setIsPlaying(bool isPlaying); public slots: - void setSampleBuffer(SampleBuffer* sb); - void setSampleFile( const QString & _sf ); + void setSampleBuffer( lmms::SampleBuffer* sb ); + void setSampleFile( const QString & sf ); void updateLength(); void toggleRecord(); void playbackPositionChanged(); diff --git a/plugins/AudioFileProcessor/AudioFileProcessor.cpp b/plugins/AudioFileProcessor/AudioFileProcessor.cpp index 24d5694a6..350540978 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessor.cpp +++ b/plugins/AudioFileProcessor/AudioFileProcessor.cpp @@ -172,9 +172,6 @@ void AudioFileProcessor::playNote( NotePlayHandle * _n, static_cast(m_loopModel.value()))) { applyRelease( _working_buffer, _n ); - instrumentTrack()->processAudioBuffer( _working_buffer, - frames + offset, _n ); - emit isPlaying((static_cast(_n->m_pluginData)->frameIndex())); } else diff --git a/plugins/BitInvader/BitInvader.cpp b/plugins/BitInvader/BitInvader.cpp index 98ef1e97c..4ea73dc71 100644 --- a/plugins/BitInvader/BitInvader.cpp +++ b/plugins/BitInvader/BitInvader.cpp @@ -307,8 +307,6 @@ void BitInvader::playNote( NotePlayHandle * _n, } applyRelease( _working_buffer, _n ); - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/CarlaBase/Carla.cpp b/plugins/CarlaBase/Carla.cpp index faff94b57..819736e92 100644 --- a/plugins/CarlaBase/Carla.cpp +++ b/plugins/CarlaBase/Carla.cpp @@ -508,7 +508,6 @@ void CarlaInstrument::play(sampleFrame* workingBuffer) if (fHandle == nullptr) { - instrumentTrack()->processAudioBuffer(workingBuffer, bufsize, nullptr); return; } @@ -556,8 +555,6 @@ void CarlaInstrument::play(sampleFrame* workingBuffer) workingBuffer[i][0] = buf1[i]; workingBuffer[i][1] = buf2[i]; } - - instrumentTrack()->processAudioBuffer(workingBuffer, bufsize, nullptr); } bool CarlaInstrument::handleMidiEvent(const MidiEvent& event, const TimePos&, f_cnt_t offset) diff --git a/plugins/FreeBoy/FreeBoy.cpp b/plugins/FreeBoy/FreeBoy.cpp index 6450253ee..f2dc95699 100644 --- a/plugins/FreeBoy/FreeBoy.cpp +++ b/plugins/FreeBoy/FreeBoy.cpp @@ -419,7 +419,6 @@ void FreeBoyInstrument::playNote(NotePlayHandle* nph, sampleFrame* workingBuffer } framesLeft -= count; } - instrumentTrack()->processAudioBuffer(workingBuffer, frames + offset, nph); } diff --git a/plugins/GigPlayer/GigPlayer.cpp b/plugins/GigPlayer/GigPlayer.cpp index 0afc2836d..2d67f0ddf 100644 --- a/plugins/GigPlayer/GigPlayer.cpp +++ b/plugins/GigPlayer/GigPlayer.cpp @@ -494,8 +494,6 @@ void GigInstrument::play( sampleFrame * _working_buffer ) _working_buffer[i][0] *= m_gain.value(); _working_buffer[i][1] *= m_gain.value(); } - - instrumentTrack()->processAudioBuffer( _working_buffer, frames, nullptr ); } diff --git a/plugins/Kicker/Kicker.cpp b/plugins/Kicker/Kicker.cpp index ef1d623c1..e6418e2da 100644 --- a/plugins/Kicker/Kicker.cpp +++ b/plugins/Kicker/Kicker.cpp @@ -197,8 +197,6 @@ void KickerInstrument::playNote( NotePlayHandle * _n, _working_buffer[f+offset][1] *= fac; } } - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/LadspaEffect/swh/CMakeLists.txt b/plugins/LadspaEffect/swh/CMakeLists.txt index aec01c22f..a83001177 100644 --- a/plugins/LadspaEffect/swh/CMakeLists.txt +++ b/plugins/LadspaEffect/swh/CMakeLists.txt @@ -16,6 +16,7 @@ SET(COMPILE_FLAGS "${COMPILE_FLAGS} ${PIC_FLAGS}") # Loop over every XML file FILE(GLOB XML_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/ladspa/*.xml") LIST(SORT XML_SOURCES) + FOREACH(_item ${XML_SOURCES}) # Get library name and (soon to be) C file GET_FILENAME_COMPONENT(_plugin "${_item}" NAME_WE) @@ -24,7 +25,7 @@ FOREACH(_item ${XML_SOURCES}) # Coerce XML source file to C ADD_CUSTOM_COMMAND( OUTPUT "${_out_file}" - COMMAND ./makestub.pl "${_item}" > "${_out_file}" + COMMAND "${PERL_EXECUTABLE}" ./makestub.pl "${_item}" > "${_out_file}" DEPENDS "${_item}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ladspa" VERBATIM diff --git a/plugins/Lb302/Lb302.cpp b/plugins/Lb302/Lb302.cpp index b8fff2c0b..ee49442d5 100644 --- a/plugins/Lb302/Lb302.cpp +++ b/plugins/Lb302/Lb302.cpp @@ -790,7 +790,6 @@ void Lb302Synth::play( sampleFrame * _working_buffer ) const fpp_t frames = Engine::audioEngine()->framesPerPeriod(); process( _working_buffer, frames ); - instrumentTrack()->processAudioBuffer( _working_buffer, frames, nullptr ); // release_frame = 0; //removed for issue # 1432 } diff --git a/plugins/Lv2Instrument/Lv2Instrument.cpp b/plugins/Lv2Instrument/Lv2Instrument.cpp index 1e45f4e91..32f81d23c 100644 --- a/plugins/Lv2Instrument/Lv2Instrument.cpp +++ b/plugins/Lv2Instrument/Lv2Instrument.cpp @@ -197,8 +197,6 @@ void Lv2Instrument::play(sampleFrame *buf) copyModelsToLmms(); copyBuffersToLmms(buf, fpp); - - instrumentTrack()->processAudioBuffer(buf, fpp, nullptr); } diff --git a/plugins/Monstro/Monstro.cpp b/plugins/Monstro/Monstro.cpp index f588d6b78..2201e4ed9 100644 --- a/plugins/Monstro/Monstro.cpp +++ b/plugins/Monstro/Monstro.cpp @@ -1040,8 +1040,6 @@ void MonstroInstrument::playNote( NotePlayHandle * _n, ms->renderOutput( frames, _working_buffer + offset ); //applyRelease( _working_buffer, _n ); // we have our own release - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } void MonstroInstrument::deleteNotePluginData( NotePlayHandle * _n ) diff --git a/plugins/Nes/Nes.cpp b/plugins/Nes/Nes.cpp index a530ac19b..47122a0c6 100644 --- a/plugins/Nes/Nes.cpp +++ b/plugins/Nes/Nes.cpp @@ -561,8 +561,6 @@ void NesInstrument::playNote( NotePlayHandle * n, sampleFrame * workingBuffer ) nes->renderOutput( workingBuffer + offset, frames ); applyRelease( workingBuffer, n ); - - instrumentTrack()->processAudioBuffer( workingBuffer, frames + offset, n ); } diff --git a/plugins/OpulenZ/OpulenZ.cpp b/plugins/OpulenZ/OpulenZ.cpp index 64f609995..d90d5f343 100644 --- a/plugins/OpulenZ/OpulenZ.cpp +++ b/plugins/OpulenZ/OpulenZ.cpp @@ -412,10 +412,6 @@ void OpulenzInstrument::play( sampleFrame * _working_buffer ) } } emulatorMutex.unlock(); - - // Throw the data to the track... - instrumentTrack()->processAudioBuffer( _working_buffer, frameCount, nullptr ); - } diff --git a/plugins/Organic/Organic.cpp b/plugins/Organic/Organic.cpp index f8a2b0d13..a70da6421 100644 --- a/plugins/Organic/Organic.cpp +++ b/plugins/Organic/Organic.cpp @@ -312,8 +312,6 @@ void OrganicInstrument::playNote( NotePlayHandle * _n, } // -- -- - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/Patman/Patman.cpp b/plugins/Patman/Patman.cpp index 29e5dc6e6..e525498a5 100644 --- a/plugins/Patman/Patman.cpp +++ b/plugins/Patman/Patman.cpp @@ -157,8 +157,6 @@ void PatmanInstrument::playNote( NotePlayHandle * _n, play_freq, m_loopedModel.value() ? Sample::Loop::On : Sample::Loop::Off)) { applyRelease( _working_buffer, _n ); - instrumentTrack()->processAudioBuffer( _working_buffer, - frames + offset, _n ); } else { @@ -436,7 +434,7 @@ namespace gui PatmanView::PatmanView( Instrument * _instrument, QWidget * _parent ) : InstrumentViewFixedSize( _instrument, _parent ), - m_pi( nullptr ) + m_pi(castModel()) { setAutoFillBackground( true ); QPalette pal; @@ -477,7 +475,15 @@ PatmanView::PatmanView( Instrument * _instrument, QWidget * _parent ) : "tune_off" ) ); m_tuneButton->setToolTip(tr("Tune mode")); - m_displayFilename = tr( "No file selected" ); + + if (m_pi->m_patchFile.isEmpty()) + { + m_displayFilename = tr("No file selected"); + } + else + { + updateFilename(); + } setAcceptDrops( true ); } diff --git a/plugins/Sf2Player/Sf2Player.cpp b/plugins/Sf2Player/Sf2Player.cpp index d46af5e4f..79bd4b976 100644 --- a/plugins/Sf2Player/Sf2Player.cpp +++ b/plugins/Sf2Player/Sf2Player.cpp @@ -848,7 +848,6 @@ void Sf2Instrument::play( sampleFrame * _working_buffer ) if( m_playingNotes.isEmpty() ) { renderFrames( frames, _working_buffer ); - instrumentTrack()->processAudioBuffer( _working_buffer, frames, nullptr ); return; } @@ -906,7 +905,6 @@ void Sf2Instrument::play( sampleFrame * _working_buffer ) { renderFrames( frames - currentFrame, _working_buffer + currentFrame ); } - instrumentTrack()->processAudioBuffer( _working_buffer, frames, nullptr ); } diff --git a/plugins/Sfxr/Sfxr.cpp b/plugins/Sfxr/Sfxr.cpp index fc39ea0fa..e79b8e2ad 100644 --- a/plugins/Sfxr/Sfxr.cpp +++ b/plugins/Sfxr/Sfxr.cpp @@ -480,9 +480,6 @@ void SfxrInstrument::playNote( NotePlayHandle * _n, sampleFrame * _working_buffe delete[] pitchedBuffer; applyRelease( _working_buffer, _n ); - - instrumentTrack()->processAudioBuffer( _working_buffer, frameNum + offset, _n ); - } diff --git a/plugins/Sid/CMakeLists.txt b/plugins/Sid/CMakeLists.txt index c9fce7bb7..c771fc66d 100644 --- a/plugins/Sid/CMakeLists.txt +++ b/plugins/Sid/CMakeLists.txt @@ -1,51 +1,14 @@ INCLUDE(BuildPlugin) -INCLUDE_DIRECTORIES(resid) +if(NOT LMMS_HAVE_SID) + return() +endif() BUILD_PLUGIN(sid SidInstrument.cpp SidInstrument.h - resid/envelope.h - resid/extfilt.h - resid/filter.h - resid/pot.h - resid/siddefs.h - resid/sid.h - resid/spline.h - resid/voice.h - resid/wave.h - resid/envelope.cc - resid/extfilt.cc - resid/filter.cc - resid/pot.cc - resid/sid.cc - resid/version.cc - resid/voice.cc - resid/wave6581_PS_.cc - resid/wave6581_PST.cc - resid/wave6581_P_T.cc - resid/wave6581__ST.cc - resid/wave8580_PS_.cc - resid/wave8580_PST.cc - resid/wave8580_P_T.cc - resid/wave8580__ST.cc - resid/wave.cc MOCFILES SidInstrument.h EMBEDDED_RESOURCES *.png) -# Parse VERSION -FILE(READ "resid/CMakeLists.txt" lines) -STRING(REGEX MATCH "set\\(MAJOR_VER [A-Za-z0-9_]*\\)" MAJOR_RAW ${lines}) -STRING(REGEX MATCH "set\\(MINOR_VER [A-Za-z0-9_]*\\)" MINOR_RAW ${lines}) -STRING(REGEX MATCH "set\\(PATCH_VER [A-Za-z0-9_]*\\)" PATCH_RAW ${lines}) -SEPARATE_ARGUMENTS(MAJOR_RAW) -SEPARATE_ARGUMENTS(MINOR_RAW) -SEPARATE_ARGUMENTS(PATCH_RAW) -LIST(GET MAJOR_RAW 1 MAJOR_RAW) -LIST(GET MINOR_RAW 1 MINOR_RAW) -LIST(GET PATCH_RAW 1 PATCH_RAW) -STRING(REPLACE ")" "" MAJOR_VER "${MAJOR_RAW}") -STRING(REPLACE ")" "" MINOR_VER "${MINOR_RAW}") -STRING(REPLACE ")" "" PATCH_VER "${PATCH_RAW}") - -TARGET_COMPILE_DEFINITIONS(sid PRIVATE VERSION="${MAJOR_VER}.${MINOR_VER}.${PATCH_VER}") +add_subdirectory(resid) +target_link_libraries(sid resid) diff --git a/plugins/Sid/SidInstrument.cpp b/plugins/Sid/SidInstrument.cpp index f663c3b69..7f9edf13f 100644 --- a/plugins/Sid/SidInstrument.cpp +++ b/plugins/Sid/SidInstrument.cpp @@ -239,7 +239,7 @@ f_cnt_t SidInstrument::desiredReleaseFrames() const -static int sid_fillbuffer(unsigned char* sidreg, SID *sid, int tdelta, short *ptr, int samples) +static int sid_fillbuffer(unsigned char* sidreg, reSID::SID *sid, int tdelta, short *ptr, int samples) { int tdelta2; int result; @@ -302,9 +302,9 @@ void SidInstrument::playNote( NotePlayHandle * _n, if (!_n->m_pluginData) { - SID *sid = new SID(); - sid->set_sampling_parameters( clockrate, SAMPLE_FAST, samplerate ); - sid->set_chip_model( MOS8580 ); + auto sid = new reSID::SID(); + sid->set_sampling_parameters(clockrate, reSID::SAMPLE_FAST, samplerate); + sid->set_chip_model(reSID::MOS8580); sid->enable_filter( true ); sid->reset(); _n->m_pluginData = sid; @@ -312,7 +312,7 @@ void SidInstrument::playNote( NotePlayHandle * _n, const fpp_t frames = _n->framesLeftForCurrentPeriod(); const f_cnt_t offset = _n->noteOffset(); - SID *sid = static_cast( _n->m_pluginData ); + auto sid = static_cast(_n->m_pluginData); int delta_t = clockrate * frames / samplerate + 4; // avoid variable length array for msvc compat auto buf = reinterpret_cast(_working_buffer + offset); @@ -325,20 +325,20 @@ void SidInstrument::playNote( NotePlayHandle * _n, if( (ChipModel)m_chipModel.value() == ChipModel::MOS6581 ) { - sid->set_chip_model( MOS6581 ); + sid->set_chip_model(reSID::MOS6581); } else { - sid->set_chip_model( MOS8580 ); + sid->set_chip_model(reSID::MOS8580); } // voices - reg8 data8 = 0; - reg8 data16 = 0; - reg8 base = 0; + reSID::reg8 data8 = 0; + reSID::reg16 data16 = 0; + size_t base = 0; float freq = 0.0; float note = 0.0; - for( reg8 i = 0 ; i < 3 ; ++i ) + for (size_t i = 0; i < 3; ++i) { base = i*7; // freq ( Fn = Fout / Fclk * 16777216 ) + coarse detuning @@ -429,8 +429,6 @@ void SidInstrument::playNote( NotePlayHandle * _n, _working_buffer[frame+offset][ch] = s; } } - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } @@ -438,7 +436,7 @@ void SidInstrument::playNote( NotePlayHandle * _n, void SidInstrument::deleteNotePluginData( NotePlayHandle * _n ) { - delete static_cast( _n->m_pluginData ); + delete static_cast(_n->m_pluginData); } diff --git a/plugins/Sid/resid b/plugins/Sid/resid deleted file mode 160000 index 02afcc5ce..000000000 --- a/plugins/Sid/resid +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 02afcc5cefac34bd0c665dc0fa6b748d238c1831 diff --git a/plugins/Sid/resid/CMakeLists.txt b/plugins/Sid/resid/CMakeLists.txt new file mode 100644 index 000000000..bb39e3d16 --- /dev/null +++ b/plugins/Sid/resid/CMakeLists.txt @@ -0,0 +1,68 @@ +# These are the defaults +set(RESID_INLINING 1) +set(RESID_INLINE inline) +set(RESID_BRANCH_HINTS 1) +set(NEW_8580_FILTER 0) + +set(HAVE_BOOL 1) +set(HAVE_LOG1P 1) + +if(CMAKE_CXX_COMPILER_ID MATCHES "GCC|Clang") + set(HAVE_BUILTIN_EXPECT 1) +else() + set(HAVE_BUILTIN_EXPECT 0) +endif() + +configure_file(resid/siddefs.h.in resid/siddefs.h @ONLY) + +add_library(resid_objects OBJECT + resid/sid.cc + resid/voice.cc + resid/wave.cc + resid/envelope.cc + resid/filter.cc + resid/dac.cc + resid/extfilt.cc + resid/pot.cc + resid/version.cc +) + +target_include_directories(resid_objects PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/resid" + "${CMAKE_CURRENT_BINARY_DIR}/resid" +) +target_compile_definitions(resid_objects PUBLIC VERSION="1.0") + +set(RESID_WAVES + wave6581_PST + wave6581_PS_ + wave6581_P_T + wave6581__ST + wave8580_PST + wave8580_PS_ + wave8580_P_T + wave8580__ST +) + +set(RESID_SAMP2SRC_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/resid/samp2src.pl) +foreach(WAVE_DATA IN LISTS RESID_WAVES) + set(WAVE_DATA_IN ${CMAKE_CURRENT_SOURCE_DIR}/resid/${WAVE_DATA}.dat) + set(WAVE_SRC_OUT ${CMAKE_CURRENT_BINARY_DIR}/resid/${WAVE_DATA}.h) + set(WAVE_COMMAND + "${PERL_EXECUTABLE}" + "${RESID_SAMP2SRC_SCRIPT}" + "${WAVE_DATA}" + "${WAVE_DATA_IN}" + "${WAVE_SRC_OUT}" + ) + add_custom_command(OUTPUT ${WAVE_SRC_OUT} COMMAND ${WAVE_COMMAND} VERBATIM) + target_sources(resid_objects PUBLIC ${WAVE_SRC_OUT}) +endforeach() + +# TODO CMake 3.12: Use target_link_libraries() to propagate usage requirements directly to sid plugin +add_library(resid INTERFACE) + +target_sources(resid INTERFACE $) + +get_target_property(resid_includes resid_objects INCLUDE_DIRECTORIES) +target_include_directories(resid INTERFACE ${resid_includes}) diff --git a/plugins/Sid/resid/resid b/plugins/Sid/resid/resid new file mode 160000 index 000000000..ef72462f5 --- /dev/null +++ b/plugins/Sid/resid/resid @@ -0,0 +1 @@ +Subproject commit ef72462f5fa0682d099413512b764ae479e77f9b diff --git a/plugins/Stk/Mallets/Mallets.cpp b/plugins/Stk/Mallets/Mallets.cpp index 4fb077de5..b746e9491 100644 --- a/plugins/Stk/Mallets/Mallets.cpp +++ b/plugins/Stk/Mallets/Mallets.cpp @@ -359,8 +359,6 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, _working_buffer[frame][1] = ps->nextSampleRight() * ( m_scalers[p] + add_scale ); } - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/TripleOscillator/TripleOscillator.cpp b/plugins/TripleOscillator/TripleOscillator.cpp index c8d7942a7..968f6b25b 100644 --- a/plugins/TripleOscillator/TripleOscillator.cpp +++ b/plugins/TripleOscillator/TripleOscillator.cpp @@ -387,8 +387,6 @@ void TripleOscillator::playNote( NotePlayHandle * _n, applyFadeIn(_working_buffer, _n); applyRelease( _working_buffer, _n ); - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/Vestige/Vestige.cpp b/plugins/Vestige/Vestige.cpp index dd8e9cbef..a696a4b2d 100644 --- a/plugins/Vestige/Vestige.cpp +++ b/plugins/Vestige/Vestige.cpp @@ -399,8 +399,6 @@ void VestigeInstrument::play( sampleFrame * _buf ) { if (!m_pluginMutex.tryLock(Engine::getSong()->isExporting() ? -1 : 0)) {return;} - const fpp_t frames = Engine::audioEngine()->framesPerPeriod(); - if( m_plugin == nullptr ) { m_pluginMutex.unlock(); @@ -409,8 +407,6 @@ void VestigeInstrument::play( sampleFrame * _buf ) m_plugin->process( nullptr, _buf ); - instrumentTrack()->processAudioBuffer( _buf, frames, nullptr ); - m_pluginMutex.unlock(); } diff --git a/plugins/Vibed/Vibed.cpp b/plugins/Vibed/Vibed.cpp index 3ed51fe79..ad6a3942a 100644 --- a/plugins/Vibed/Vibed.cpp +++ b/plugins/Vibed/Vibed.cpp @@ -251,8 +251,6 @@ void Vibed::playNote(NotePlayHandle* n, sampleFrame* workingBuffer) } } } - - instrumentTrack()->processAudioBuffer(workingBuffer, frames + offset, n); } void Vibed::deleteNotePluginData(NotePlayHandle* n) diff --git a/plugins/VstEffect/VstSubPluginFeatures.cpp b/plugins/VstEffect/VstSubPluginFeatures.cpp index f929b5526..7eab7a9bf 100644 --- a/plugins/VstEffect/VstSubPluginFeatures.cpp +++ b/plugins/VstEffect/VstSubPluginFeatures.cpp @@ -82,7 +82,11 @@ void VstSubPluginFeatures::addPluginsFromDir( QStringList* filenames, QString pa } } QStringList dlls = QDir( ConfigManager::inst()->vstDir() + path ). - entryList( QStringList() << "*.dll", + entryList( QStringList() << "*.dll" +#ifdef LMMS_BUILD_LINUX + << "*.so" +#endif + , QDir::Files, QDir::Name ); for( int i = 0; i < dlls.size(); i++ ) { diff --git a/plugins/Watsyn/Watsyn.cpp b/plugins/Watsyn/Watsyn.cpp index 7603a9c1b..8e49942e1 100644 --- a/plugins/Watsyn/Watsyn.cpp +++ b/plugins/Watsyn/Watsyn.cpp @@ -445,8 +445,6 @@ void WatsynInstrument::playNote( NotePlayHandle * _n, } applyRelease( _working_buffer, _n ); - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/Xpressive/Xpressive.cpp b/plugins/Xpressive/Xpressive.cpp index b1a17a1ce..babc37231 100644 --- a/plugins/Xpressive/Xpressive.cpp +++ b/plugins/Xpressive/Xpressive.cpp @@ -233,8 +233,6 @@ void Xpressive::playNote(NotePlayHandle* nph, sampleFrame* working_buffer) { const f_cnt_t offset = nph->noteOffset(); ps->renderOutput(frames, working_buffer + offset); - - instrumentTrack()->processAudioBuffer(working_buffer, frames + offset, nph); } void Xpressive::deleteNotePluginData(NotePlayHandle* nph) { diff --git a/plugins/ZynAddSubFx/ZynAddSubFx.cpp b/plugins/ZynAddSubFx/ZynAddSubFx.cpp index 2ec864592..be38bcb79 100644 --- a/plugins/ZynAddSubFx/ZynAddSubFx.cpp +++ b/plugins/ZynAddSubFx/ZynAddSubFx.cpp @@ -341,7 +341,6 @@ void ZynAddSubFxInstrument::play( sampleFrame * _buf ) m_plugin->processAudio( _buf ); } m_pluginMutex.unlock(); - instrumentTrack()->processAudioBuffer( _buf, Engine::audioEngine()->framesPerPeriod(), nullptr ); } diff --git a/src/core/AudioEngine.cpp b/src/core/AudioEngine.cpp index 2d077babc..29c54647c 100644 --- a/src/core/AudioEngine.cpp +++ b/src/core/AudioEngine.cpp @@ -701,7 +701,10 @@ void AudioEngine::removeAudioPort(AudioPort * port) bool AudioEngine::addPlayHandle( PlayHandle* handle ) { - if( criticalXRuns() == false ) + // Only add play handles if we have the CPU capacity to process them. + // Instrument play handles are not added during playback, but when the + // associated instrument is created, so add those unconditionally. + if (handle->type() == PlayHandle::Type::InstrumentPlayHandle || !criticalXRuns()) { m_newPlayHandles.push( handle ); handle->audioPort()->addPlayHandle( handle ); diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index 5c98ec81c..8d0a8dca4 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -79,6 +79,7 @@ const std::vector DataFile::UPGRADE_METHODS = { &DataFile::upgrade_automationNodes , &DataFile::upgrade_extendedNoteRange, &DataFile::upgrade_defaultTripleOscillatorHQ, &DataFile::upgrade_mixerRename , &DataFile::upgrade_bbTcoRename, + &DataFile::upgrade_sampleAndHold , &DataFile::upgrade_midiCCIndexing }; // Vector of all versions that have upgrade routines. @@ -1703,26 +1704,56 @@ void DataFile::upgrade_mixerRename() { // Change nodename to QDomNodeList fxmixer = elementsByTagName("fxmixer"); - for (int i = 0; !fxmixer.item(i).isNull(); ++i) + for (int i = 0; i < fxmixer.length(); ++i) { - fxmixer.item(i).toElement().setTagName("mixer"); + auto item = fxmixer.item(i).toElement(); + if (item.isNull()) + { + continue; + } + item.setTagName("mixer"); } // Change nodename to QDomNodeList fxchannel = elementsByTagName("fxchannel"); - for (int i = 0; !fxchannel.item(i).isNull(); ++i) + for (int i = 0; i < fxchannel.length(); ++i) { - fxchannel.item(i).toElement().setTagName("mixerchannel"); + auto item = fxchannel.item(i).toElement(); + if (item.isNull()) + { + continue; + } + item.setTagName("mixerchannel"); } // Change the attribute fxch of element to mixch QDomNodeList fxch = elementsByTagName("instrumenttrack"); - for(int i = 0; !fxch.item(i).isNull(); ++i) + for (int i = 0; i < fxch.length(); ++i) { - if(fxch.item(i).toElement().hasAttribute("fxch")) + auto item = fxch.item(i).toElement(); + if (item.isNull()) { - fxch.item(i).toElement().setAttribute("mixch", fxch.item(i).toElement().attribute("fxch")); - fxch.item(i).toElement().removeAttribute("fxch"); + continue; + } + if (item.hasAttribute("fxch")) + { + item.setAttribute("mixch", item.attribute("fxch")); + item.removeAttribute("fxch"); + } + } + // Change the attribute fxch of element to mixch + fxch = elementsByTagName("sampletrack"); + for (int i = 0; i < fxch.length(); ++i) + { + auto item = fxch.item(i).toElement(); + if (item.isNull()) + { + continue; + } + if (item.hasAttribute("fxch")) + { + item.setAttribute("mixch", item.attribute("fxch")); + item.removeAttribute("fxch"); } } } @@ -1762,6 +1793,44 @@ void DataFile::upgrade_bbTcoRename() } +// Set LFO speed to 0.01 on projects made before sample-and-hold PR +void DataFile::upgrade_sampleAndHold() +{ + QDomNodeList elements = elementsByTagName("lfocontroller"); + for (int i = 0; i < elements.length(); ++i) + { + if (elements.item(i).isNull()) { continue; } + auto e = elements.item(i).toElement(); + // Correct old random wave LFO speeds + if (e.attribute("wave").toInt() == 6) + { + e.setAttribute("speed",0.01f); + } + } +} + +//! Update MIDI CC indexes, so that they are counted from 0. Older releases of LMMS +//! count the CCs from 1. +void DataFile::upgrade_midiCCIndexing() +{ + static constexpr std::array attributesToUpdate{"inputcontroller", "outputcontroller"}; + + QDomNodeList elements = elementsByTagName("Midicontroller"); + for(int i = 0; i < elements.length(); i++) + { + if (elements.item(i).isNull()) { continue; } + auto element = elements.item(i).toElement(); + for (const char* attrName : attributesToUpdate) + { + if (element.hasAttribute(attrName)) + { + int cc = element.attribute(attrName).toInt(); + element.setAttribute(attrName, cc - 1); + } + } + } +} + void DataFile::upgrade() { // Runs all necessary upgrade methods diff --git a/src/core/InstrumentPlayHandle.cpp b/src/core/InstrumentPlayHandle.cpp index e1a9d9d65..097719ad8 100644 --- a/src/core/InstrumentPlayHandle.cpp +++ b/src/core/InstrumentPlayHandle.cpp @@ -24,18 +24,57 @@ #include "InstrumentPlayHandle.h" +#include "Instrument.h" #include "InstrumentTrack.h" +#include "Engine.h" +#include "AudioEngine.h" namespace lmms { -InstrumentPlayHandle::InstrumentPlayHandle( Instrument * instrument, InstrumentTrack* instrumentTrack ) : - PlayHandle( Type::InstrumentPlayHandle ), - m_instrument( instrument ) +InstrumentPlayHandle::InstrumentPlayHandle(Instrument * instrument, InstrumentTrack* instrumentTrack) : + PlayHandle(Type::InstrumentPlayHandle), + m_instrument(instrument) { - setAudioPort( instrumentTrack->audioPort() ); + setAudioPort(instrumentTrack->audioPort()); +} + +void InstrumentPlayHandle::play(sampleFrame * working_buffer) +{ + InstrumentTrack * instrumentTrack = m_instrument->instrumentTrack(); + + // ensure that all our nph's have been processed first + auto nphv = NotePlayHandle::nphsOfInstrumentTrack(instrumentTrack, true); + + bool nphsLeft; + do + { + nphsLeft = false; + for (const NotePlayHandle * constNotePlayHandle : nphv) + { + if (constNotePlayHandle->state() != ThreadableJob::ProcessingState::Done && + !constNotePlayHandle->isFinished()) + { + nphsLeft = true; + NotePlayHandle * notePlayHandle = const_cast(constNotePlayHandle); + notePlayHandle->process(); + } + } + } + while (nphsLeft); + + m_instrument->play(working_buffer); + + // Process the audio buffer that the instrument has just worked on... + const fpp_t frames = Engine::audioEngine()->framesPerPeriod(); + instrumentTrack->processAudioBuffer(working_buffer, frames, nullptr); +} + +bool InstrumentPlayHandle::isFromTrack(const Track* track) const +{ + return m_instrument->isFromTrack(track); } -} // namespace lmms \ No newline at end of file +} // namespace lmms diff --git a/src/core/LfoController.cpp b/src/core/LfoController.cpp index 7aeee65a6..6532b5741 100644 --- a/src/core/LfoController.cpp +++ b/src/core/LfoController.cpp @@ -88,6 +88,7 @@ void LfoController::updateValueBuffer() { m_phaseOffset = m_phaseModel.value() / 360.0; float phase = m_currentPhase + m_phaseOffset; + float phasePrev = 0.0f; // roll phase up until we're in sync with period counter m_bufferLastUpdated++; @@ -102,21 +103,45 @@ void LfoController::updateValueBuffer() ValueBuffer *amountBuffer = m_amountModel.valueBuffer(); int amountInc = amountBuffer ? 1 : 0; float *amountPtr = amountBuffer ? &(amountBuffer->values()[ 0 ] ) : &amount; + Oscillator::WaveShape waveshape = static_cast(m_waveModel.value()); for( float& f : m_valueBuffer ) { - const float currentSample = m_sampleFunction != nullptr - ? m_sampleFunction( phase ) - // TODO C++20: Deprecated, use std::atomic instead - : Oscillator::userWaveSample(std::atomic_load(&m_userDefSampleBuffer).get(), phase); + float currentSample = 0; + switch (waveshape) + { + case Oscillator::WaveShape::WhiteNoise: + { + if (absFraction(phase) < absFraction(phasePrev)) + { + // Resample when phase period has completed + m_heldSample = m_sampleFunction(phase); + } + currentSample = m_heldSample; + break; + } + case Oscillator::WaveShape::UserDefined: + { + currentSample = Oscillator::userWaveSample(std::atomic_load(&m_userDefSampleBuffer).get(), phase); + break; + } + default: + { + if (m_sampleFunction != nullptr) + { + currentSample = m_sampleFunction(phase); + } + } + } f = std::clamp(m_baseModel.value() + (*amountPtr * currentSample / 2.0f), 0.0f, 1.0f); + phasePrev = phase; phase += 1.0 / m_duration; amountPtr += amountInc; } - m_currentPhase = absFraction( phase - m_phaseOffset ); + m_currentPhase = absFraction(phase - m_phaseOffset); m_bufferLastUpdated = s_periods; } diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 57819e27a..2a39888e4 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -196,4 +196,4 @@ auto SampleBuffer::empty() const -> bool return m_data.empty(); } -} // namespace lmms \ No newline at end of file +} // namespace lmms diff --git a/src/core/SampleClip.cpp b/src/core/SampleClip.cpp index 7dddfc104..2c76c0805 100644 --- a/src/core/SampleClip.cpp +++ b/src/core/SampleClip.cpp @@ -137,25 +137,29 @@ void SampleClip::setSampleBuffer( SampleBuffer* sb ) -void SampleClip::setSampleFile( const QString & _sf ) +void SampleClip::setSampleFile(const QString & sf) { - int length; - if ( _sf.isEmpty() ) - { //When creating an empty sample clip make it a bar long - float nom = Engine::getSong()->getTimeSigModel().getNumerator(); - float den = Engine::getSong()->getTimeSigModel().getDenominator(); - length = DefaultTicksPerBar * ( nom / den ); - } - else - { //Otherwise set it to the sample's length - auto buffer = gui::SampleLoader::createBufferFromFile(_sf); + int length = 0; + + if (!sf.isEmpty()) + { + //Otherwise set it to the sample's length + auto buffer = gui::SampleLoader::createBufferFromFile(sf); // TODO C++20: Deprecated, use std::atomic instead std::atomic_store(&m_sample, std::make_shared(std::move(buffer))); length = sampleLength(); } - changeLength(length); - setStartTimeOffset( 0 ); + if (length == 0) + { + //If there is no sample, make the clip a bar long + float nom = Engine::getSong()->getTimeSigModel().getNumerator(); + float den = Engine::getSong()->getTimeSigModel().getDenominator(); + length = DefaultTicksPerBar * (nom / den); + } + + changeLength(length); + setStartTimeOffset(0); emit sampleChanged(); emit playbackPositionChanged(); diff --git a/src/core/midi/MidiController.cpp b/src/core/midi/MidiController.cpp index d7c89e940..0ae76d352 100644 --- a/src/core/midi/MidiController.cpp +++ b/src/core/midi/MidiController.cpp @@ -72,21 +72,20 @@ void MidiController::updateName() -void MidiController::processInEvent( const MidiEvent& event, const TimePos& time, f_cnt_t offset ) +void MidiController::processInEvent(const MidiEvent& event, const TimePos& time, f_cnt_t offset) { unsigned char controllerNum; - switch( event.type() ) + switch(event.type()) { case MidiControlChange: controllerNum = event.controllerNumber(); - if( m_midiPort.inputController() == controllerNum + 1 && - ( m_midiPort.inputChannel() == event.channel() + 1 || - m_midiPort.inputChannel() == 0 ) ) + if (m_midiPort.inputController() == controllerNum && + (m_midiPort.inputChannel() == event.channel() + 1 || m_midiPort.inputChannel() == 0)) { unsigned char val = event.controllerValue(); m_previousValue = m_lastValue; - m_lastValue = (float)( val ) / 127.0f; + m_lastValue = static_cast(val) / 127.0f; emit valueChanged(); } break; diff --git a/src/core/midi/MidiPort.cpp b/src/core/midi/MidiPort.cpp index c7c947e8e..24263f913 100644 --- a/src/core/midi/MidiPort.cpp +++ b/src/core/midi/MidiPort.cpp @@ -31,6 +31,7 @@ #include "MidiEventProcessor.h" #include "Note.h" #include "Song.h" +#include "MidiController.h" namespace lmms @@ -54,8 +55,8 @@ MidiPort::MidiPort( const QString& name, m_mode( mode ), m_inputChannelModel( 0, 0, MidiChannelCount, this, tr( "Input channel" ) ), m_outputChannelModel( 1, 0, MidiChannelCount, this, tr( "Output channel" ) ), - m_inputControllerModel( 0, 0, MidiControllerCount, this, tr( "Input controller" ) ), - m_outputControllerModel( 0, 0, MidiControllerCount, this, tr( "Output controller" ) ), + m_inputControllerModel(MidiController::NONE, MidiController::NONE, MidiControllerCount - 1, this, tr( "Input controller" )), + m_outputControllerModel(MidiController::NONE, MidiController::NONE, MidiControllerCount - 1, this, tr( "Output controller" )), m_fixedInputVelocityModel( -1, -1, MidiMaxVelocity, this, tr( "Fixed input velocity" ) ), m_fixedOutputVelocityModel( -1, -1, MidiMaxVelocity, this, tr( "Fixed output velocity" ) ), m_fixedOutputNoteModel( -1, -1, MidiMaxKey, this, tr( "Fixed output note" ) ), @@ -436,4 +437,4 @@ void MidiPort::invalidateCilent() } -} // namespace lmms \ No newline at end of file +} // namespace lmms diff --git a/src/gui/FileBrowser.cpp b/src/gui/FileBrowser.cpp index c0763d542..181e67cd7 100644 --- a/src/gui/FileBrowser.cpp +++ b/src/gui/FileBrowser.cpp @@ -23,7 +23,6 @@ * */ - #include #include #include @@ -126,7 +125,7 @@ FileBrowser::FileBrowser(const QString & directories, const QString & filter, m_filterEdit->setPlaceholderText( tr("Search") ); m_filterEdit->setClearButtonEnabled( true ); connect( m_filterEdit, SIGNAL( textEdited( const QString& ) ), - this, SLOT( filterItems( const QString& ) ) ); + this, SLOT( filterAndExpandItems( const QString& ) ) ); auto reload_btn = new QPushButton(embed::getIconPixmap("reload"), QString(), searchWidget); reload_btn->setToolTip( tr( "Refresh list" ) ); @@ -145,53 +144,95 @@ FileBrowser::FileBrowser(const QString & directories, const QString & filter, auto filterFocusShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this, SLOT(giveFocusToFilter())); filterFocusShortcut->setContext(Qt::WidgetWithChildrenShortcut); + m_previousFilterValue = ""; + reloadTree(); show(); } -bool FileBrowser::filterItems( const QString & filter, QTreeWidgetItem * item ) +void FileBrowser::saveDirectoriesStates() +{ + m_savedExpandedDirs = m_fileBrowserTreeWidget->expandedDirs(); +} + +void FileBrowser::restoreDirectoriesStates() { - // call with item=NULL to filter the entire tree + expandItems(nullptr, m_savedExpandedDirs); +} + +bool FileBrowser::filterAndExpandItems(const QString & filter, QTreeWidgetItem * item) +{ + // Call with item = nullptr to filter the entire tree + + if (item == nullptr) + { + // First search character so need to save current expanded directories + if (m_previousFilterValue.isEmpty()) + { + saveDirectoriesStates(); + } + + m_previousFilterValue = filter; + } + + if (filter.isEmpty()) + { + // Restore previous expanded directories + if (item == nullptr) + { + restoreDirectoriesStates(); + } + + return false; + } + bool anyMatched = false; int numChildren = item ? item->childCount() : m_fileBrowserTreeWidget->topLevelItemCount(); - for( int i = 0; i < numChildren; ++i ) + + for (int i = 0; i < numChildren; ++i) { QTreeWidgetItem * it = item ? item->child( i ) : m_fileBrowserTreeWidget->topLevelItem(i); - // is directory? - if( it->childCount() ) + auto d = dynamic_cast(it); + if (d) { - // matches filter? - if( it->text( 0 ). - contains( filter, Qt::CaseInsensitive ) ) + if (it->text(0).contains(filter, Qt::CaseInsensitive)) { - // yes, then show everything below - it->setHidden( false ); - filterItems( QString(), it ); + it->setHidden(false); + it->setExpanded(true); + filterAndExpandItems(QString(), it); anyMatched = true; } else { - // only show if item below matches filter - bool didMatch = filterItems( filter, it ); - it->setHidden( !didMatch ); + // Expanding is required when recursive to load in its contents, even if it's collapsed right afterward + it->setExpanded(true); + + bool didMatch = filterAndExpandItems(filter, it); + it->setHidden(!didMatch); + it->setExpanded(didMatch); anyMatched = anyMatched || didMatch; } } - // a standard item (i.e. no file or directory item?) - else if( it->type() == QTreeWidgetItem::Type ) - { - // hide if there's any filter - it->setHidden( !filter.isEmpty() ); - } + else { - // file matches filter? - bool didMatch = it->text( 0 ). - contains( filter, Qt::CaseInsensitive ); - it->setHidden( !didMatch ); - anyMatched = anyMatched || didMatch; + auto f = dynamic_cast(it); + if (f) + { + // File + bool didMatch = it->text(0).contains(filter, Qt::CaseInsensitive); + it->setHidden(!didMatch); + anyMatched = anyMatched || didMatch; + } + + // A standard item (i.e. no file or directory item?) + else + { + // Hide if there's any filter + it->setHidden(!filter.isEmpty()); + } } } @@ -201,15 +242,20 @@ bool FileBrowser::filterItems( const QString & filter, QTreeWidgetItem * item ) void FileBrowser::reloadTree() { - QList expandedDirs = m_fileBrowserTreeWidget->expandedDirs(); - const QString text = m_filterEdit->text(); - m_filterEdit->clear(); + if (m_filterEdit->text().isEmpty()) + { + saveDirectoriesStates(); + } + m_fileBrowserTreeWidget->clear(); + QStringList paths = m_directories.split('*'); + if (m_showUserContent && !m_showUserContent->isChecked()) { paths.removeAll(m_userDir); } + if (m_showFactoryContent && !m_showFactoryContent->isChecked()) { paths.removeAll(m_factoryDir); @@ -222,9 +268,15 @@ void FileBrowser::reloadTree() addItems(path); } } - expandItems(nullptr, expandedDirs); - m_filterEdit->setText( text ); - filterItems( text ); + + if (m_filterEdit->text().isEmpty()) + { + restoreDirectoriesStates(); + } + else + { + filterAndExpandItems(m_filterEdit->text()); + } } @@ -240,12 +292,16 @@ void FileBrowser::expandItems(QTreeWidgetItem* item, QList expandedDirs { // Expanding is required when recursive to load in its contents, even if it's collapsed right afterward if (m_recurse) { d->setExpanded(true); } + d->setExpanded(expandedDirs.contains(d->fullName())); + if (m_recurse && it->childCount()) { expandItems(it, expandedDirs); } } + + it->setHidden(false); } } @@ -416,8 +472,6 @@ QList FileBrowserTreeWidget::expandedDirs( QTreeWidgetItem * item ) con } - - void FileBrowserTreeWidget::keyPressEvent(QKeyEvent * ke ) { // Shorter names for some commonly used properties of the event diff --git a/src/gui/SampleTrackWindow.cpp b/src/gui/SampleTrackWindow.cpp index 68b5eb8a2..f6d7f9ea1 100644 --- a/src/gui/SampleTrackWindow.cpp +++ b/src/gui/SampleTrackWindow.cpp @@ -84,7 +84,6 @@ SampleTrackWindow::SampleTrackWindow(SampleTrackView * tv) : // setup line edit for changing sample track name m_nameLineEdit = new QLineEdit; - m_nameLineEdit->setFont(pointSize<9>(m_nameLineEdit->font())); connect(m_nameLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(textChanged(const QString&))); diff --git a/src/gui/clips/MidiClipView.cpp b/src/gui/clips/MidiClipView.cpp index 79c4cd73d..151df8d3c 100644 --- a/src/gui/clips/MidiClipView.cpp +++ b/src/gui/clips/MidiClipView.cpp @@ -523,7 +523,8 @@ void MidiClipView::paintEvent( QPaintEvent * ) p.scale(width(), height() - distanceToTop - 2 * notesBorder); // set colour based on mute status - QColor noteFillColor = muted ? getMutedNoteFillColor() : getNoteFillColor(); + QColor noteFillColor = muted ? getMutedNoteFillColor().lighter(200) + : (c.lightness() > 175 ? getNoteFillColor().darker(400) : getNoteFillColor()); QColor noteBorderColor = muted ? getMutedNoteBorderColor() : ( m_clip->hasColor() ? c.lighter( 200 ) : getNoteBorderColor() ); diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index b38335995..cef2205d2 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -130,7 +130,10 @@ QPixmap* PianoRoll::s_toolKnife = nullptr; SimpleTextFloat * PianoRoll::s_textFloat = nullptr; -static std::array s_noteStrings {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; +static std::array s_noteStrings { + "C", "C\u266F / D\u266D", "D", "D\u266F / E\u266D", "E", "F", "F\u266F / G\u266D", + "G", "G\u266F / A\u266D", "A", "A\u266F / B\u266D", "B" +}; static QString getNoteString(int key) { @@ -1719,10 +1722,10 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) const NoteVector & notes = m_midiClip->notes(); // will be our iterator in the following loop - auto it = notes.begin() + notes.size() - 1; + auto it = notes.rbegin(); // loop through whole note-vector... - for( int i = 0; i < notes.size(); ++i ) + while (it != notes.rend()) { Note *note = *it; TimePos len = note->length(); @@ -1747,7 +1750,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) { break; } - --it; + ++it; } // first check whether the user clicked in note-edit- @@ -1769,7 +1772,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) Note * created_new_note = nullptr; // did it reach end of vector because // there's no note?? - if( it == notes.begin()-1 ) + if (it == notes.rend()) { is_new_note = true; m_midiClip->addJournalCheckPoint(); @@ -1816,8 +1819,8 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) // reset it so that it can be used for // ops (move, resize) after this // code-block - it = notes.begin(); - while( it != notes.end() && *it != created_new_note ) + it = notes.rbegin(); + while (it != notes.rend() && *it != created_new_note) { ++it; } @@ -1933,7 +1936,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) { // erase single note m_mouseDownRight = true; - if( it != notes.begin()-1 ) + if (it != notes.rend()) { m_midiClip->addJournalCheckPoint(); m_midiClip->removeNote( *it ); @@ -2513,7 +2516,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) bool altPressed = me->modifiers() & Qt::AltModifier; // We iterate from last note in MIDI clip to the first, // chronologically - auto it = notes.begin() + notes.size() - 1; + auto it = notes.rbegin(); for( int i = 0; i < notes.size(); ++i ) { Note* n = *it; @@ -2556,7 +2559,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) } - --it; + ++it; } // Emit MIDI clip has changed @@ -2575,10 +2578,10 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) const NoteVector & notes = m_midiClip->notes(); // will be our iterator in the following loop - auto it = notes.begin() + notes.size() - 1; + auto it = notes.rbegin(); // loop through whole note-vector... - for( int i = 0; i < notes.size(); ++i ) + while (it != notes.rend()) { Note *note = *it; // and check whether the cursor is over an @@ -2591,12 +2594,12 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) { break; } - --it; + ++it; } // did it reach end of vector because there's // no note?? - if( it != notes.begin()-1 ) + if (it != notes.rend()) { Note *note = *it; // x coordinate of the right edge of the note diff --git a/src/gui/menus/MidiPortMenu.cpp b/src/gui/menus/MidiPortMenu.cpp index 296be3506..b99c3a0b7 100644 --- a/src/gui/menus/MidiPortMenu.cpp +++ b/src/gui/menus/MidiPortMenu.cpp @@ -34,7 +34,6 @@ MidiPortMenu::MidiPortMenu( MidiPort::Mode _mode ) : ModelView( nullptr, this ), m_mode( _mode ) { - setFont( pointSize<9>( font() ) ); connect( this, SIGNAL(triggered(QAction*)), this, SLOT(activatedPort(QAction*))); } diff --git a/src/gui/modals/ControllerConnectionDialog.cpp b/src/gui/modals/ControllerConnectionDialog.cpp index 79daa25b5..4d1090d5c 100644 --- a/src/gui/modals/ControllerConnectionDialog.cpp +++ b/src/gui/modals/ControllerConnectionDialog.cpp @@ -54,7 +54,7 @@ public: AutoDetectMidiController( Model* parent ) : MidiController( parent ), m_detectedMidiChannel( 0 ), - m_detectedMidiController( 0 ) + m_detectedMidiController(NONE) { updateName(); } @@ -69,7 +69,7 @@ public: ( m_midiPort.inputChannel() == 0 || m_midiPort.inputChannel() == event.channel() + 1 ) ) { m_detectedMidiChannel = event.channel() + 1; - m_detectedMidiController = event.controllerNumber() + 1; + m_detectedMidiController = event.controllerNumber(); m_detectedMidiPort = Engine::audioEngine()->midiClient()->sourcePortName( event ); emit valueChanged(); @@ -152,7 +152,7 @@ ControllerConnectionDialog::ControllerConnectionDialog( QWidget * _parent, m_midiControllerSpinBox = new LcdSpinBox( 3, m_midiGroupBox, tr( "Input controller" ) ); - m_midiControllerSpinBox->addTextForValue( 0, "---" ); + m_midiControllerSpinBox->addTextForValue(MidiController::NONE, "---" ); m_midiControllerSpinBox->setLabel( tr( "CONTROLLER" ) ); m_midiControllerSpinBox->move( 68, 24 ); diff --git a/src/gui/modals/FileDialog.cpp b/src/gui/modals/FileDialog.cpp index 512d7179f..a6cf4827a 100644 --- a/src/gui/modals/FileDialog.cpp +++ b/src/gui/modals/FileDialog.cpp @@ -26,11 +26,12 @@ #include #include #include +#include +#include #include "ConfigManager.h" #include "FileDialog.h" - namespace lmms::gui { @@ -45,19 +46,38 @@ FileDialog::FileDialog( QWidget *parent, const QString &caption, setOption( QFileDialog::DontUseNativeDialog ); - // Add additional locations to the sidebar +#ifdef LMMS_BUILD_LINUX + QList urls; +#else QList urls = sidebarUrls(); - urls << QUrl::fromLocalFile( QStandardPaths::writableLocation( QStandardPaths::DesktopLocation ) ); - // Find downloads directory - QDir downloadDir( QDir::homePath() + "/Downloads" ); - if ( ! downloadDir.exists() ) - downloadDir.setPath(QStandardPaths::writableLocation( QStandardPaths::DownloadLocation )); - if ( downloadDir.exists() ) - urls << QUrl::fromLocalFile( downloadDir.absolutePath() ); +#endif - urls << QUrl::fromLocalFile( QStandardPaths::writableLocation( QStandardPaths::MusicLocation ) ); - urls << QUrl::fromLocalFile( ConfigManager::inst()->workingDir() ); + QDir desktopDir; + desktopDir.setPath(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); + if (desktopDir.exists()) + { + urls << QUrl::fromLocalFile(desktopDir.absolutePath()); + } + + QDir downloadDir(QDir::homePath() + "/Downloads"); + if (!downloadDir.exists()) + { + downloadDir.setPath(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); + } + if (downloadDir.exists()) + { + urls << QUrl::fromLocalFile(downloadDir.absolutePath()); + } + QDir musicDir; + musicDir.setPath(QStandardPaths::writableLocation(QStandardPaths::MusicLocation)); + if (musicDir.exists()) + { + urls << QUrl::fromLocalFile(musicDir.absolutePath()); + } + + urls << QUrl::fromLocalFile(ConfigManager::inst()->workingDir()); + // Add `/Volumes` directory on OS X systems, this allows the user to browse // external disk drives. #ifdef LMMS_BUILD_APPLE @@ -66,6 +86,22 @@ FileDialog::FileDialog( QWidget *parent, const QString &caption, urls << QUrl::fromLocalFile( volumesDir.absolutePath() ); #endif +#ifdef LMMS_BUILD_LINUX + + // FileSystem types : https://www.javatpoint.com/linux-file-system + QStringList usableFileSystems = {"ext", "ext2", "ext3", "ext4", "jfs", "reiserfs", "ntfs3", "fuse.sshfs", "fuseblk"}; + + for(QStorageInfo storage : QStorageInfo::mountedVolumes()) + { + storage.refresh(); + + if (usableFileSystems.contains(QString(storage.fileSystemType()), Qt::CaseInsensitive) && storage.isValid() && storage.isReady()) + { + urls << QUrl::fromLocalFile(storage.rootPath()); + } + } +#endif + setSidebarUrls(urls); } diff --git a/src/gui/tracks/TrackOperationsWidget.cpp b/src/gui/tracks/TrackOperationsWidget.cpp index ce6177d76..fa1a651f6 100644 --- a/src/gui/tracks/TrackOperationsWidget.cpp +++ b/src/gui/tracks/TrackOperationsWidget.cpp @@ -64,7 +64,6 @@ TrackOperationsWidget::TrackOperationsWidget( TrackView * parent ) : "to begin a new drag'n'drop action." ).arg(UI_CTRL_KEY) ); auto toMenu = new QMenu(this); - toMenu->setFont( pointSize<9>( toMenu->font() ) ); connect( toMenu, SIGNAL(aboutToShow()), this, SLOT(updateMenu())); diff --git a/src/gui/widgets/ComboBox.cpp b/src/gui/widgets/ComboBox.cpp index bdf78ccce..2377a37ab 100644 --- a/src/gui/widgets/ComboBox.cpp +++ b/src/gui/widgets/ComboBox.cpp @@ -70,7 +70,6 @@ ComboBox::ComboBox( QWidget * _parent, const QString & _name ) : } setFont( pointSize<9>( font() ) ); - m_menu.setFont( pointSize<8>( m_menu.font() ) ); connect( &m_menu, SIGNAL(triggered(QAction*)), this, SLOT(setItem(QAction*))); diff --git a/src/gui/widgets/LcdFloatSpinBox.cpp b/src/gui/widgets/LcdFloatSpinBox.cpp index 6391f314a..96f2b27e1 100644 --- a/src/gui/widgets/LcdFloatSpinBox.cpp +++ b/src/gui/widgets/LcdFloatSpinBox.cpp @@ -49,6 +49,7 @@ namespace lmms::gui LcdFloatSpinBox::LcdFloatSpinBox(int numWhole, int numFrac, const QString& name, QWidget* parent) : + QWidget(parent), FloatModelView(new FloatModel(0, 0, 0, 0, nullptr, name, true), this), m_wholeDisplay(numWhole, parent, name, false), m_fractionDisplay(numFrac, parent, name, true), @@ -62,6 +63,7 @@ LcdFloatSpinBox::LcdFloatSpinBox(int numWhole, int numFrac, const QString& name, LcdFloatSpinBox::LcdFloatSpinBox(int numWhole, int numFrac, const QString& style, const QString& name, QWidget* parent) : + QWidget(parent), FloatModelView(new FloatModel(0, 0, 0, 0, nullptr, name, true), this), m_wholeDisplay(numWhole, style, parent, name, false), m_fractionDisplay(numFrac, style, parent, name, true), @@ -101,6 +103,7 @@ void LcdFloatSpinBox::layoutSetup(const QString &style) outerLayout->setContentsMargins(0, 0, 0, 0); outerLayout->setSizeConstraint(QLayout::SetFixedSize); this->setLayout(outerLayout); + this->setFixedHeight(32); } @@ -240,9 +243,9 @@ void LcdFloatSpinBox::paintEvent(QPaintEvent*) { p.setFont(pointSizeF(p.font(), 6.5)); p.setPen(m_wholeDisplay.textShadowColor()); - p.drawText(width() / 2 - horizontalAdvance(p.fontMetrics(), m_label) / 2 + 1, height(), m_label); + p.drawText(width() / 2 - p.fontMetrics().boundingRect(m_label).width() / 2 + 1, height(), m_label); p.setPen(m_wholeDisplay.textColor()); - p.drawText(width() / 2 - horizontalAdvance(p.fontMetrics(), m_label) / 2, height() - 1, m_label); + p.drawText(width() / 2 - p.fontMetrics().boundingRect(m_label).width() / 2, height() - 1, m_label); } } diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index a4de188a5..29fda075e 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -570,6 +570,10 @@ f_cnt_t InstrumentTrack::beatLen( NotePlayHandle * _n ) const void InstrumentTrack::playNote( NotePlayHandle* n, sampleFrame* workingBuffer ) { + // Note: under certain circumstances the working buffer is a nullptr. + // These cases are triggered in PlayHandle::doProcessing when the play method is called with a nullptr. + // TODO: Find out if we can skip processing at a higher level if the buffer is nullptr. + // arpeggio- and chord-widget has to do its work -> adding sub-notes // for chords/arpeggios m_noteStacking.processNote( n ); @@ -579,6 +583,15 @@ void InstrumentTrack::playNote( NotePlayHandle* n, sampleFrame* workingBuffer ) { // all is done, so now lets play the note! m_instrument->playNote( n, workingBuffer ); + + // This is effectively the same as checking if workingBuffer is not a nullptr. + // Calling processAudioBuffer with a nullptr leads to crashes. Hence the check. + if (n->usesBuffer()) + { + const fpp_t frames = n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = n->noteOffset(); + processAudioBuffer(workingBuffer, frames + offset, n); + } } }