diff --git a/.travis/debian_pkgs.sha256 b/.travis/debian_pkgs.sha256 index 96a4bd8aa..ed4e11737 100644 --- a/.travis/debian_pkgs.sha256 +++ b/.travis/debian_pkgs.sha256 @@ -1,2 +1,3 @@ 314ef4af137903dfb13e8c3ef1e6ea56cfdb23808d52ec4f5f50e288c73610c5 pbuilder_0.229.1_all.deb fa82aa8ed3055c6f6330104deedf080b26778295e589426d4c4dd0f2c2a5defa debootstrap_1.0.95_all.deb +2ef4c09f7841b72f93412803ddd142f72658536dbfabe00e449eb548f432f3f8 debian-archive-keyring_2017.7ubuntu1_all.deb diff --git a/.travis/linux.debian-sid.install.sh b/.travis/linux.debian-sid.install.sh index ecdcf6d91..ef8368822 100755 --- a/.travis/linux.debian-sid.install.sh +++ b/.travis/linux.debian-sid.install.sh @@ -2,15 +2,16 @@ set -e sudo apt-get install -y \ - debian-archive-keyring \ dpkg \ pbuilder # work around a pbuilder bug which breaks ccache # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=666525 +# and also missing signing keys in Trusty's debian-archive-keyring cd /tmp wget http://archive.ubuntu.com/ubuntu/pool/main/p/pbuilder/pbuilder_0.229.1_all.deb wget http://archive.ubuntu.com/ubuntu/pool/main/d/debootstrap/debootstrap_1.0.95_all.deb +wget http://archive.ubuntu.com/ubuntu/pool/universe/d/debian-archive-keyring/debian-archive-keyring_2017.7ubuntu1_all.deb sha256sum -c "$TRAVIS_BUILD_DIR/.travis/debian_pkgs.sha256" -sudo dpkg -i pbuilder_0.229.1_all.deb debootstrap_1.0.95_all.deb +sudo dpkg -i pbuilder_0.229.1_all.deb debootstrap_1.0.95_all.deb debian-archive-keyring_2017.7ubuntu1_all.deb cd "$OLDPWD" diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a39d7d9f..0f9d66def 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -157,7 +157,7 @@ SET(QT_LIBRARIES Qt5::Xml ) -IF(LMMS_BUILD_LINUX) +IF(LMMS_BUILD_LINUX AND WANT_VST) FIND_PACKAGE(Qt5 COMPONENTS X11Extras REQUIRED) LIST(APPEND QT_LIBRARIES Qt5::X11Extras) ENDIF() diff --git a/cmake/linux/package_linux.sh.in b/cmake/linux/package_linux.sh.in index ba6c7279a..0dec715f4 100644 --- a/cmake/linux/package_linux.sh.in +++ b/cmake/linux/package_linux.sh.in @@ -74,8 +74,10 @@ else success "Downloaded $LINUXDEPLOYQT" # Extract AppImage and replace LINUXDEPLOYQT variable with extracted binary # to support systems without fuse + # Also, we need to set LD_LIBRARY_PATH, but linuxdepoyqt's AppRun unsets it + # See https://github.com/probonopd/linuxdeployqt/pull/370/ "$LINUXDEPLOYQT" --appimage-extract > /dev/null 2>&1 - LINUXDEPLOYQT="squashfs-root/AppRun" + LINUXDEPLOYQT="squashfs-root/usr/bin/linuxdeployqt" success "Extracted $APPIMAGETOOL" fi diff --git a/doc/wiki b/doc/wiki index 42193f98f..19179c6f6 160000 --- a/doc/wiki +++ b/doc/wiki @@ -1 +1 @@ -Subproject commit 42193f98f37d6b69f47edbdfd50a20090193e70a +Subproject commit 19179c6f6afb422cf8376ed3b4a498a6396fc12f diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 78f4882b4..3e0b6143d 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -33,6 +33,7 @@ #include "MidiTime.h" #include "ValueBuffer.h" #include "MemoryManager.h" +#include "ModelVisitor.h" // simple way to map a property of a view to a model #define mapPropertyFromModelPtr(type,getfunc,setfunc,modelname) \ @@ -59,6 +60,11 @@ modelname.setValue( (float) val ); \ } +// use this to make subclasses visitable +#define MODEL_IS_VISITABLE \ + void accept(ModelVisitor& v) override { v.visit(*this); } \ + void accept(ConstModelVisitor& v) const override { v.visit(*this); } + class ControllerConnection; @@ -68,6 +74,7 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject Q_OBJECT MM_OPERATORS public: + typedef QVector AutoModelVector; enum ScaleType @@ -80,6 +87,35 @@ public: virtual ~AutomatableModel(); + // Implement those by using the MODEL_IS_VISITABLE macro + virtual void accept(ModelVisitor& v) = 0; + virtual void accept(ConstModelVisitor& v) const = 0; + +public: + /** + @brief Return this class casted to Target + @test AutomatableModelTest.cpp + @param doThrow throw an assertion if the cast fails, instead of + returning a nullptr + @return the casted class if Target is the exact or a base class of + *this, nullptr otherwise + */ + template + Target* dynamicCast(bool doThrow = false) + { + DCastVisitor vis; accept(vis); + if (doThrow && !vis.result) { Q_ASSERT(false); } + return vis.result; + } + + //! const overload, see overloaded function + template + const Target* dynamicCast(bool doThrow = false) const + { + ConstDCastVisitor vis; accept(vis); + if (doThrow && !vis.result) { Q_ASSERT(false); } + return vis.result; + } bool isAutomated() const; bool isAutomatedOrControlled() const @@ -283,6 +319,22 @@ protected: private: + // dynamicCast implementation + template + struct DCastVisitor : public ModelVisitor + { + Target* result = nullptr; + void visit(Target& tar) { result = &tar; } + }; + + // dynamicCast implementation + template + struct ConstDCastVisitor : public ConstModelVisitor + { + const Target* result = nullptr; + void visit(const Target& tar) { result = &tar; } + }; + static bool mustQuoteName(const QString &name); virtual void saveSettings( QDomDocument& doc, QDomElement& element ) @@ -382,6 +434,7 @@ public: class LMMS_EXPORT FloatModel : public TypedAutomatableModel { Q_OBJECT + MODEL_IS_VISITABLE public: FloatModel( float val = 0, float min = 0, float max = 0, float step = 0, Model * parent = NULL, @@ -399,6 +452,7 @@ public: class LMMS_EXPORT IntModel : public TypedAutomatableModel { Q_OBJECT + MODEL_IS_VISITABLE public: IntModel( int val = 0, int min = 0, int max = 0, Model* parent = NULL, @@ -414,6 +468,7 @@ public: class LMMS_EXPORT BoolModel : public TypedAutomatableModel { Q_OBJECT + MODEL_IS_VISITABLE public: BoolModel( const bool val = false, Model* parent = NULL, diff --git a/include/ComboBoxModel.h b/include/ComboBoxModel.h index ad3603759..82c01e69e 100644 --- a/include/ComboBoxModel.h +++ b/include/ComboBoxModel.h @@ -36,6 +36,7 @@ class LMMS_EXPORT ComboBoxModel : public IntModel { Q_OBJECT + MODEL_IS_VISITABLE public: ComboBoxModel( Model* parent = NULL, const QString& displayName = QString(), diff --git a/include/Effect.h b/include/Effect.h index 3d765fdc0..4dc50e8a4 100644 --- a/include/Effect.h +++ b/include/Effect.h @@ -161,6 +161,13 @@ public: protected: + /** + Effects should call this at the end of audio processing + + If the setting "Keep effects running even without input" is disabled, + after "decay" ms of a signal below "gate", the effect is turned off + and won't be processed again until it receives new audio input + */ void checkGate( double _out_sum ); virtual PluginView * instantiateView( QWidget * ); diff --git a/include/FileBrowser.h b/include/FileBrowser.h index 7e0482ce3..4b92dd549 100644 --- a/include/FileBrowser.h +++ b/include/FileBrowser.h @@ -55,7 +55,7 @@ public: private slots: void reloadTree( void ); - void expandItems( QTreeWidgetItem * item=NULL ); + void expandItems( QTreeWidgetItem * item=NULL, QList expandedDirs = QList() ); // call with item=NULL to filter the entire tree bool filterItems( const QString & filter, QTreeWidgetItem * item=NULL ); void giveFocusToFilter(); @@ -87,6 +87,10 @@ public: FileBrowserTreeWidget( QWidget * parent ); virtual ~FileBrowserTreeWidget() = default; + //! This method returns a QList with paths (QString's) of all directories + //! that are expanded in the tree. + QList expandedDirs( QTreeWidgetItem * item = nullptr ) const; + protected: virtual void contextMenuEvent( QContextMenuEvent * e ); diff --git a/include/Graph.h b/include/Graph.h index 4827bda40..1bee05c41 100644 --- a/include/Graph.h +++ b/include/Graph.h @@ -44,13 +44,18 @@ class LMMS_EXPORT Graph : public QWidget, public ModelView public: enum graphStyle { - NearestStyle, - LinearStyle, - LinearNonCyclicStyle, - BarStyle, + NearestStyle, //!< draw as stairs + LinearStyle, //!< connect each 2 samples with a line, with wrapping + LinearNonCyclicStyle, //!< LinearStyle without wrapping + BarStyle, //!< draw thick bars NumGraphStyles }; + /** + * @brief Constructor + * @param _width Pixel width of widget + * @param _height Pixel height of widget + */ Graph( QWidget * _parent, graphStyle _style = Graph::LinearStyle, int _width = 132, int _height = 104 @@ -111,10 +116,24 @@ private: } ; +/** + @brief 2 dimensional function plot + + Function plot graph with discrete x scale and continous y scale + This makes it possible to display "#x" samples +*/ class LMMS_EXPORT graphModel : public Model { Q_OBJECT public: + /** + * @brief Constructor + * @param _min Minimum y value to display + * @param _max Maximum y value to display + * @param _size Number of samples (e.g. x value) + * @param _step Step size on y axis where values snap to, or 0.0f + * for "no snapping" + */ graphModel( float _min, float _max, int _size, @@ -146,14 +165,21 @@ public: return( m_samples.data() ); } - void convolve(const float *convolution, const int convolutionLength, const int centerOffset); + //! Make cyclic convolution + //! @param convolution Samples to convolve with + //! @param convolutionLength Number of samples to take for each sum + //! @param centerOffset Offset for resulting values + void convolve(const float *convolution, + const int convolutionLength, const int centerOffset); public slots: + //! Set range of y values void setRange( float _min, float _max ); void setLength( int _size ); - + //! Update one sample void setSampleAt( int x, float val ); + //! Update samples array void setSamples( const float * _value ); void setWaveToSine(); diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index 6a2cacb3a..c487438d0 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -431,6 +431,9 @@ protected slots: private: virtual void modelChanged(); void viewInstrumentInDirection(int d); + //! adjust size of any child widget of the main tab + //! required to keep the old look when using a variable sized tab widget + void adjustTabSize(QWidget *w); InstrumentTrack * m_track; InstrumentTrackView * m_itv; diff --git a/include/ModelVisitor.h b/include/ModelVisitor.h new file mode 100644 index 000000000..f9d156e30 --- /dev/null +++ b/include/ModelVisitor.h @@ -0,0 +1,64 @@ +/* + * ModelVisitor.h - visitors for automatable models + * + * Copyright (c) 2019-2019 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef MODELVISITOR_H +#define MODELVISITOR_H + +class AutomatableModel; +class BoolModel; +class IntModel; +class FloatModel; +class ComboBoxModel; +class TempoSyncKnobModel; + +class ModelVisitor +{ + template + void up(ModelType& m) { visit(static_cast(m)); } +public: + virtual void visit(AutomatableModel& ) {} + virtual void visit(BoolModel& m); + virtual void visit(IntModel& m); + virtual void visit(FloatModel& m); + virtual void visit(ComboBoxModel& m); + virtual void visit(TempoSyncKnobModel& m); + virtual ~ModelVisitor(); +}; + +class ConstModelVisitor +{ + template + void up(const ModelType& m) { + visit(static_cast(m)); } +public: + virtual void visit(const AutomatableModel& ) {} + virtual void visit(const BoolModel& m); + virtual void visit(const IntModel& m); + virtual void visit(const FloatModel& m); + virtual void visit(const ComboBoxModel& m); + virtual void visit(const TempoSyncKnobModel& m); + virtual ~ConstModelVisitor(); +}; + +#endif // MODELVISITOR_H diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index c94a69c2c..ae55c9ebb 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -303,6 +303,7 @@ private: NotePlayHandleList m_subNotes; // used for chords and arpeggios volatile bool m_released; // indicates whether note is released bool m_releaseStarted; + bool m_hasMidiNote; bool m_hasParent; // indicates whether note has parent NotePlayHandle * m_parent; // parent note bool m_hadChildren; diff --git a/include/PianoRoll.h b/include/PianoRoll.h index b4115b054..4451a07c5 100644 --- a/include/PianoRoll.h +++ b/include/PianoRoll.h @@ -296,6 +296,8 @@ private: void testPlayNote( Note * n ); void testPlayKey( int _key, int _vol, int _pan ); void pauseTestNotes(bool pause = true ); + void playChordNotes(int key, int velocity=-1); + void pauseChordNotes(int key); QList getAllOctavesForKey( int keyToMirror ) const; diff --git a/include/PluginIssue.h b/include/PluginIssue.h new file mode 100644 index 000000000..c00945805 --- /dev/null +++ b/include/PluginIssue.h @@ -0,0 +1,66 @@ +/* + * PluginIssue.h - PluginIssue class + * + * Copyright (c) 2019 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef PLUGINISSUE_H +#define PLUGINISSUE_H + +#include +#include + +//! Types of issues that can cause LMMS to not load a plugin +//! LMMS Plugins should use this to indicate errors +enum PluginIssueType +{ + unknownPortFlow, + unknownPortType, + tooManyInputChannels, + tooManyOutputChannels, + noOutputChannel, + portHasNoDef, + portHasNoMin, + portHasNoMax, + featureNotSupported, //!< plugin requires functionality LMMS can't offer + badPortType, //!< port type not supported + noIssue +}; + +//! Issue type bundled with informational string +class PluginIssue +{ + static const char* msgFor(const PluginIssueType& it); + + PluginIssueType m_issueType; + std::string m_info; + +public: + PluginIssue(PluginIssueType it, std::string msg = std::string()) + : m_issueType(it), m_info(msg) + { + } + friend QDebug operator<<(QDebug stream, const PluginIssue& iss); +}; + +QDebug operator<<(QDebug stream, const PluginIssue& iss); + +#endif // PLUGINISSUE_H diff --git a/include/TabWidget.h b/include/TabWidget.h index dacd2648b..11e4da40a 100644 --- a/include/TabWidget.h +++ b/include/TabWidget.h @@ -36,7 +36,10 @@ class TabWidget : public QWidget { Q_OBJECT public: - TabWidget( const QString & _caption, QWidget * _parent, bool usePixmap = false ); + //! @param resizable If true, the widget resizes to fit the size of all tabs + //! If false, all child widget will be cut down to the TabWidget's size + TabWidget( const QString & _caption, QWidget * _parent, + bool usePixmap = false, bool resizable = false ); virtual ~TabWidget() = default; void addTab( QWidget * w, const QString & name, const char *pixmap = NULL, int idx = -1 ); @@ -74,7 +77,7 @@ protected: virtual void paintEvent( QPaintEvent * _pe ); virtual void resizeEvent( QResizeEvent * _re ); virtual void wheelEvent( QWheelEvent * _we ); - + virtual QSize minimumSizeHint() const; private: struct widgetDesc @@ -88,6 +91,7 @@ private: widgetStack m_widgets; + bool m_resizable; int m_activeTab; QString m_caption; // Tab caption, used as the tooltip text on icon tabs quint8 m_tabbarHeight; // The height of the tab bar diff --git a/include/TempoSyncKnobModel.h b/include/TempoSyncKnobModel.h index 9a8ad619c..9aaf48fea 100644 --- a/include/TempoSyncKnobModel.h +++ b/include/TempoSyncKnobModel.h @@ -33,6 +33,7 @@ class QAction; class LMMS_EXPORT TempoSyncKnobModel : public FloatModel { Q_OBJECT + MODEL_IS_VISITABLE public: enum TempoSyncMode { @@ -51,10 +52,10 @@ public: const float _max, const float _step, const float _scale, Model * _parent, const QString & _display_name = QString() ); - virtual ~TempoSyncKnobModel(); + virtual ~TempoSyncKnobModel() override; - void saveSettings( QDomDocument & _doc, QDomElement & _this, const QString& name ); - void loadSettings( const QDomElement & _this, const QString& name ); + void saveSettings( QDomDocument & _doc, QDomElement & _this, const QString& name ) override; + void loadSettings( const QDomElement & _this, const QString& name ) override; TempoSyncMode syncMode() const { diff --git a/plugins/Eq/EqControlsDialog.cpp b/plugins/Eq/EqControlsDialog.cpp index af2378a2c..00c8eaaa7 100644 --- a/plugins/Eq/EqControlsDialog.cpp +++ b/plugins/Eq/EqControlsDialog.cpp @@ -55,10 +55,10 @@ EqControlsDialog::EqControlsDialog( EqControls *controls ) : EqSpectrumView * inSpec = new EqSpectrumView( &controls->m_inFftBands, this ); inSpec->move( 26, 17 ); - inSpec->setColor( QColor( 54, 45, 142, 150 ) ); + inSpec->setColor( QColor( 77, 101, 242, 150 ) ); EqSpectrumView * outSpec = new EqSpectrumView( &controls->m_outFftBands, this ); - outSpec->setColor( QColor( 9, 166, 156, 150 ) ); + outSpec->setColor( QColor( 0, 255, 239, 150 ) ); outSpec->move( 26, 17 ); m_parameterWidget = new EqParameterWidget( this , controls ); diff --git a/plugins/LadspaEffect/LadspaControls.cpp b/plugins/LadspaEffect/LadspaControls.cpp index 8e65e0e93..028394d30 100644 --- a/plugins/LadspaEffect/LadspaControls.cpp +++ b/plugins/LadspaEffect/LadspaControls.cpp @@ -36,7 +36,8 @@ LadspaControls::LadspaControls( LadspaEffect * _eff ) : { connect( &m_stereoLinkModel, SIGNAL( dataChanged() ), - this, SLOT( updateLinkStatesFromGlobal() ) ); + this, SLOT( updateLinkStatesFromGlobal() ), + Qt::DirectConnection ); multi_proc_t controls = m_effect->getPortControls(); m_controlCount = controls.count(); @@ -59,7 +60,8 @@ LadspaControls::LadspaControls( LadspaEffect * _eff ) : if( linked_control ) { connect( (*it)->control, SIGNAL( linkChanged( int, bool ) ), - this, SLOT( linkPort( int, bool ) ) ); + this, SLOT( linkPort( int, bool ) ), + Qt::DirectConnection ); } } } diff --git a/plugins/VstEffect/VstEffect.cpp b/plugins/VstEffect/VstEffect.cpp index cbb30e2fe..80f209a7b 100644 --- a/plugins/VstEffect/VstEffect.cpp +++ b/plugins/VstEffect/VstEffect.cpp @@ -145,9 +145,6 @@ void VstEffect::openPlugin( const QString & _plugin ) return; } - VstPlugin::connect( Engine::getSong(), SIGNAL( tempoChanged( bpm_t ) ), m_plugin.data(), SLOT( setTempo( bpm_t ) ) ); - m_plugin->setTempo( Engine::getSong()->getTempo() ); - delete tf; m_key.attributes["file"] = _plugin; diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 3526b27cc..48ab13743 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -1962,7 +1962,8 @@ DWORD WINAPI RemoteVstPlugin::processingThread( LPVOID _param ) { if( m.id == IdStartProcessing || m.id == IdMidiEvent - || m.id == IdVstSetParameter ) + || m.id == IdVstSetParameter + || m.id == IdVstSetTempo ) { _this->processMessage( m ); } diff --git a/plugins/vst_base/VstPlugin.cpp b/plugins/vst_base/VstPlugin.cpp index 9f468087c..f224f092d 100644 --- a/plugins/vst_base/VstPlugin.cpp +++ b/plugins/vst_base/VstPlugin.cpp @@ -162,7 +162,7 @@ VstPlugin::VstPlugin( const QString & _plugin ) : setTempo( Engine::getSong()->getTempo() ); connect( Engine::getSong(), SIGNAL( tempoChanged( bpm_t ) ), - this, SLOT( setTempo( bpm_t ) ) ); + this, SLOT( setTempo( bpm_t ) ), Qt::DirectConnection ); connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSampleRate() ) ); diff --git a/src/3rdparty/CMakeLists.txt b/src/3rdparty/CMakeLists.txt index b40f7c3cd..473e7702f 100644 --- a/src/3rdparty/CMakeLists.txt +++ b/src/3rdparty/CMakeLists.txt @@ -3,7 +3,7 @@ set(CMAKE_CXX_FLAGS "") set(CMAKE_C_FLAGS_DEBUG "") set(CMAKE_CXX_FLAGS_DEBUG "") -IF(LMMS_BUILD_LINUX) +IF(LMMS_BUILD_LINUX AND WANT_VST) set(BUILD_SHARED_LIBS OFF) add_subdirectory(qt5-x11embed) ENDIF() diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 7870415f9..ba41e089c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -40,6 +40,7 @@ set(LMMS_SRCS core/MixerWorkerThread.cpp core/MixHelpers.cpp core/Model.cpp + core/ModelVisitor.cpp core/Note.cpp core/NotePlayHandle.cpp core/Oscillator.cpp @@ -48,6 +49,7 @@ set(LMMS_SRCS core/Piano.cpp core/PlayHandle.cpp core/Plugin.cpp + core/PluginIssue.cpp core/PluginFactory.cpp core/PresetPreviewPlayHandle.cpp core/ProjectJournal.cpp diff --git a/src/core/ControllerConnection.cpp b/src/core/ControllerConnection.cpp index 45e36e12f..4d43a4366 100644 --- a/src/core/ControllerConnection.cpp +++ b/src/core/ControllerConnection.cpp @@ -205,7 +205,7 @@ void ControllerConnection::loadSettings( const QDomElement & _this ) else { m_controllerId = _this.attribute( "id", "-1" ).toInt(); - if( m_controllerId < 0 ) + if( m_controllerId < 0 || m_controllerId >= Engine::getSong()->controllers().size() ) { qWarning( "controller index invalid\n" ); m_controllerId = -1; diff --git a/src/core/EnvelopeAndLfoParameters.cpp b/src/core/EnvelopeAndLfoParameters.cpp index 9001cd796..27a73b2c1 100644 --- a/src/core/EnvelopeAndLfoParameters.cpp +++ b/src/core/EnvelopeAndLfoParameters.cpp @@ -131,32 +131,32 @@ EnvelopeAndLfoParameters::EnvelopeAndLfoParameters( instances()->add( this ); connect( &m_predelayModel, SIGNAL( dataChanged() ), - this, SLOT( updateSampleVars() ) ); + this, SLOT( updateSampleVars() ), Qt::DirectConnection ); connect( &m_attackModel, SIGNAL( dataChanged() ), - this, SLOT( updateSampleVars() ) ); + this, SLOT( updateSampleVars() ), Qt::DirectConnection ); connect( &m_holdModel, SIGNAL( dataChanged() ), - this, SLOT( updateSampleVars() ) ); + this, SLOT( updateSampleVars() ), Qt::DirectConnection ); connect( &m_decayModel, SIGNAL( dataChanged() ), - this, SLOT( updateSampleVars() ) ); + this, SLOT( updateSampleVars() ), Qt::DirectConnection ); connect( &m_sustainModel, SIGNAL( dataChanged() ), - this, SLOT( updateSampleVars() ) ); + this, SLOT( updateSampleVars() ), Qt::DirectConnection ); connect( &m_releaseModel, SIGNAL( dataChanged() ), - this, SLOT( updateSampleVars() ) ); + this, SLOT( updateSampleVars() ), Qt::DirectConnection ); connect( &m_amountModel, SIGNAL( dataChanged() ), - this, SLOT( updateSampleVars() ) ); + this, SLOT( updateSampleVars() ), Qt::DirectConnection ); connect( &m_lfoPredelayModel, SIGNAL( dataChanged() ), - this, SLOT( updateSampleVars() ) ); + this, SLOT( updateSampleVars() ), Qt::DirectConnection ); connect( &m_lfoAttackModel, SIGNAL( dataChanged() ), - this, SLOT( updateSampleVars() ) ); + this, SLOT( updateSampleVars() ), Qt::DirectConnection ); connect( &m_lfoSpeedModel, SIGNAL( dataChanged() ), - this, SLOT( updateSampleVars() ) ); + this, SLOT( updateSampleVars() ), Qt::DirectConnection ); connect( &m_lfoAmountModel, SIGNAL( dataChanged() ), - this, SLOT( updateSampleVars() ) ); + this, SLOT( updateSampleVars() ), Qt::DirectConnection ); connect( &m_lfoWaveModel, SIGNAL( dataChanged() ), - this, SLOT( updateSampleVars() ) ); + this, SLOT( updateSampleVars() ), Qt::DirectConnection ); connect( &m_x100Model, SIGNAL( dataChanged() ), - this, SLOT( updateSampleVars() ) ); + this, SLOT( updateSampleVars() ), Qt::DirectConnection ); connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSampleVars() ) ); diff --git a/src/core/LadspaControl.cpp b/src/core/LadspaControl.cpp index cf8c6e468..731241b85 100644 --- a/src/core/LadspaControl.cpp +++ b/src/core/LadspaControl.cpp @@ -42,7 +42,8 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port, if( m_link ) { connect( &m_linkEnabledModel, SIGNAL( dataChanged() ), - this, SLOT( linkStateChanged() ) ); + this, SLOT( linkStateChanged() ), + Qt::DirectConnection ); } switch( m_port->data_type ) diff --git a/src/core/LfoController.cpp b/src/core/LfoController.cpp index c1c81119f..2b2db2f14 100644 --- a/src/core/LfoController.cpp +++ b/src/core/LfoController.cpp @@ -49,12 +49,12 @@ LfoController::LfoController( Model * _parent ) : { setSampleExact( true ); connect( &m_waveModel, SIGNAL( dataChanged() ), - this, SLOT( updateSampleFunction() ) ); + this, SLOT( updateSampleFunction() ), Qt::DirectConnection ); connect( &m_speedModel, SIGNAL( dataChanged() ), - this, SLOT( updateDuration() ) ); + this, SLOT( updateDuration() ), Qt::DirectConnection ); connect( &m_multiplierModel, SIGNAL( dataChanged() ), - this, SLOT( updateDuration() ) ); + this, SLOT( updateDuration() ), Qt::DirectConnection ); connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateDuration() ) ); diff --git a/src/core/MeterModel.cpp b/src/core/MeterModel.cpp index 87b1b950a..30db26d9b 100644 --- a/src/core/MeterModel.cpp +++ b/src/core/MeterModel.cpp @@ -33,9 +33,9 @@ MeterModel::MeterModel( ::Model * _parent ) : m_denominatorModel( 4, 1, 32, this, tr( "Denominator" ) ) { connect( &m_numeratorModel, SIGNAL( dataChanged() ), - this, SIGNAL( dataChanged() ) ); + this, SIGNAL( dataChanged() ), Qt::DirectConnection ); connect( &m_denominatorModel, SIGNAL( dataChanged() ), - this, SIGNAL( dataChanged() ) ); + this, SIGNAL( dataChanged() ), Qt::DirectConnection ); } diff --git a/src/core/ModelVisitor.cpp b/src/core/ModelVisitor.cpp new file mode 100644 index 000000000..4036f56e0 --- /dev/null +++ b/src/core/ModelVisitor.cpp @@ -0,0 +1,44 @@ +/* + * ModelVisitor.cpp - visitors for automatable models + * + * Copyright (c) 2019-2019 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "ModelVisitor.h" + +#include "AutomatableModel.h" +#include "ComboBoxModel.h" +#include "TempoSyncKnobModel.h" + +void ModelVisitor::visit(BoolModel &m) { up(m); } +void ModelVisitor::visit(IntModel &m) { up(m); } +void ModelVisitor::visit(FloatModel &m) { up(m); } +void ModelVisitor::visit(ComboBoxModel &m) { up(m); } +void ModelVisitor::visit(TempoSyncKnobModel &m) { up(m); } + +void ConstModelVisitor::visit(const BoolModel &m) { up(m); } +void ConstModelVisitor::visit(const IntModel &m) { up(m); } +void ConstModelVisitor::visit(const FloatModel &m) { up(m); } +void ConstModelVisitor::visit(const ComboBoxModel &m) { up(m); } +void ConstModelVisitor::visit(const TempoSyncKnobModel &m) { up(m); } + +ModelVisitor::~ModelVisitor() {} +ConstModelVisitor::~ConstModelVisitor() {} diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index 8c838f136..67b7b416a 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -62,6 +62,7 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, m_subNotes(), m_released( false ), m_releaseStarted( false ), + m_hasMidiNote( false ), m_hasParent( parent != NULL ), m_parent( parent ), m_hadChildren( false ), @@ -105,17 +106,6 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, m_instrumentTrack->midiNoteOn( *this ); } - if( hasParent() || ! m_instrumentTrack->isArpeggioEnabled() ) - { - const int baseVelocity = m_instrumentTrack->midiPort()->baseVelocity(); - - // send MidiNoteOn event - m_instrumentTrack->processOutEvent( - MidiEvent( MidiNoteOn, midiChannel(), midiKey(), midiVelocity( baseVelocity ) ), - MidiTime::fromFrames( offset(), Engine::framesPerTick() ), - offset() ); - } - if( m_instrumentTrack->instrument()->flags() & Instrument::IsSingleStreamed ) { setUsesBuffer( false ); @@ -205,6 +195,21 @@ void NotePlayHandle::play( sampleFrame * _working_buffer ) } lock(); + + if( m_totalFramesPlayed == 0 && !m_hasMidiNote + && ( hasParent() || ! m_instrumentTrack->isArpeggioEnabled() ) ) + { + m_hasMidiNote = true; + + const int baseVelocity = m_instrumentTrack->midiPort()->baseVelocity(); + + // send MidiNoteOn event + m_instrumentTrack->processOutEvent( + MidiEvent( MidiNoteOn, midiChannel(), midiKey(), midiVelocity( baseVelocity ) ), + MidiTime::fromFrames( offset(), Engine::framesPerTick() ), + offset() ); + } + if( m_frequencyNeedsUpdate ) { updateFrequency(); @@ -357,8 +362,10 @@ void NotePlayHandle::noteOff( const f_cnt_t _s ) m_framesBeforeRelease = _s; m_releaseFramesToDo = qMax( 0, actualReleaseFramesToDo() ); - if( hasParent() || ! m_instrumentTrack->isArpeggioEnabled() ) + if( m_hasMidiNote ) { + m_hasMidiNote = false; + // send MidiNoteOff event m_instrumentTrack->processOutEvent( MidiEvent( MidiNoteOff, midiChannel(), midiKey(), 0 ), diff --git a/src/core/PeakController.cpp b/src/core/PeakController.cpp index 9e5e654a6..b2e3bc921 100644 --- a/src/core/PeakController.cpp +++ b/src/core/PeakController.cpp @@ -53,8 +53,10 @@ PeakController::PeakController( Model * _parent, this, SLOT( handleDestroyedEffect( ) ) ); } connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateCoeffs() ) ); - connect( m_peakEffect->attackModel(), SIGNAL( dataChanged() ), this, SLOT( updateCoeffs() ) ); - connect( m_peakEffect->decayModel(), SIGNAL( dataChanged() ), this, SLOT( updateCoeffs() ) ); + connect( m_peakEffect->attackModel(), SIGNAL( dataChanged() ), + this, SLOT( updateCoeffs() ), Qt::DirectConnection ); + connect( m_peakEffect->decayModel(), SIGNAL( dataChanged() ), + this, SLOT( updateCoeffs() ), Qt::DirectConnection ); m_coeffNeedsUpdate = true; } diff --git a/src/core/PluginIssue.cpp b/src/core/PluginIssue.cpp new file mode 100644 index 000000000..4a8b2ee5b --- /dev/null +++ b/src/core/PluginIssue.cpp @@ -0,0 +1,72 @@ +/* + * PluginIssue.h - PluginIssue class + * + * Copyright (c) 2019 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include + +#include "PluginIssue.h" + +const char *PluginIssue::msgFor(const PluginIssueType &it) +{ + switch (it) + { + case unknownPortFlow: + return "unknown port flow for mandatory port"; + case unknownPortType: + return "unknown port type for mandatory port"; + case tooManyInputChannels: + return "too many audio input channels"; + case tooManyOutputChannels: + return "too many audio output channels"; + case noOutputChannel: + return "no audio output channel"; + case portHasNoDef: + return "port is missing default value"; + case portHasNoMin: + return "port is missing min value"; + case portHasNoMax: + return "port is missing max value"; + case featureNotSupported: + return "required feature not supported"; + case badPortType: + return "unsupported port type"; + case noIssue: + return nullptr; + } + return nullptr; +} + + + + +QDebug operator<<(QDebug stream, const PluginIssue &iss) +{ + stream << PluginIssue::msgFor(iss.m_issueType); + if (iss.m_info.length()) + { + stream.nospace() << ": " << iss.m_info.c_str(); + } + return stream; +} + + diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 2f57b51c3..2809c61ad 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -92,18 +92,18 @@ Song::Song() : { for(int i = 0; i < Mode_Count; ++i) m_elapsedMilliSeconds[i] = 0; connect( &m_tempoModel, SIGNAL( dataChanged() ), - this, SLOT( setTempo() ) ); + this, SLOT( setTempo() ), Qt::DirectConnection ); connect( &m_tempoModel, SIGNAL( dataUnchanged() ), - this, SLOT( setTempo() ) ); + this, SLOT( setTempo() ), Qt::DirectConnection ); connect( &m_timeSigModel, SIGNAL( dataChanged() ), - this, SLOT( setTimeSignature() ) ); + this, SLOT( setTimeSignature() ), Qt::DirectConnection ); connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateFramesPerTick() ) ); connect( &m_masterVolumeModel, SIGNAL( dataChanged() ), - this, SLOT( masterVolumeChanged() ) ); + this, SLOT( masterVolumeChanged() ), Qt::DirectConnection ); /* connect( &m_masterPitchModel, SIGNAL( dataChanged() ), this, SLOT( masterPitchChanged() ) );*/ diff --git a/src/core/TempoSyncKnobModel.cpp b/src/core/TempoSyncKnobModel.cpp index e94c6e424..a85ca2e9f 100644 --- a/src/core/TempoSyncKnobModel.cpp +++ b/src/core/TempoSyncKnobModel.cpp @@ -42,7 +42,8 @@ TempoSyncKnobModel::TempoSyncKnobModel( const float _val, const float _min, m_custom( _parent ) { connect( Engine::getSong(), SIGNAL( tempoChanged( bpm_t ) ), - this, SLOT( calculateTempoSyncTime( bpm_t ) ) ); + this, SLOT( calculateTempoSyncTime( bpm_t ) ), + Qt::DirectConnection ); } @@ -154,7 +155,8 @@ void TempoSyncKnobModel::setSyncMode( TempoSyncMode _new_mode ) if( _new_mode == SyncCustom ) { connect( &m_custom, SIGNAL( dataChanged() ), - this, SLOT( updateCustom() ) ); + this, SLOT( updateCustom() ), + Qt::DirectConnection ); } } calculateTempoSyncTime( Engine::getSong()->getTempo() ); diff --git a/src/core/Track.cpp b/src/core/Track.cpp index d08144e47..64c17c9e8 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -328,8 +328,8 @@ TrackContentObjectView::~TrackContentObjectView() /*! \brief Update a TrackContentObjectView * - * TCO's get drawn only when needed, - * and when a TCO is updated, + * TCO's get drawn only when needed, + * and when a TCO is updated, * it needs to be redrawn. * */ @@ -598,9 +598,9 @@ void TrackContentObjectView::dropEvent( QDropEvent * de ) */ void TrackContentObjectView::leaveEvent( QEvent * e ) { - while( QApplication::overrideCursor() != NULL ) + if( cursor().shape() != Qt::BitmapCursor ) { - QApplication::restoreOverrideCursor(); + setCursor( QCursor( embed::getIconPixmap( "hand" ), 3, 3 ) ); } if( e != NULL ) { @@ -746,20 +746,17 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) && !m_tco->getAutoResize() ) { m_action = ResizeLeft; - QCursor c( Qt::SizeHorCursor ); - QApplication::setOverrideCursor( c ); + setCursor( Qt::SizeHorCursor ); } else if( me->x() < width() - RESIZE_GRIP_WIDTH ) { m_action = Move; - QCursor c( Qt::SizeAllCursor ); - QApplication::setOverrideCursor( c ); + setCursor( Qt::SizeAllCursor ); } else if( !m_tco->getAutoResize() ) { m_action = Resize; - QCursor c( Qt::SizeHorCursor ); - QApplication::setOverrideCursor( c ); + setCursor( Qt::SizeHorCursor ); } if( m_action == Move ) @@ -1007,17 +1004,7 @@ void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me ) if( ( me->x() > width() - RESIZE_GRIP_WIDTH && !me->buttons() && !m_tco->getAutoResize() ) || ( me->x() < RESIZE_GRIP_WIDTH && !me->buttons() && sTco && !m_tco->getAutoResize() ) ) { - if( QApplication::overrideCursor() != NULL && - QApplication::overrideCursor()->shape() != - Qt::SizeHorCursor ) - { - while( QApplication::overrideCursor() != NULL ) - { - QApplication::restoreOverrideCursor(); - } - } - QCursor c( Qt::SizeHorCursor ); - QApplication::setOverrideCursor( c ); + setCursor( Qt::SizeHorCursor ); } else { @@ -1194,7 +1181,7 @@ void TrackContentWidget::updateBackground() // draw lines // vertical lines - pmp.setPen( QPen( gridColor(), 1 ) ); + pmp.setPen( QPen( gridColor(), 1 ) ); for( float x = 0; x < w * 2; x += ppt ) { pmp.drawLine( QLineF( x, 0.0, x, h ) ); @@ -1205,9 +1192,9 @@ void TrackContentWidget::updateBackground() { pmp.drawLine( QLineF( x, 0.0, x, h ) ); } - + // horizontal line - pmp.setPen( QPen( gridColor(), 1 ) ); + pmp.setPen( QPen( gridColor(), 1 ) ); pmp.drawLine( 0, h-1, w*2, h-1 ); pmp.end(); @@ -1390,7 +1377,7 @@ MidiTime TrackContentWidget::getPosition( int mouseX ) */ void TrackContentWidget::dragEnterEvent( QDragEnterEvent * dee ) { - MidiTime tcoPos = MidiTime( getPosition( dee->pos().x() ).getTact(), 0 ); + MidiTime tcoPos = getPosition( dee->pos().x() ); if( canPasteSelection( tcoPos, dee ) == false ) { dee->ignore(); @@ -1928,7 +1915,7 @@ void TrackOperationsWidget::updateMenu() toMenu->addAction( embed::getIconPixmap( "cancel", 16, 16 ), tr( "Remove this track" ), this, SLOT( removeTrack() ) ); - + if( ! m_trackView->trackContainerView()->fixedTCOs() ) { toMenu->addAction( tr( "Clear this track" ), this, SLOT( clearTrack() ) ); @@ -2624,7 +2611,7 @@ TrackView::TrackView( Track * track, TrackContainerView * tcv ) : &m_trackContentWidget, SLOT( update() ) ); connect( &m_track->m_soloModel, SIGNAL( dataChanged() ), - m_track, SLOT( toggleSolo() ) ); + m_track, SLOT( toggleSolo() ), Qt::DirectConnection ); // create views for already existing TCOs for( Track::tcoVector::iterator it = m_track->m_trackContentObjects.begin(); @@ -2871,12 +2858,12 @@ void TrackView::mouseMoveEvent( QMouseEvent * me ) else if( m_action == MoveTrack ) { // look which track-widget the mouse-cursor is over - const int yPos = + const int yPos = m_trackContainerView->contentWidget()->mapFromGlobal( me->globalPos() ).y(); const TrackView * trackAtY = m_trackContainerView->trackViewAt( yPos ); -// debug code -// qDebug( "y position %d", yPos ); + // debug code + // qDebug( "y position %d", yPos ); // a track-widget not equal to ourself? if( trackAtY != NULL && trackAtY != this ) diff --git a/src/core/midi/MidiAlsaSeq.cpp b/src/core/midi/MidiAlsaSeq.cpp index be1e623de..e420ebc08 100644 --- a/src/core/midi/MidiAlsaSeq.cpp +++ b/src/core/midi/MidiAlsaSeq.cpp @@ -100,7 +100,7 @@ MidiAlsaSeq::MidiAlsaSeq() : snd_seq_start_queue( m_seqHandle, m_queueID, NULL ); changeQueueTempo( Engine::getSong()->getTempo() ); connect( Engine::getSong(), SIGNAL( tempoChanged( bpm_t ) ), - this, SLOT( changeQueueTempo( bpm_t ) ) ); + this, SLOT( changeQueueTempo( bpm_t ) ), Qt::DirectConnection ); // initial list-update updatePortList(); diff --git a/src/core/midi/MidiPort.cpp b/src/core/midi/MidiPort.cpp index 9e3cdb13d..52e0a5223 100644 --- a/src/core/midi/MidiPort.cpp +++ b/src/core/midi/MidiPort.cpp @@ -63,9 +63,12 @@ MidiPort::MidiPort( const QString& name, m_readableModel.setValue( m_mode == Input || m_mode == Duplex ); m_writableModel.setValue( m_mode == Output || m_mode == Duplex ); - connect( &m_readableModel, SIGNAL( dataChanged() ), this, SLOT( updateMidiPortMode() ) ); - connect( &m_writableModel, SIGNAL( dataChanged() ), this, SLOT( updateMidiPortMode() ) ); - connect( &m_outputProgramModel, SIGNAL( dataChanged() ), this, SLOT( updateOutputProgram() ) ); + connect( &m_readableModel, SIGNAL( dataChanged() ), + this, SLOT( updateMidiPortMode() ), Qt::DirectConnection ); + connect( &m_writableModel, SIGNAL( dataChanged() ), + this, SLOT( updateMidiPortMode() ), Qt::DirectConnection ); + connect( &m_outputProgramModel, SIGNAL( dataChanged() ), + this, SLOT( updateOutputProgram() ), Qt::DirectConnection ); // when using with non-raw-clients we can provide buttons showing diff --git a/src/gui/FileBrowser.cpp b/src/gui/FileBrowser.cpp index 4311e4e05..7eeb87ed2 100644 --- a/src/gui/FileBrowser.cpp +++ b/src/gui/FileBrowser.cpp @@ -163,6 +163,7 @@ bool FileBrowser::filterItems( const QString & filter, QTreeWidgetItem * item ) void FileBrowser::reloadTree( void ) { + QList expandedDirs = m_fileBrowserTreeWidget->expandedDirs(); const QString text = m_filterEdit->text(); m_filterEdit->clear(); m_fileBrowserTreeWidget->clear(); @@ -171,17 +172,17 @@ void FileBrowser::reloadTree( void ) { addItems( *it ); } - expandItems(); + expandItems(NULL, expandedDirs); m_filterEdit->setText( text ); filterItems( text ); } -void FileBrowser::expandItems( QTreeWidgetItem * item ) +void FileBrowser::expandItems( QTreeWidgetItem * item, QList expandedDirs ) { 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); if ( m_recurse ) @@ -189,14 +190,15 @@ void FileBrowser::expandItems( QTreeWidgetItem * item ) it->setExpanded( true ); } Directory *d = dynamic_cast ( it ); - if( d ) + if (d) { d->update(); - d->setExpanded( false ); + bool expand = expandedDirs.contains( d->fullName() ); + d->setExpanded( expand ); } - if( m_recurse && it->childCount() ) + if (m_recurse && it->childCount()) { - expandItems(it); + expandItems(it, expandedDirs); } } } @@ -326,6 +328,30 @@ FileBrowserTreeWidget::FileBrowserTreeWidget(QWidget * parent ) : } +QList FileBrowserTreeWidget::expandedDirs( QTreeWidgetItem * item ) const +{ + int numChildren = item ? item->childCount() : topLevelItemCount(); + QList dirs; + for (int i = 0; i < numChildren; ++i) + { + QTreeWidgetItem * it = item ? item->child(i) : topLevelItem(i); + + // Add expanded top level directories. + if (it->isExpanded() && (it->type() == TypeDirectoryItem)) + { + Directory *d = static_cast ( it ); + dirs.append( d->fullName() ); + } + + // Add expanded child directories (recurse). + if (it->childCount()) + { + dirs.append( expandedDirs( it ) ); + } + } + return dirs; +} + void FileBrowserTreeWidget::contextMenuEvent(QContextMenuEvent * e ) { FileItem * f = dynamic_cast( itemAt( e->pos() ) ); diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index 0a6a54866..90cd1dbf3 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -313,7 +313,7 @@ FxMixerView::FxChannelView::FxChannelView(QWidget * _parent, FxMixerView * _mv, m_soloBtn->setCheckable( true ); m_soloBtn->move( 9, m_fader->y()-21); connect(&fxChannel->m_soloModel, SIGNAL( dataChanged() ), - _mv, SLOT ( toggledSolo() ) ); + _mv, SLOT ( toggledSolo() ), Qt::DirectConnection ); ToolTip::add( m_soloBtn, tr( "Solo FX channel" ) ); // Create EffectRack for the channel diff --git a/src/gui/InstrumentView.cpp b/src/gui/InstrumentView.cpp index 93e153f6f..cf19bbbe1 100644 --- a/src/gui/InstrumentView.cpp +++ b/src/gui/InstrumentView.cpp @@ -34,7 +34,6 @@ InstrumentView::InstrumentView( Instrument * _Instrument, QWidget * _parent ) : PluginView( _Instrument, _parent ) { setModel( _Instrument ); - setFixedSize( 250, 250 ); setAttribute( Qt::WA_DeleteOnClose, true ); } diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 76b2d04f1..a3ff9c5fd 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -1007,6 +1007,9 @@ void PianoRoll::drawDetuningInfo( QPainter & _p, const Note * _n, int _x, { int middle_y = _y + KEY_LINE_HEIGHT / 2; _p.setPen( noteColor() ); + _p.setClipRect(WHITE_KEY_WIDTH, PR_TOP_MARGIN, + width() - WHITE_KEY_WIDTH, + keyAreaBottom() - PR_TOP_MARGIN); int old_x = 0; int old_y = 0; @@ -1188,9 +1191,11 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) { const int key_num = PianoView::getKeyFromKeyEvent( ke ) + ( DefaultOctave - 1 ) * KeysPerOctave; - if(! ke->isAutoRepeat() && key_num > -1) + if (!ke->isAutoRepeat() && key_num > -1) { - m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( key_num ); + m_pattern->instrumentTrack()->pianoModel()->handleKeyPress(key_num); + // if a chord is set, play all chord notes (simulate click on all): + playChordNotes(key_num); ke->accept(); } } @@ -1388,10 +1393,11 @@ void PianoRoll::keyReleaseEvent(QKeyEvent* ke ) if( hasValidPattern() && ke->modifiers() == Qt::NoModifier ) { const int key_num = PianoView::getKeyFromKeyEvent( ke ) + ( DefaultOctave - 1 ) * KeysPerOctave; - - if( ! ke->isAutoRepeat() && key_num > -1 ) + if (!ke->isAutoRepeat() && key_num > -1) { - m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( key_num ); + m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease(key_num); + // if a chord is set, simulate click release on all chord notes + pauseChordNotes(key_num); ke->accept(); } } @@ -1836,7 +1842,9 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) { // left click - play the note int v = ( (float) x ) / ( (float) WHITE_KEY_WIDTH ) * MidiDefaultVelocity; - m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( key_num, v ); + m_pattern->instrumentTrack()->pianoModel()->handleKeyPress(key_num, v); + // if a chord is set, play the chords notes as well: + playChordNotes(key_num, v); } } else @@ -1939,7 +1947,10 @@ void PianoRoll::testPlayNote( Note * n ) const int baseVelocity = m_pattern->instrumentTrack()->midiPort()->baseVelocity(); - m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( n->key(), n->midiVelocity( baseVelocity ) ); + m_pattern->instrumentTrack()->pianoModel()->handleKeyPress(n->key(), n->midiVelocity(baseVelocity)); + + // if a chord is set, play the chords notes as well: + playChordNotes(n->key(), n->midiVelocity(baseVelocity)); MidiEvent event( MidiMetaEvent, -1, n->key(), panningToMidi( n->getPanning() ) ); @@ -1962,6 +1973,9 @@ void PianoRoll::pauseTestNotes( bool pause ) { // stop note m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( note->key() ); + + // if a chord was set, stop the chords notes as well: + pauseChordNotes(note->key()); } else { @@ -1973,19 +1987,56 @@ void PianoRoll::pauseTestNotes( bool pause ) } } +void PianoRoll::playChordNotes(int key, int velocity) +{ + // if a chord is set, play the chords notes beside the base note. + Piano *pianoModel = m_pattern->instrumentTrack()->pianoModel(); + const InstrumentFunctionNoteStacking::Chord & chord = + InstrumentFunctionNoteStacking::ChordTable::getInstance().getChordByName( + m_chordModel.currentText()); + if (!chord.isEmpty()) + { + for (int i = 1; i < chord.size(); ++i) + { + pianoModel->handleKeyPress(key + chord[i], velocity); + } + } +} + +void PianoRoll::pauseChordNotes(int key) +{ + // if a chord was set, stop the chords notes beside the base note. + Piano *pianoModel = m_pattern->instrumentTrack()->pianoModel(); + const InstrumentFunctionNoteStacking::Chord & chord = + InstrumentFunctionNoteStacking::ChordTable::getInstance().getChordByName( + m_chordModel.currentText()); + if (!chord.isEmpty()) + { + for (int i = 1; i < chord.size(); ++i) + { + pianoModel->handleKeyRelease(key + chord[i]); + } + } +} + void PianoRoll::testPlayKey( int key, int velocity, int pan ) { + Piano *pianoModel = m_pattern->instrumentTrack()->pianoModel(); // turn off old key - m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( m_lastKey ); + pianoModel->handleKeyRelease( m_lastKey ); + // if a chord was set, stop the chords notes as well + pauseChordNotes(m_lastKey); // remember which one we're playing m_lastKey = key; // play new key - m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( key, velocity ); + pianoModel->handleKeyPress( key, velocity ); + // and if a chord is set, play chord notes: + playChordNotes(key, velocity); } @@ -2115,6 +2166,7 @@ void PianoRoll::mouseReleaseEvent( QMouseEvent * me ) { m_pattern->instrumentTrack()->pianoModel()-> handleKeyRelease( note->key() ); + pauseChordNotes(note->key()); note->setIsPlaying( false ); } } @@ -2122,6 +2174,7 @@ void PianoRoll::mouseReleaseEvent( QMouseEvent * me ) // stop playing keys that we let go of m_pattern->instrumentTrack()->pianoModel()-> handleKeyRelease( m_lastKey ); + pauseChordNotes(m_lastKey); } m_currentNote = NULL; @@ -2309,6 +2362,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) { // mouse not over this note, stop playing it. m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( n->key() ); + pauseChordNotes(n->key()); n->setIsPlaying( false ); } @@ -3255,6 +3309,9 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) drawDetuningInfo( p, note, x + WHITE_KEY_WIDTH, y_base - key * KEY_LINE_HEIGHT ); + p.setClipRect(WHITE_KEY_WIDTH, PR_TOP_MARGIN, + width() - WHITE_KEY_WIDTH, + height() - PR_TOP_MARGIN); } } diff --git a/src/gui/widgets/Graph.cpp b/src/gui/widgets/Graph.cpp index f93ed523c..4710089dd 100644 --- a/src/gui/widgets/Graph.cpp +++ b/src/gui/widgets/Graph.cpp @@ -635,13 +635,14 @@ void graphModel::smoothNonCyclic() emit samplesChanged(0, length()-1); } -//makes a cyclic convolution. -void graphModel::convolve(const float *convolution, const int convolutionLength, const int centerOffset) +void graphModel::convolve(const float *convolution, + const int convolutionLength, const int centerOffset) { // store values in temporary array QVector temp = m_samples; const int graphLength = length(); float sum; + // make a cyclic convolution for ( int i = 0; i < graphLength; i++ ) { sum = 0; diff --git a/src/gui/widgets/TabWidget.cpp b/src/gui/widgets/TabWidget.cpp index f06710098..9bdbec2e0 100644 --- a/src/gui/widgets/TabWidget.cpp +++ b/src/gui/widgets/TabWidget.cpp @@ -34,8 +34,10 @@ #include "gui_templates.h" #include "embed.h" -TabWidget::TabWidget( const QString & caption, QWidget * parent, bool usePixmap ) : +TabWidget::TabWidget(const QString & caption, QWidget * parent, bool usePixmap, + bool resizable) : QWidget( parent ), + m_resizable( resizable ), m_activeTab( 0 ), m_caption( caption ), m_usePixmap( usePixmap ), @@ -81,7 +83,10 @@ void TabWidget::addTab( QWidget * w, const QString & name, const char *pixmap, i m_widgets[idx] = d; // Position tab's window - w->setFixedSize( width() - 4, height() - m_tabbarHeight ); + if (!m_resizable) + { + w->setFixedSize( width() - 4, height() - m_tabbarHeight ); + } w->move( 2, m_tabbarHeight - 1 ); w->hide(); @@ -189,17 +194,19 @@ void TabWidget::mousePressEvent( QMouseEvent * me ) void TabWidget::resizeEvent( QResizeEvent * ) { - for( widgetStack::iterator it = m_widgets.begin(); - it != m_widgets.end(); ++it ) + if (!m_resizable) { - ( *it ).w->setFixedSize( width() - 4, height() - m_tabbarHeight ); + for ( widgetStack::iterator it = m_widgets.begin(); + it != m_widgets.end(); ++it ) + { + ( *it ).w->setFixedSize( width() - 4, height() - m_tabbarHeight ); + } } } - void TabWidget::paintEvent( QPaintEvent * pe ) { QPainter p( this ); @@ -284,7 +291,7 @@ void TabWidget::wheelEvent( QWheelEvent * we ) if( we->y() > m_tabheight ) { return; - } + } we->accept(); int dir = ( we->delta() < 0 ) ? 1 : -1; @@ -300,6 +307,32 @@ void TabWidget::wheelEvent( QWheelEvent * we ) setActiveTab( tab ); } + + + +// Let parent widgets know how much space this tab widget needs +QSize TabWidget::minimumSizeHint() const +{ + if (m_resizable) + { + int maxWidth = 0, maxHeight = 0; + for ( widgetStack::const_iterator it = m_widgets.begin(); + it != m_widgets.end(); ++it ) + { + maxWidth = std::max(maxWidth, it->w->width()); + maxHeight = std::max(maxHeight, it->w->height()); + } + // "-1" : + // in "addTab", under "Position tab's window", the widget is + // moved up by 1 pixel + return QSize(maxWidth + 4, maxHeight + m_tabbarHeight - 1); + } + else { return QWidget::minimumSizeHint(); } +} + + + + // Return the color to be used to draw a TabWidget's title text (if any) QColor TabWidget::tabTitleText() const { diff --git a/src/tracks/BBTrack.cpp b/src/tracks/BBTrack.cpp index c37c1466f..205a22087 100644 --- a/src/tracks/BBTrack.cpp +++ b/src/tracks/BBTrack.cpp @@ -635,5 +635,6 @@ bool BBTrackView::close() void BBTrackView::clickedTrackLabel() { Engine::getBBTrackContainer()->setCurrentBB( m_bbTrack->index() ); - gui->getBBEditor()->show(); + gui->getBBEditor()->parentWidget()->show(); + gui->getBBEditor()->setFocus( Qt::ActiveWindowFocusReason ); } diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 0142952cf..6c95f3c9a 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -1298,7 +1298,7 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : this, SLOT( textChanged( const QString & ) ) ); m_nameLineEdit->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); - nameAndChangeTrackLayout->addWidget(m_nameLineEdit); + nameAndChangeTrackLayout->addWidget(m_nameLineEdit, 1); // set up left/right arrows for changing instrument @@ -1410,8 +1410,11 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : generalSettingsLayout->addLayout( basicControlsLayout ); - m_tabWidget = new TabWidget( "", this, true ); - m_tabWidget->setFixedHeight( INSTRUMENT_HEIGHT + GRAPHIC_TAB_HEIGHT - 4 ); + m_tabWidget = new TabWidget( "", this, true, true ); + // "-1" : + // in "TabWidget::addTab", under "Position tab's window", the widget is + // moved up by 1 pixel + m_tabWidget->setMinimumHeight( INSTRUMENT_HEIGHT + GRAPHIC_TAB_HEIGHT - 4 - 1 ); // create tab-widgets @@ -1443,24 +1446,27 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : m_tabWidget->addTab( m_effectView, tr( "Effects" ), "fx_tab", 3 ); m_tabWidget->addTab( m_midiView, tr( "MIDI" ), "midi_tab", 4 ); m_tabWidget->addTab( m_miscView, tr( "Miscellaneous" ), "misc_tab", 5 ); + adjustTabSize(m_ssView); + adjustTabSize(instrumentFunctions); + adjustTabSize(m_effectView); + adjustTabSize(m_midiView); + adjustTabSize(m_miscView); // setup piano-widget m_pianoView = new PianoView( this ); - m_pianoView->setFixedSize( INSTRUMENT_WIDTH, PIANO_HEIGHT ); + m_pianoView->setMinimumHeight( PIANO_HEIGHT ); + m_pianoView->setMaximumHeight( PIANO_HEIGHT ); vlayout->addWidget( generalSettingsWidget ); - vlayout->addWidget( m_tabWidget ); + vlayout->addWidget( m_tabWidget, 1 ); vlayout->addWidget( m_pianoView ); - - setModel( _itv->model() ); updateInstrumentView(); - setFixedWidth( INSTRUMENT_WIDTH ); resize( sizeHint() ); - QMdiSubWindow * subWin = gui->mainWindow()->addWindowedWidget( this ); + QMdiSubWindow* subWin = gui->mainWindow()->addWindowedWidget( this ); Qt::WindowFlags flags = subWin->windowFlags(); flags |= Qt::MSWindowsFixedSizeDialogHint; flags &= ~Qt::WindowMaximizeButtonHint; @@ -1473,7 +1479,7 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : systemMenu->actions().at( 4 )->setVisible( false ); // Maximize subWin->setWindowIcon( embed::getIconPixmap( "instrument_track" ) ); - subWin->setFixedSize( subWin->size() ); + subWin->setMinimumSize( subWin->size() ); subWin->hide(); } @@ -1624,6 +1630,7 @@ void InstrumentTrackWindow::updateInstrumentView() modelChanged(); // Get the instrument window to refresh m_track->dataChanged(); // Get the text on the trackButton to change + adjustTabSize(m_instrumentView); m_pianoView->setVisible(m_track->m_instrument->hasNoteInput()); } } @@ -1819,6 +1826,7 @@ void InstrumentTrackWindow::viewInstrumentInDirection(int d) // get the instrument window to refresh modelChanged(); } + Q_ASSERT(bringToFront); bringToFront->getInstrumentTrackWindow()->setFocus(); } @@ -1831,4 +1839,12 @@ void InstrumentTrackWindow::viewPrevInstrument() viewInstrumentInDirection(-1); } +void InstrumentTrackWindow::adjustTabSize(QWidget *w) +{ + // "-1" : + // in "TabWidget::addTab", under "Position tab's window", the widget is + // moved up by 1 pixel + w->setMinimumSize(INSTRUMENT_WIDTH - 4, INSTRUMENT_HEIGHT - 4 - 1); +} + #include "InstrumentTrack.moc" diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index c965af140..959d6389e 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -65,7 +65,7 @@ SampleTCO::SampleTCO( Track * _track ) : // we need to receive bpm-change-events, because then we have to // change length of this TCO connect( Engine::getSong(), SIGNAL( tempoChanged( bpm_t ) ), - this, SLOT( updateLength() ) ); + this, SLOT( updateLength() ), Qt::DirectConnection ); connect( Engine::getSong(), SIGNAL( timeSignatureChanged( int,int ) ), this, SLOT( updateLength() ) ); @@ -110,7 +110,7 @@ SampleTCO::SampleTCO( Track * _track ) : SampleTCO::~SampleTCO() { SampleTrack * sampletrack = dynamic_cast( getTrack() ); - if( sampletrack) + if ( sampletrack ) { sampletrack->updateTcos(); } @@ -122,10 +122,7 @@ SampleTCO::~SampleTCO() void SampleTCO::changeLength( const MidiTime & _length ) { - float nom = Engine::getSong()->getTimeSigModel().getNumerator(); - float den = Engine::getSong()->getTimeSigModel().getDenominator(); - int ticksPerTact = DefaultTicksPerTact * ( nom / den ); - TrackContentObject::changeLength( qMax( static_cast( _length ), ticksPerTact ) ); + TrackContentObject::changeLength( qMax( static_cast( _length ), 1 ) ); } @@ -151,9 +148,21 @@ void SampleTCO::setSampleBuffer( SampleBuffer* sb ) void SampleTCO::setSampleFile( const QString & _sf ) { - m_sampleBuffer->setAudioFile( _sf ); + int length; + if ( _sf.isEmpty() ) + { //When creating an empty sample pattern make it a bar long + float nom = Engine::getSong()->getTimeSigModel().getNumerator(); + float den = Engine::getSong()->getTimeSigModel().getDenominator(); + length = DefaultTicksPerTact * ( nom / den ); + } + else + { //Otherwise set it to the sample's length + m_sampleBuffer->setAudioFile( _sf ); + length = sampleLength(); + } + changeLength(length); + setStartTimeOffset( 0 ); - changeLength( (int) ( m_sampleBuffer->frames() / Engine::framesPerTick() ) ); emit sampleChanged(); emit playbackPositionChanged(); @@ -440,8 +449,15 @@ void SampleTCOView::mouseReleaseEvent(QMouseEvent *_me) void SampleTCOView::mouseDoubleClickEvent( QMouseEvent * ) { QString af = m_tco->m_sampleBuffer->openAudioFile(); - if( af != "" && af != m_tco->m_sampleBuffer->audioFile() ) - { + + if ( af.isEmpty() ) {} //Don't do anything if no file is loaded + else if ( af == m_tco->m_sampleBuffer->audioFile() ) + { //Instead of reloading the existing file, just reset the size + int length = (int) ( m_tco->m_sampleBuffer->frames() / Engine::framesPerTick() ); + m_tco->changeLength(length); + } + else + { //Otherwise load the new file as ususal m_tco->setSampleFile( af ); Engine::getSong()->setModified(); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c39f8e56e..ddebe116c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,6 +14,7 @@ ADD_EXECUTABLE(tests QTestSuite $ + src/core/AutomatableModelTest.cpp src/core/ProjectVersionTest.cpp src/core/RelativePathsTest.cpp diff --git a/tests/src/core/AutomatableModelTest.cpp b/tests/src/core/AutomatableModelTest.cpp new file mode 100644 index 000000000..116f95e60 --- /dev/null +++ b/tests/src/core/AutomatableModelTest.cpp @@ -0,0 +1,55 @@ +/* + * AutomatableModelTest.cpp + * + * Copyright (c) 2019-2019 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "QTestSuite.h" + +#include "AutomatableModel.h" +#include "ComboBoxModel.h" + +class AutomatableModelTest : QTestSuite +{ + Q_OBJECT + +private slots: + //! Test that upcast and exact casts work, + //! but no downcast or any other casts + void CastTests() + { + ComboBoxModel comboModel; + AutomatableModel* amPtr = &comboModel; + QVERIFY(nullptr == amPtr->dynamicCast()); // not a parent class + QCOMPARE(&comboModel, amPtr->dynamicCast()); // parent class + QCOMPARE(&comboModel, amPtr->dynamicCast()); // parent class + QCOMPARE(&comboModel, amPtr->dynamicCast()); // same class + + IntModel intModel; + IntModel* imPtr = &intModel; + QVERIFY(nullptr == imPtr->dynamicCast()); // not a parent class + QCOMPARE(&intModel, imPtr->dynamicCast()); // parent class + QCOMPARE(&intModel, imPtr->dynamicCast()); // same class + QVERIFY(nullptr == imPtr->dynamicCast()); // child class + } +} AutomatableModelTests; + +#include "AutomatableModelTest.moc"