From 957ec6b611af5abcc7a7822c7bbc3b8f3eec9240 Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Fri, 9 Mar 2018 11:47:18 -0500 Subject: [PATCH] Initial global groove quantization feature Add initial support for "Groove quantizing". This is a squash of all work by @teknopaul, @tresf, @Sawuare to make it mergabe against master. --- data/themes/default/groove.png | Bin 0 -> 231 bytes include/AutomatableControlButton.h | 62 +++++ include/Groove.h | 71 ++++++ include/GrooveExperiments.h | 74 ++++++ include/GrooveFactory.h | 41 ++++ include/GrooveView.h | 37 +++ include/GuiApplication.h | 6 + include/HalfSwing.h | 72 ++++++ include/HydrogenSwing.h | 74 ++++++ include/InstrumentTrack.h | 11 + include/MainWindow.h | 2 + include/MidiController.h | 1 + include/MidiSwing.h | 43 ++++ include/Song.h | 12 +- include/SongEditor.h | 13 +- include/StudioControllerView.h | 53 +++++ include/Track.h | 3 + plugins/Xpressive/exprtk.hpp.patch | 13 ++ src/core/CMakeLists.txt | 6 + src/core/Groove.cpp | 40 ++++ src/core/GrooveExperiments.cpp | 216 ++++++++++++++++++ src/core/GrooveFactory.cpp | 31 +++ src/core/HalfSwing.cpp | 222 ++++++++++++++++++ src/core/HydrogenSwing.cpp | 227 +++++++++++++++++++ src/core/MidiSwing.cpp | 102 +++++++++ src/core/Song.cpp | 32 ++- src/core/Track.cpp | 61 ++++- src/core/midi/MidiController.cpp | 8 +- src/gui/CMakeLists.txt | 3 + src/gui/GuiApplication.cpp | 19 ++ src/gui/MainWindow.cpp | 31 ++- src/gui/editors/SongEditor.cpp | 85 ++++++- src/gui/widgets/AutomatableControlButton.cpp | 107 +++++++++ src/gui/widgets/GrooveView.cpp | 157 +++++++++++++ src/gui/widgets/StudioControllerView.cpp | 220 ++++++++++++++++++ src/tracks/InstrumentTrack.cpp | 88 +++++-- 36 files changed, 2212 insertions(+), 31 deletions(-) create mode 100644 data/themes/default/groove.png create mode 100644 include/AutomatableControlButton.h create mode 100644 include/Groove.h create mode 100644 include/GrooveExperiments.h create mode 100644 include/GrooveFactory.h create mode 100644 include/GrooveView.h create mode 100644 include/HalfSwing.h create mode 100644 include/HydrogenSwing.h create mode 100644 include/MidiSwing.h create mode 100644 include/StudioControllerView.h create mode 100644 plugins/Xpressive/exprtk.hpp.patch create mode 100644 src/core/Groove.cpp create mode 100644 src/core/GrooveExperiments.cpp create mode 100644 src/core/GrooveFactory.cpp create mode 100644 src/core/HalfSwing.cpp create mode 100644 src/core/HydrogenSwing.cpp create mode 100644 src/core/MidiSwing.cpp create mode 100644 src/gui/widgets/AutomatableControlButton.cpp create mode 100644 src/gui/widgets/GrooveView.cpp create mode 100644 src/gui/widgets/StudioControllerView.cpp diff --git a/data/themes/default/groove.png b/data/themes/default/groove.png new file mode 100644 index 0000000000000000000000000000000000000000..542d3f52476dc4b67d1c1b414b444602040c98eb GIT binary patch literal 231 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VTavfC3&Vd9T(EcfWS|IVfk$L9 zP{(x;W^~e+T>%tiFY)wsWq-&cz{jAoCwb-vpiqgYi(`nz>8Dc+xf%>aoaaAMytOp; zR<@;O8VEa!T3>7jK;qYfua9aBBW$_Jf-={wl{7>@7T*>W_-zTi`KBU{9o zscxQXCzICQ{b%asEbV^D`Sr*BS07q8uKiFMnIE;D-@${`xH!?{Y5!uL|J57RuU$$m T{Li)k + * + * This file is part of LMMS - http://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 AUTOMATABLE_CONTROL_BUTTON_H +#define AUTOMATABLE_CONTROL_BUTTON_H + +#include + +#include "AutomatableModelView.h" + + +class automatableButtonGroup; + + +class EXPORT AutomatableControlButton : public QPushButton, public FloatModelView +{ + Q_OBJECT +public: + AutomatableControlButton( QWidget * _parent, const QString & _name = QString::null ); + virtual ~AutomatableControlButton(); + + virtual void modelChanged(); + + +public slots: + virtual void update(); + +protected: + virtual void contextMenuEvent( QContextMenuEvent * _me ); + virtual void mousePressEvent( QMouseEvent * _me ); + virtual void mouseReleaseEvent( QMouseEvent * _me ); + + +private: + +} ; + + +#endif diff --git a/include/Groove.h b/include/Groove.h new file mode 100644 index 000000000..ee5936bed --- /dev/null +++ b/include/Groove.h @@ -0,0 +1,71 @@ +/* + * Groove.h - classes for addinng swing/funk/groove/slide (you can't name it but you can feel it) + * to midi which is not precise enough at 192 ticks per tact to make your arse move. + * + * In it simplest terms a groove is a subtle delay on some notes in a pattern. + * + * Copyright (c) 2005-2008 teknopaul + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 GROOVE_H +#define GROOVE_H + +#include + +#include "lmms_basics.h" +#include "MidiTime.h" +#include "Note.h" +#include "Pattern.h" +#include "SerializingObject.h" + +class Groove : public SerializingObject +{ + +public: + Groove(); + + /* + * Groove should return true if the note should be played in the curr_time tick, + * at the start of the tick or any time before the next tick. + * + * cur_start - the tick about to be played + * n - the note to be played, or not played + * p - the pattern to which the note belongs + * + * default implementation (no groove) would be return n.pos() == cur_start ? 0 : -1; + * + * returns 0 to play now on the tick, -1 to not play at all and the new offset + * that the note should be shifted if it is to be played later in this tick. + */ + virtual int isInTick( MidiTime * _cur_start, fpp_t _frames, f_cnt_t _offset, + Note * _n, Pattern * _p ); + + virtual void saveSettings( QDomDocument & _doc, QDomElement & _element ); + virtual void loadSettings( const QDomElement & _this ); + + virtual QWidget * instantiateView( QWidget * _parent ); + + virtual QString nodeName() const + { + return "none"; + } +}; + +#endif // GROOVE_H diff --git a/include/GrooveExperiments.h b/include/GrooveExperiments.h new file mode 100644 index 000000000..3f2aaae2a --- /dev/null +++ b/include/GrooveExperiments.h @@ -0,0 +1,74 @@ +#ifndef GROOVEEXPERIMENTS_H +#define GROOVEEXPERIMENTS_H + +#include + +#include "Groove.h" +#include "Knob.h" +#include "lmms_basics.h" +#include "MidiTime.h" +#include "Note.h" +#include "Pattern.h" + +/** + * A groove thats new + */ +class GrooveExperiments : public QObject, public Groove +{ + Q_OBJECT +public: + GrooveExperiments(QObject *parent=0 ); + + virtual ~GrooveExperiments(); + + void init(); + int amount(); + + int isInTick(MidiTime * _cur_start, const fpp_t _frames, const f_cnt_t _offset, Note * _n, Pattern * _p ); + + void loadSettings( const QDomElement & _this ); + void saveSettings( QDomDocument & _doc, QDomElement & _element ); + inline virtual QString nodeName() const + { + return "experiment"; + } + + + + QWidget * instantiateView( QWidget * _parent ); + +signals: + void shiftAmountChanged(int _newAmount); + + +public slots: + // valid values are from 0 - 127 + void setAmount(int _amount); + void update(); + +private: + int m_frames_per_tick; + int m_shiftAmount; + float m_shiftFactor;// = (m_shiftAmount / 127.0) + +} ; + +class GrooveExperimentsView : public QWidget +{ + Q_OBJECT +public: + GrooveExperimentsView(GrooveExperiments * _m_ge, QWidget * parent=0 ); + ~GrooveExperimentsView(); + +public slots: + void modelChanged(); + void valueChanged(float); + +private: + GrooveExperiments * m_ge; + FloatModel * m_nobModel; + Knob * m_nob; + +} ; + +#endif // GROOVEEXPERIMENTS_H diff --git a/include/GrooveFactory.h b/include/GrooveFactory.h new file mode 100644 index 000000000..9ad95657f --- /dev/null +++ b/include/GrooveFactory.h @@ -0,0 +1,41 @@ +/* + * Groove.h - classes for addinng swing/funk/groove/slide (you can't name it but you can feel it) + * to midi which is not precise enough at 192 ticks per tact to make your arse move. + * + * In it simplest terms a groove is a subtle delay on some notes in a pattern. + * + * Copyright (c) 2005-2008 teknopaul + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 GROOVEFACTORY_H +#define GROOVEFACTORY_H + +#include "Groove.h" + +class GrooveFactory +{ + +public: + static Groove * create(QString type); + +private: + GrooveFactory(); +}; + +#endif // GROOVEFACTORY_H diff --git a/include/GrooveView.h b/include/GrooveView.h new file mode 100644 index 000000000..03b0b221a --- /dev/null +++ b/include/GrooveView.h @@ -0,0 +1,37 @@ + + +#ifndef GROOVEVIEW_H +#define GROOVEVIEW_H + +#include +#include + +#include +#include + +#include "Groove.h" +#include "SerializingObject.h" + +class GrooveView : public QWidget +{ + Q_OBJECT +public: + GrooveView(); + virtual ~GrooveView(); + + void clear(); + +signals: + +public slots: + void update(); + void grooveChanged(int index); + +private: + void setView(Groove * groove); + + QComboBox * m_dropDown; + QVBoxLayout * m_layout; +}; + +#endif // GROOVEVIEW_H diff --git a/include/GuiApplication.h b/include/GuiApplication.h index 999db3009..966afd71b 100644 --- a/include/GuiApplication.h +++ b/include/GuiApplication.h @@ -35,6 +35,8 @@ class AutomationEditorWindow; class BBEditor; class ControllerRackView; class FxMixerView; +class GrooveView; +class StudioControllerView; class MainWindow; class PianoRollWindow; class ProjectNotes; @@ -51,6 +53,8 @@ public: MainWindow* mainWindow() { return m_mainWindow; } FxMixerView* fxMixerView() { return m_fxMixerView; } + GrooveView* grooveView() { return m_grooveView; } + StudioControllerView* studioControllerView() { return m_studioControllerView; } SongEditorWindow* songEditor() { return m_songEditor; } BBEditor* getBBEditor() { return m_bbEditor; } PianoRollWindow* pianoRoll() { return m_pianoRoll; } @@ -69,6 +73,8 @@ private: MainWindow* m_mainWindow; FxMixerView* m_fxMixerView; + GrooveView* m_grooveView; + StudioControllerView* m_studioControllerView; SongEditorWindow* m_songEditor; AutomationEditorWindow* m_automationEditor; BBEditor* m_bbEditor; diff --git a/include/HalfSwing.h b/include/HalfSwing.h new file mode 100644 index 000000000..0fd1f28ff --- /dev/null +++ b/include/HalfSwing.h @@ -0,0 +1,72 @@ +#ifndef HALFSWING_H +#define HALFSWING_H + +#include + +#include "Groove.h" +#include "Knob.h" +#include "lmms_basics.h" +#include "MidiTime.h" +#include "Note.h" +#include "Pattern.h" + +/** + * A groove thatjust latter half of the HydrogenSwing algo. + */ +class HalfSwing : public QObject, public Groove +{ + Q_OBJECT +public: + HalfSwing(QObject *parent=0 ); + + virtual ~HalfSwing(); + + void init(); + int amount(); + + int isInTick(MidiTime * _cur_start, const fpp_t _frames, const f_cnt_t _offset, Note * _n, Pattern * _p ); + + void loadSettings( const QDomElement & _this ); + void saveSettings( QDomDocument & _doc, QDomElement & _element ); + inline virtual QString nodeName() const + { + return "half"; + } + + QWidget * instantiateView( QWidget * _parent ); + +signals: + void swingAmountChanged(int _newAmount); + + +public slots: + // valid values are from 0 - 127 + void setAmount(int _amount); + void update(); + +private: + int m_frames_per_tick; + int m_swingAmount; + float m_swingFactor;// = (m_swingAmount / 127.0) + +} ; + +class HalfSwingView : public QWidget +{ + Q_OBJECT +public: + HalfSwingView(HalfSwing * _half_swing, QWidget * parent=0 ); + ~HalfSwingView(); + +public slots: + void modelChanged(); + void valueChanged(float); + +private: + HalfSwing * m_half_swing; + FloatModel * m_nobModel; + Knob * m_nob; + +} ; + +#endif // HALFSWING_H diff --git a/include/HydrogenSwing.h b/include/HydrogenSwing.h new file mode 100644 index 000000000..44f99aa2e --- /dev/null +++ b/include/HydrogenSwing.h @@ -0,0 +1,74 @@ +#ifndef HYDROGENSWING_H +#define HYDROGENSWING_H + +#include + +#include "Groove.h" +#include "Knob.h" +#include "lmms_basics.h" +#include "MidiTime.h" +#include "Note.h" +#include "Pattern.h" + +/** + * A groove that mimics Hydrogen drum machine's swing feature + */ +class HydrogenSwing : public QObject, public Groove +{ + Q_OBJECT +public: + HydrogenSwing(QObject *parent=0 ); + + virtual ~HydrogenSwing(); + + void init(); + int amount(); + + int isInTick(MidiTime * _cur_start, const fpp_t _frames, const f_cnt_t _offset, Note * _n, Pattern * _p ); + + void loadSettings( const QDomElement & _this ); + void saveSettings( QDomDocument & _doc, QDomElement & _element ); + inline virtual QString nodeName() const + { + return "hydrogen"; + } + + + + QWidget * instantiateView( QWidget * _parent ); + +signals: + void swingAmountChanged(int _newAmount); + + +public slots: + // valid values are from 0 - 127 + void setAmount(int _amount); + void update(); + +private: + int m_frames_per_tick; + int m_swingAmount; + float m_swingFactor;// = (m_swingAmount / 127.0) + +} ; + +class HydrogenSwingView : public QWidget +{ + Q_OBJECT +public: + HydrogenSwingView(HydrogenSwing * _hy_swing, QWidget * parent=0 ); + ~HydrogenSwingView(); + +public slots: + void modelChanged(); + void valueChanged(float); + +private: + HydrogenSwing * m_hy_swing; + FloatModel * m_nobModel; + Knob * m_nob; + +} ; + +#endif // HYDROGENSWING_H diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index 5ef604def..ac4c36937 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -27,6 +27,7 @@ #define INSTRUMENT_TRACK_H #include "AudioPort.h" +#include "Groove.h" #include "GroupBox.h" #include "InstrumentFunctions.h" #include "InstrumentSoundShaping.h" @@ -90,6 +91,8 @@ public: f_cnt_t beatLen( NotePlayHandle * _n ) const; + void disableGroove(); + void enableGroove(); // for capturing note-play-events -> need that for arpeggio, // filter and so on @@ -153,6 +156,8 @@ public: return &m_audioPort; } + Groove * groove(); + MidiPort * midiPort() { return &m_midiPort; @@ -256,6 +261,11 @@ private: FloatModel m_panningModel; AudioPort m_audioPort; + + // Track specific groove or NULL + Groove * m_groove; + Groove * m_noGroove; + bool m_grooveOn; //if true temporarily return nooop Groove for the groove FloatModel m_pitchModel; IntModel m_pitchRangeModel; @@ -271,6 +281,7 @@ private: Piano m_piano; + friend class InstrumentTrackView; friend class InstrumentTrackWindow; friend class NotePlayHandle; diff --git a/include/MainWindow.h b/include/MainWindow.h index 6f243bfab..788d27515 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -166,6 +166,8 @@ public slots: void toggleFxMixerWin(); void togglePianoRollWin(); void toggleControllerRack(); + void toggleStudioControllerView(); + void toggleGrooveView(); void updatePlayPauseIcons(); diff --git a/include/MidiController.h b/include/MidiController.h index d661b8d0f..87d370976 100644 --- a/include/MidiController.h +++ b/include/MidiController.h @@ -53,6 +53,7 @@ public: } virtual void saveSettings( QDomDocument & _doc, QDomElement & _this ); + virtual void saveControllerSettings( QDomDocument & _doc, QDomElement & _this ); virtual void loadSettings( const QDomElement & _this ); virtual QString nodeName() const; diff --git a/include/MidiSwing.h b/include/MidiSwing.h new file mode 100644 index 000000000..396dcd8f0 --- /dev/null +++ b/include/MidiSwing.h @@ -0,0 +1,43 @@ +#ifndef MIDISWING_H +#define MIDISWING_H + +#include + +#include "Groove.h" +#include "lmms_basics.h" +#include "MidiTime.h" +#include "Note.h" +#include "Pattern.h" + +/* + * A swing groove that adjusts by whole ticks. + * Someone might like it, also might be able to save the output to a midi file later. + */ +class MidiSwing : public QObject, public Groove +{ + Q_OBJECT +public: + MidiSwing(QObject * _parent=0 ); + + ~MidiSwing(); + + // TODO why declaring this should it not come from super class? + int isInTick(MidiTime * cur_start, const fpp_t _frames, const f_cnt_t _offset, Note * n, Pattern * p ); + int isInTick(MidiTime * _cur_start, Note * _n, Pattern * _p ); + + void loadSettings( const QDomElement & _this ); + void saveSettings( QDomDocument & _doc, QDomElement & _element ); + inline virtual QString nodeName() const + { + return "midi"; + } + + QWidget * instantiateView( QWidget * _parent ); + +signals: + +public slots: + +}; + +#endif // MIDISWING_H diff --git a/include/Song.h b/include/Song.h index 8b2c21316..51b397b9c 100644 --- a/include/Song.h +++ b/include/Song.h @@ -35,11 +35,13 @@ #include "MeterModel.h" #include "Mixer.h" #include "VstSyncController.h" +#include "Groove.h" class AutomationTrack; class Pattern; class TimeLineWidget; +class Groove; const bpm_t MinTempo = 10; @@ -236,6 +238,11 @@ public: return m_globalAutomationTrack; } + Groove * globalGroove() + { + return m_globalGroove; + } + //TODO: Add Q_DECL_OVERRIDE when Qt4 is dropped AutomatedValueMap automatedValuesAt(MidiTime time, int tcoNum = -1) const; @@ -246,6 +253,7 @@ public: bool guiSaveProject(); bool guiSaveProjectAs( const QString & filename ); bool saveProjectFile( const QString & filename ); + void setGlobalGroove(Groove * groove); const QString & projectFileName() const { @@ -310,6 +318,7 @@ public slots: void playPattern( const Pattern * patternToPlay, bool loop = true ); void togglePause(); void stop(); + void setPlayPos( tick_t ticks, PlayModes playMode ); void startExport(); void stopExport(); @@ -361,8 +370,6 @@ private: m_playPos[m_playMode].currentFrame(); } - void setPlayPos( tick_t ticks, PlayModes playMode ); - void saveControllerStates( QDomDocument & doc, QDomElement & element ); void restoreControllerStates( const QDomElement & element ); @@ -375,6 +382,7 @@ private: void setProjectFileName(QString const & projectFileName); AutomationTrack * m_globalAutomationTrack; + Groove * m_globalGroove; IntModel m_tempoModel; MeterModel m_timeSigModel; diff --git a/include/SongEditor.h b/include/SongEditor.h index d6e904ace..064317b03 100644 --- a/include/SongEditor.h +++ b/include/SongEditor.h @@ -110,7 +110,7 @@ private: virtual void wheelEvent( QWheelEvent * we ); virtual bool allowRubberband() const; - + void scrollToPos( const MidiTime & t ); Song * m_song; @@ -159,11 +159,18 @@ public: protected: virtual void resizeEvent( QResizeEvent * event ); -protected slots: +public slots: void play(); void record(); - void recordAccompany(); void stop(); + void home(); + void next(); + void prev(); + void end(); + + +protected slots: + void recordAccompany(); void lostFocus(); void adjustUiAfterProjectLoad(); diff --git a/include/StudioControllerView.h b/include/StudioControllerView.h new file mode 100644 index 000000000..2f3864f4b --- /dev/null +++ b/include/StudioControllerView.h @@ -0,0 +1,53 @@ + + +#ifndef STUDIOCONTROLLERVIEW_H +#define STUDIOCONTROLLERVIEW_H + +#include +#include + +#include +#include + +#include "SerializingObject.h" +#include "AutomatableControlButton.h" + +class StudioControllerView : public QWidget +{ + Q_OBJECT +public: + StudioControllerView(); + virtual ~StudioControllerView(); + +signals: + +public slots: + void controllerChanged(int index); + void doHome(); + void doPlay(); + void doStop(); + void doRecord(); + void doNext(); + void doPrev(); + void doScroll(); + void saveControllers(); + void loadControllers(); + +private: + float m_scrollLast; + + QVBoxLayout * m_layout; + QComboBox * m_dropDown; + QLabel * m_controllerLabel; + QLabel * m_actionsLabel; + AutomatableControlButton * m_homeButton; + AutomatableControlButton * m_playButton; + AutomatableControlButton * m_stopButton; + AutomatableControlButton * m_recordButton; + AutomatableControlButton * m_nextButton; + AutomatableControlButton * m_prevButton; + AutomatableControlButton * m_scrollButton; + AutomatableControlButton * m_saveButton; +}; + +#endif // STUDIOCONTROLLERVIEW_H diff --git a/include/Track.h b/include/Track.h index 575ed5f3d..584bd91b9 100644 --- a/include/Track.h +++ b/include/Track.h @@ -441,6 +441,9 @@ private slots: void recordingOn(); void recordingOff(); void clearTrack(); + QMenu * grooveMenu(); + void enableGroove(); + void disableGroove(); private: static QPixmap * s_grip; diff --git a/plugins/Xpressive/exprtk.hpp.patch b/plugins/Xpressive/exprtk.hpp.patch new file mode 100644 index 000000000..21dc89db8 --- /dev/null +++ b/plugins/Xpressive/exprtk.hpp.patch @@ -0,0 +1,13 @@ +diff --git a/exprtk.hpp b/exprtk.hpp +index 916e74b..ae7de24 100644 +--- a/exprtk.hpp ++++ b/exprtk.hpp +@@ -1962,7 +1962,7 @@ namespace exprtk + template + inline bool string_to_real(const std::string& s, T& t) + { +- const typename numeric::details::number_type::type num_type; ++ typename numeric::details::number_type::type num_type; + + const char_t* begin = s.data(); + const char_t* end = s.data() + s.size(); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 49686f691..d20fa2038 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -19,6 +19,11 @@ set(LMMS_SRCS core/EnvelopeAndLfoParameters.cpp core/fft_helpers.cpp core/FxMixer.cpp + core/Groove.cpp + core/GrooveExperiments.cpp + core/GrooveFactory.cpp + core/HalfSwing.cpp + core/HydrogenSwing.cpp core/ImportFilter.cpp core/InlineAutomation.cpp core/Instrument.cpp @@ -34,6 +39,7 @@ set(LMMS_SRCS core/MemoryHelper.cpp core/MemoryManager.cpp core/MeterModel.cpp + core/MidiSwing.cpp core/MicroTimer.cpp core/Mixer.cpp core/MixerProfiler.cpp diff --git a/src/core/Groove.cpp b/src/core/Groove.cpp new file mode 100644 index 000000000..49bfc6437 --- /dev/null +++ b/src/core/Groove.cpp @@ -0,0 +1,40 @@ + +#include +#include + +#include "Groove.h" +#include "lmms_basics.h" +#include "MidiTime.h" +#include "Note.h" +#include "Pattern.h" +#include "Song.h" + +Groove::Groove() +{ +} + +/* + * Default groove is no groove. Not even a wiggle. + * @return 0 or -1 + */ +int Groove::isInTick(MidiTime * _cur_start, fpp_t _frames, f_cnt_t _offset, + Note * _n, Pattern * _p ) { + + return _n->pos().getTicks() == _cur_start->getTicks() ? 0 : -1; +} + + +void Groove::saveSettings( QDomDocument & _doc, QDomElement & _element ) +{ + +} + +void Groove::loadSettings( const QDomElement & _this ) +{ + +} + +QWidget * Groove::instantiateView( QWidget * _parent ) +{ + return new QLabel(""); +} diff --git a/src/core/GrooveExperiments.cpp b/src/core/GrooveExperiments.cpp new file mode 100644 index 000000000..7cd35df46 --- /dev/null +++ b/src/core/GrooveExperiments.cpp @@ -0,0 +1,216 @@ +/* + * GrooveExperiments.cpp - Try to find new groove algos that sound interesting + * + * Copyright (c) 2004-2014 teknopaul + * + * This file is part of LMMS - http://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 +#include + +#include "Engine.h" +#include "Groove.h" +#include "GrooveExperiments.h" +#include "Knob.h" +#include "lmms_basics.h" +#include "MidiTime.h" +#include "Note.h" +#include "Pattern.h" +#include "Song.h" + +#include "stdio.h" + +GrooveExperiments::GrooveExperiments(QObject * _parent) : + QObject( _parent ), + Groove() +{ + m_shiftAmount = 0; + m_shiftFactor = 0; + init(); + update(); +} + +GrooveExperiments::~GrooveExperiments() +{ +} + + +void GrooveExperiments::init() +{ + + Song * s = Engine::getSong(); + connect( s, SIGNAL(projectLoaded()), this, SLOT(update()) ); + connect( s, SIGNAL(lengthChanged(int)), this, SLOT(update()) ); + connect( s, SIGNAL(tempoChanged(bpm_t)), this, SLOT(update()) ); + connect( s, SIGNAL(timeSignatureChanged(int, int)), this, SLOT(update()) ); + +} + +int GrooveExperiments::amount() +{ + return m_shiftAmount; +} + +void GrooveExperiments::update() +{ + m_frames_per_tick = Engine::framesPerTick(); +} + +void GrooveExperiments::setAmount(int _amount) +{ + + if (_amount > 0 && _amount <= 127) + { + m_shiftAmount = _amount; + m_shiftFactor = (((float)m_shiftAmount) / 127.0); + emit shiftAmountChanged(m_shiftAmount); + } + else if (_amount == 0) + { + m_shiftAmount = 0; + m_shiftFactor = 0.0; + emit shiftAmountChanged(m_shiftAmount); + } + else + { + m_shiftAmount = 127; + m_shiftFactor = 1.0; + emit shiftAmountChanged(m_shiftAmount); + } + +} + + +int GrooveExperiments::isInTick(MidiTime * _cur_start, const fpp_t _frames, const f_cnt_t _offset, Note * _n, Pattern * _p ) +{ + // TODO why is this wrong on boot how do we set it once not every loop + if ( m_frames_per_tick == 0 ) + { + m_frames_per_tick = Engine::framesPerTick(); // e.g. 500 at 120BPM 4/4 + } + + // only ever delay notes by 12 ticks, so if the tick is earlier don't play + if ( _n->pos().getTicks() + 12 < _cur_start->getTicks()) + { + return -1; + } + + // else work out how much to offset the start point. + + // Where are we in the beat + // 48 ticks to the beat, 192 ticks to the bar + int pos_in_beat = _n->pos().getTicks() % 48; + + + int pos_in_eigth = -1; + if ( pos_in_beat >= 36 && pos_in_beat < 48 ) + { + // third quarter + pos_in_eigth = pos_in_beat - 36; // 0-11 + } + + if ( pos_in_eigth >= 0 ) + { + + float ticks_to_shift = ((pos_in_eigth - 12) * -m_shiftFactor); + + f_cnt_t frames_to_shift = (int)(ticks_to_shift * m_frames_per_tick); + + int tick_offset = (int)(frames_to_shift / m_frames_per_tick); // round down + + if ( _cur_start->getTicks() == (_n->pos().getTicks() + tick_offset) ) + { + // play in this tick + + f_cnt_t new_offset = (frames_to_shift % m_frames_per_tick) + _offset; + + return new_offset; + } + else + { + // this note does not play in this tick + return -1; + } + } + + // else no groove adjustments + return _n->pos().getTicks() == _cur_start->getTicks() ? 0 : -1; +} + +void GrooveExperiments::saveSettings( QDomDocument & _doc, QDomElement & _element ) +{ + _element.setAttribute("shiftAmount", m_shiftAmount); +} + +void GrooveExperiments::loadSettings( const QDomElement & _this ) +{ + bool ok; + int amount = _this.attribute("shiftAmount").toInt(&ok); + if (ok) + { + setAmount(amount); + } + else + { + setAmount(0); + } +} + +QWidget * GrooveExperiments::instantiateView( QWidget * _parent ) +{ + return new GrooveExperimentsView(this, _parent); +} + + + +// VIEW // + +GrooveExperimentsView::GrooveExperimentsView(GrooveExperiments * _ge, QWidget * _parent) : + QWidget( _parent ) +{ + m_nobModel = new FloatModel(0.0, 0.0, 127.0, 1.0); // Unused + m_nob = new Knob(knobBright_26, this); + m_nob->setModel(m_nobModel); + m_nob->setLabel(tr("Shiftiness")); + m_nob->setEnabled(true); + m_nobModel->setValue(_ge->amount()); + + m_ge = _ge; + + connect(m_nob, SIGNAL(sliderMoved(float)), this, SLOT(valueChanged(float))); + connect(m_nobModel, SIGNAL( dataChanged() ), this, SLOT(modelChanged()) ); + +} + +GrooveExperimentsView::~GrooveExperimentsView() +{ + delete m_nob; + delete m_nobModel; +} + +void GrooveExperimentsView::modelChanged() +{ + m_ge->setAmount((int)m_nobModel->value()); +} + +void GrooveExperimentsView::valueChanged(float _f) // this value passed is gibberish +{ + m_ge->setAmount((int)m_nobModel->value()); +} diff --git a/src/core/GrooveFactory.cpp b/src/core/GrooveFactory.cpp new file mode 100644 index 000000000..b8ec3e937 --- /dev/null +++ b/src/core/GrooveFactory.cpp @@ -0,0 +1,31 @@ + + +#include "GrooveFactory.h" + +#include "Groove.h" +#include "MidiSwing.h" +#include "HydrogenSwing.h" + +GrooveFactory::GrooveFactory() +{ +} + +/** + * Factory method to create grooves classes of the correct type. + * grooveType should match the string returned by the grooves nodeName() method + * + * TODO this is a bit Java-like how does C++ do this kind of thing normally + */ +Groove * GrooveFactory::create(QString _grooveType) { + if (_grooveType == NULL || _grooveType == "none") { + return new Groove(); + } + if (_grooveType == "hydrogen") { + return new HydrogenSwing(); + } + if (_grooveType == "midi") { + return new MidiSwing(); + } + return new Groove(); +} + diff --git a/src/core/HalfSwing.cpp b/src/core/HalfSwing.cpp new file mode 100644 index 000000000..00856953c --- /dev/null +++ b/src/core/HalfSwing.cpp @@ -0,0 +1,222 @@ +/* + * HalfSwing.cpp - Swing algo that varies adjustments form 0-127 + * The algorythm is just the latter half of the HydrogenSwing groove.. + * + * Copyright (c) 2004-2014 teknopaul + * + * This file is part of LMMS - http://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 +#include + +#include "Engine.h" +#include "Groove.h" +#include "HalfSwing.h" +#include "Knob.h" +#include "lmms_basics.h" +#include "MidiTime.h" +#include "Note.h" +#include "Pattern.h" +#include "Song.h" + +#include "stdio.h" + + +HalfSwing::HalfSwing(QObject * _parent) : + QObject( _parent ), + Groove() +{ + m_swingAmount = 0; + m_swingFactor = 0; + init(); + update(); +} + +HalfSwing::~HalfSwing() +{ +} + + +void HalfSwing::init() +{ + + Song * s = Engine::getSong(); + connect( s, SIGNAL(projectLoaded()), this, SLOT(update()) ); + connect( s, SIGNAL(lengthChanged(int)), this, SLOT(update()) ); + connect( s, SIGNAL(tempoChanged(bpm_t)), this, SLOT(update()) ); + connect( s, SIGNAL(timeSignatureChanged(int, int)), this, SLOT(update()) ); + +} + +int HalfSwing::amount() +{ + return m_swingAmount; +} + +void HalfSwing::update() +{ + m_frames_per_tick = Engine::framesPerTick(); +} + +void HalfSwing::setAmount(int _amount) +{ + + if (_amount > 0 && _amount <= 127) + { + m_swingAmount = _amount; + m_swingFactor = (((float)m_swingAmount) / 127.0); + emit swingAmountChanged(m_swingAmount); + } + else if (_amount == 0) + { + m_swingAmount = 0; + m_swingFactor = 0.0; + emit swingAmountChanged(m_swingAmount); + } + else + { + m_swingAmount = 127; + m_swingFactor = 1.0; + emit swingAmountChanged(m_swingAmount); + } + +} + + +int HalfSwing::isInTick(MidiTime * _cur_start, const fpp_t _frames, const f_cnt_t _offset, + Note * _n, Pattern * _p ) +{ + // TODO why is this wrong on boot how do we set it once not every loop + if ( m_frames_per_tick == 0 ) + { + m_frames_per_tick = Engine::framesPerTick(); // e.g. 500 at 120BPM 4/4 + } + + // only ever delay notes by 7 ticks, so if the tick is earlier don't play + if ( _n->pos().getTicks() + 7 < _cur_start->getTicks()) + { + return -1; + } + + // else work out how much to offset the start point. + + // Where are we in the beat + // 48 ticks to the beat, 192 ticks to the bar + int pos_in_beat = _n->pos().getTicks() % 48; + + + // The Half Swing algorthym. + // Basically we delay (shift) notes on the the 4th quarter of the beat. + + int pos_in_eigth = -1; + if ( pos_in_beat >= 36 && pos_in_beat < 42 ) + { + // 1st half of third quarter + pos_in_eigth = pos_in_beat - 36; // 0-5 + } + + if ( pos_in_eigth >= 0 ) + { + + float ticks_to_shift = ((pos_in_eigth - 6) * -m_swingFactor); + + f_cnt_t frames_to_shift = (int)(ticks_to_shift * m_frames_per_tick); + + int tick_offset = (int)(frames_to_shift / m_frames_per_tick); // round down + + if ( _cur_start->getTicks() == (_n->pos().getTicks() + tick_offset) ) + { + // play in this tick + + f_cnt_t new_offset = (frames_to_shift % m_frames_per_tick) + _offset; + + return new_offset; + } + else + { + // this note does not play in this tick + return -1; + } + } + + // else no groove adjustments + return _n->pos().getTicks() == _cur_start->getTicks() ? 0 : -1; +} + +void HalfSwing::saveSettings( QDomDocument & _doc, QDomElement & _element ) +{ + _element.setAttribute("swingAmount", m_swingAmount); +} + +void HalfSwing::loadSettings( const QDomElement & _this ) +{ + bool ok; + int amount = _this.attribute("swingAmount").toInt(&ok); + if (ok) + { + setAmount(amount); + } + else + { + setAmount(0); + } +} + +QWidget * HalfSwing::instantiateView( QWidget * _parent ) +{ + return new HalfSwingView(this, _parent); +} + + + +// VIEW // + +HalfSwingView::HalfSwingView(HalfSwing * _half_swing, QWidget * _parent) : + QWidget( _parent ) +{ + m_nobModel = new FloatModel(0.0, 0.0, 127.0, 1.0); // Unused + m_nob = new Knob(knobBright_26, this); + m_nob->setModel( m_nobModel ); + m_nob->setLabel( tr( "Swinginess" ) ); + m_nob->setEnabled(true); + m_nobModel->setValue(_half_swing->amount()); + + m_half_swing = _half_swing; + + connect(m_nob, SIGNAL(sliderMoved(float)), this, SLOT(valueChanged(float))); + connect(m_nobModel, SIGNAL( dataChanged() ), this, SLOT(modelChanged()) ); + +} + +HalfSwingView::~HalfSwingView() +{ + delete m_nob; + delete m_nobModel; +} + +void HalfSwingView::modelChanged() +{ + m_half_swing->setAmount((int)m_nobModel->value()); +} + +void HalfSwingView::valueChanged(float _f) // this value passed is gibberish +{ + m_half_swing->setAmount((int)m_nobModel->value()); +} diff --git a/src/core/HydrogenSwing.cpp b/src/core/HydrogenSwing.cpp new file mode 100644 index 000000000..4d9db0a2d --- /dev/null +++ b/src/core/HydrogenSwing.cpp @@ -0,0 +1,227 @@ +/* + * HydrogenSwing.cpp - Swing algo that varies adjustments form 0-127 + * The algorythm mimics Hydrogen drum machines swing feature. + * + * Copyright (c) 2004-2014 teknopaul + * + * This file is part of LMMS - http://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 +#include + +#include "Engine.h" +#include "Groove.h" +#include "HydrogenSwing.h" +#include "Knob.h" +#include "lmms_basics.h" +#include "MidiTime.h" +#include "Note.h" +#include "Pattern.h" +#include "Song.h" + +#include "stdio.h" + + +HydrogenSwing::HydrogenSwing(QObject * _parent) : + QObject( _parent ), + Groove() +{ + m_swingAmount = 0; + m_swingFactor = 0; + init(); + update(); +} + +HydrogenSwing::~HydrogenSwing() +{ +} + + +void HydrogenSwing::init() +{ + + Song * s = Engine::getSong(); + connect( s, SIGNAL(projectLoaded()), this, SLOT(update()) ); + connect( s, SIGNAL(lengthChanged(int)), this, SLOT(update()) ); + connect( s, SIGNAL(tempoChanged(bpm_t)), this, SLOT(update()) ); + connect( s, SIGNAL(timeSignatureChanged(int, int)), this, SLOT(update()) ); + +} + +int HydrogenSwing::amount() +{ + return m_swingAmount; +} + +void HydrogenSwing::update() +{ + m_frames_per_tick = Engine::framesPerTick(); +} + +void HydrogenSwing::setAmount(int _amount) +{ + + if (_amount > 0 && _amount <= 127) + { + m_swingAmount = _amount; + m_swingFactor = (((float)m_swingAmount) / 127.0); + emit swingAmountChanged(m_swingAmount); + } + else if (_amount == 0) + { + m_swingAmount = 0; + m_swingFactor = 0.0; + emit swingAmountChanged(m_swingAmount); + } + else + { + m_swingAmount = 127; + m_swingFactor = 1.0; + emit swingAmountChanged(m_swingAmount); + } + +} + + +int HydrogenSwing::isInTick(MidiTime * _cur_start, const fpp_t _frames, const f_cnt_t _offset, + Note * _n, Pattern * _p ) +{ + // TODO why is this wrong on boot how do we set it once not every loop + if ( m_frames_per_tick == 0 ) + { + m_frames_per_tick = Engine::framesPerTick(); // e.g. 500 at 120BPM 4/4 + } + + // only ever delay notes by 7 ticks, so if the tick is earlier don't play + if ( _n->pos().getTicks() + 7 < _cur_start->getTicks()) + { + return -1; + } + + // else work out how much to offset the start point. + + // Where are we in the beat + // 48 ticks to the beat, 192 ticks to the bar + int pos_in_beat = _n->pos().getTicks() % 48; + + + // The Hydrogen Swing algorthym. + // Guessed by turning the knob and watching the possitions change in Audacity. + // Basically we delay (shift) notes on the the 2nd and 4th quarter of the beat. + + int pos_in_eigth = -1; + if ( pos_in_beat >= 12 && pos_in_beat < 18 ) + { + // 1st half of second quarter + pos_in_eigth = pos_in_beat - 12; // 0-5 + } + else if ( pos_in_beat >= 36 && pos_in_beat < 42 ) + { + // 1st half of third quarter + pos_in_eigth = pos_in_beat - 36; // 0-5 + } + + if ( pos_in_eigth >= 0 ) + { + + float ticks_to_shift = ((pos_in_eigth - 6) * -m_swingFactor); + + f_cnt_t frames_to_shift = (int)(ticks_to_shift * m_frames_per_tick); + + int tick_offset = (int)(frames_to_shift / m_frames_per_tick); // round down + + if ( _cur_start->getTicks() == (_n->pos().getTicks() + tick_offset) ) + { + // play in this tick + + f_cnt_t new_offset = (frames_to_shift % m_frames_per_tick) + _offset; + + return new_offset; + } + else + { + // this note does not play in this tick + return -1; + } + } + + // else no groove adjustments + return _n->pos().getTicks() == _cur_start->getTicks() ? 0 : -1; +} + +void HydrogenSwing::saveSettings( QDomDocument & _doc, QDomElement & _element ) +{ + _element.setAttribute("swingAmount", m_swingAmount); +} + +void HydrogenSwing::loadSettings( const QDomElement & _this ) +{ + bool ok; + int amount = _this.attribute("swingAmount").toInt(&ok); + if (ok) + { + setAmount(amount); + } + else + { + setAmount(0); + } +} + +QWidget * HydrogenSwing::instantiateView( QWidget * _parent ) +{ + return new HydrogenSwingView(this, _parent); +} + + + +// VIEW // + +HydrogenSwingView::HydrogenSwingView(HydrogenSwing * _hy_swing, QWidget * _parent) : + QWidget( _parent ) +{ + m_nobModel = new FloatModel(0.0, 0.0, 127.0, 1.0); // Unused + m_nob = new Knob(knobBright_26, this); + m_nob->setModel( m_nobModel ); + m_nob->setLabel( tr( "Swinginess" ) ); + m_nob->setEnabled(true); + m_nobModel->setValue(_hy_swing->amount()); + + m_hy_swing = _hy_swing; + + connect(m_nob, SIGNAL(sliderMoved(float)), this, SLOT(valueChanged(float))); + connect(m_nobModel, SIGNAL( dataChanged() ), this, SLOT(modelChanged()) ); +} + +HydrogenSwingView::~HydrogenSwingView() +{ + delete m_nob; + delete m_nobModel; +} + +void HydrogenSwingView::modelChanged() +{ + m_hy_swing->setAmount((int)m_nobModel->value()); +} + +void HydrogenSwingView::valueChanged(float _f) // this value passed is gibberish +{ + m_hy_swing->setAmount((int)m_nobModel->value()); +} diff --git a/src/core/MidiSwing.cpp b/src/core/MidiSwing.cpp new file mode 100644 index 000000000..3f6bae182 --- /dev/null +++ b/src/core/MidiSwing.cpp @@ -0,0 +1,102 @@ +/* + * MidiSwing.cpp - Swing algo using fixed integer step adjustments that + * could be represented as midi. + * + * Copyright (c) 2004-2014 teknopaul + * + * This file is part of LMMS - http://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 "MidiSwing.h" + +MidiSwing::MidiSwing(QObject * _parent) : + QObject( _parent ), + Groove() +{ +} + +MidiSwing::~MidiSwing() +{ +} + +static int applyMidiSwing(int pos_in_eight); + +int MidiSwing::isInTick(MidiTime * _cur_start, const fpp_t _frames, const f_cnt_t _offset, + Note * _n, Pattern * _p ) +{ + return isInTick(_cur_start, _n, _p ); +} + +int MidiSwing::isInTick(MidiTime * _cur_start, Note * _n, Pattern * _p ) +{ + + // Where are we in the beat + int pos_in_beat = _n->pos().getTicks() % 48; // assumes 48 ticks per beat, todo verify this + + + // the Midi Swing algorthym. + + int pos_in_eigth = -1; + if ( pos_in_beat >= 12 && pos_in_beat < 18 ) + { + // 1st half of second quarter + //add a 0 - 24 tick shift + pos_in_eigth = pos_in_beat - 12; // 0-5 + } + else if ( pos_in_beat >= 36 && pos_in_beat < 42 ) + { + // 1st half of third quarter + pos_in_eigth = pos_in_beat - 36; // 0-5 + } + + int swingTicks = applyMidiSwing(pos_in_eigth); + + return _cur_start->getTicks() == swingTicks + _n->pos().getTicks() ? 0 : -1; + +} + +void MidiSwing::saveSettings( QDomDocument & _doc, QDomElement & _element ) +{ + +} + +void MidiSwing::loadSettings( const QDomElement & _this ) +{ + +} + +QWidget * MidiSwing::instantiateView( QWidget * _parent ) +{ + return new QLabel(""); +} + +static int applyMidiSwing(int _pos_in_eigth) +{ + // TODO case + if (_pos_in_eigth < 0) return 0; + if (_pos_in_eigth == 0) return 3; + if (_pos_in_eigth == 1) return 3; + if (_pos_in_eigth == 2) return 4; + if (_pos_in_eigth == 3) return 4; + if (_pos_in_eigth == 4) return 5; + if (_pos_in_eigth == 5) return 5; + return 0; +} diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 1d0e7ed20..12ff833e0 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -44,6 +44,8 @@ #include "EnvelopeAndLfoParameters.h" #include "FxMixer.h" #include "FxMixerView.h" +#include "GrooveFactory.h" +#include "GrooveView.h" #include "GuiApplication.h" #include "ExportFilter.h" #include "Pattern.h" @@ -64,6 +66,7 @@ Song::Song() : m_globalAutomationTrack( dynamic_cast( Track::create( Track::HiddenAutomationTrack, this ) ) ), + m_globalGroove(GrooveFactory::create("none")), m_tempoModel( DefaultTempo, MinTempo, MaxTempo, this, tr( "Tempo" ) ), m_timeSigModel( this ), m_oldTicksPerTact( DefaultTicksPerTact ), @@ -116,6 +119,7 @@ Song::~Song() { m_playing = false; delete m_globalAutomationTrack; + delete m_globalGroove; } @@ -614,8 +618,6 @@ void Song::setPlayPos( tick_t ticks, PlayModes playMode ) } - - void Song::togglePause() { if( m_paused == true ) @@ -1032,6 +1034,22 @@ void Song::loadProject( const QString & fileName ) } } + node = dataFile.content().firstChildElement( "groove" ); + if( !node.isNull() ) + { + QDomElement ge = dataFile.content().firstChildElement( "groove" ); + m_globalGroove = GrooveFactory::create(ge.attribute("type")); + m_globalGroove->restoreState( ge.firstChildElement(ge.attribute("type")) ); + + } + else + { + m_globalGroove = GrooveFactory::create("none"); + } + if ( gui ) { + gui->grooveView()->update(); + } + node = dataFile.content().firstChild(); QDomNodeList tclist=dataFile.content().elementsByTagName("trackcontainer"); @@ -1154,6 +1172,12 @@ bool Song::saveProjectFile( const QString & filename ) saveState( dataFile, dataFile.content() ); m_globalAutomationTrack->saveState( dataFile, dataFile.content() ); + + QDomElement ge = dataFile.createElement( "groove" ); + ge.setAttribute("type", m_globalGroove->nodeName()); + dataFile.content().appendChild( ge ); + m_globalGroove->saveState( dataFile, ge ); + Engine::fxMixer()->saveState( dataFile, dataFile.content() ); if( gui ) { @@ -1169,6 +1193,10 @@ bool Song::saveProjectFile( const QString & filename ) return dataFile.writeFile( filename ); } +void Song::setGlobalGroove(Groove * groove) +{ + m_globalGroove = groove; +} // Save the current song diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 209be3a49..3b28bf3f6 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -1894,8 +1894,33 @@ void TrackOperationsWidget::removeTrack() emit trackRemovalScheduled( m_trackView ); } +/*! \brief Turns off the groove + * + */ +void TrackOperationsWidget::disableGroove() +{ + //engine::getMixer()->lock(); + Track * t = m_trackView->getTrack(); + if (t->type() == Track::InstrumentTrack) { + InstrumentTrack * it = dynamic_cast( t ); + it->disableGroove(); + } + //engine::getMixer()->unlock(); +} - +/*! \brief Turns on the groove + * + */ +void TrackOperationsWidget::enableGroove() +{ + //engine::getMixer()->lock(); + Track * t = m_trackView->getTrack(); + if (t->type() == Track::InstrumentTrack) { + InstrumentTrack * it = dynamic_cast( t ); + it->enableGroove(); + } + //engine::getMixer()->unlock(); +} /*! \brief Update the trackOperationsWidget context menu * @@ -1927,6 +1952,7 @@ void TrackOperationsWidget::updateMenu() toMenu->addSeparator(); toMenu->addMenu( trackView->midiMenu() ); + toMenu->addMenu( grooveMenu() ); } if( dynamic_cast( m_trackView ) ) { @@ -1951,6 +1977,39 @@ void TrackOperationsWidget::recordingOn() } } +// Groove operations +QMenu * TrackOperationsWidget::grooveMenu() +{ + + // TODO, this is a memory leak presumably + QMenu * grooveMenu = new QMenu(); + grooveMenu->setIcon( embed::getIconPixmap( "note_double_whole", 16, 16 ) ); + grooveMenu->setTitle("Groove"); + + Track * t = this->m_trackView->getTrack(); + + if(t->type() == Track::InstrumentTrack) { + + // turn groove off. + QAction * muteAct = new QAction(embed::getIconPixmap( "led_red", 16, 16 ), + "Off", this); + muteAct->setData(t->id()); + grooveMenu->addAction( muteAct ); + QObject::connect(muteAct, SIGNAL( triggered( ) ), + this, SLOT( disableGroove( ) )); + + // turn groove on. + QAction * unmuteAct = new QAction(embed::getIconPixmap( "led_green", 16, 16 ), + "On", this); + unmuteAct->setData(t->id()); + grooveMenu->addAction( unmuteAct ); + QObject::connect(unmuteAct, SIGNAL( triggered( ) ), + this, SLOT( enableGroove( ) )); + + } + + return grooveMenu; +} void TrackOperationsWidget::recordingOff() { diff --git a/src/core/midi/MidiController.cpp b/src/core/midi/MidiController.cpp index 72128e184..a16fa0fc6 100644 --- a/src/core/midi/MidiController.cpp +++ b/src/core/midi/MidiController.cpp @@ -127,7 +127,13 @@ void MidiController::saveSettings( QDomDocument & _doc, QDomElement & _this ) } - +void MidiController::saveControllerSettings( QDomDocument & _doc, QDomElement & _this ) +{ + qWarning("inputChannel..."); + _this.setAttribute("channel", m_midiPort.inputChannel()); + qWarning("inputController..."); + _this.setAttribute("controller", m_midiPort.inputController()); +} void MidiController::loadSettings( const QDomElement & _this ) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 5b4050bca..259d1061f 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -44,6 +44,7 @@ SET(LMMS_SRCS gui/editors/SongEditor.cpp gui/widgets/AutomatableButton.cpp + gui/widgets/AutomatableControlButton.cpp gui/widgets/AutomatableSlider.cpp gui/widgets/CaptionMenu.cpp gui/widgets/ComboBox.cpp @@ -57,6 +58,7 @@ SET(LMMS_SRCS gui/widgets/Fader.cpp gui/widgets/FxLine.cpp gui/widgets/Graph.cpp + gui/widgets/GrooveView.cpp gui/widgets/GroupBox.cpp gui/widgets/InstrumentFunctionViews.cpp gui/widgets/InstrumentMidiIOView.cpp @@ -77,6 +79,7 @@ SET(LMMS_SRCS gui/widgets/SendButtonIndicator.cpp gui/widgets/SideBar.cpp gui/widgets/SideBarWidget.cpp + gui/widgets/StudioControllerView.cpp gui/widgets/TabBar.cpp gui/widgets/TabWidget.cpp gui/widgets/TempoSyncKnob.cpp diff --git a/src/gui/GuiApplication.cpp b/src/gui/GuiApplication.cpp index 788d38cba..e7b103272 100644 --- a/src/gui/GuiApplication.cpp +++ b/src/gui/GuiApplication.cpp @@ -34,6 +34,9 @@ #include "ConfigManager.h" #include "ControllerRackView.h" #include "FxMixerView.h" +#include "GrooveView.h" +#include "StudioControllerView.h" +#include "InstrumentTrack.h" #include "MainWindow.h" #include "PianoRoll.h" #include "ProjectNotes.h" @@ -136,6 +139,14 @@ GuiApplication::GuiApplication() m_fxMixerView = new FxMixerView; connect(m_fxMixerView, SIGNAL(destroyed(QObject*)), this, SLOT(childDestroyed(QObject*))); + displayInitProgress(tr("Preparing groove")); + m_grooveView = new GrooveView; + connect(m_grooveView, SIGNAL(destroyed(QObject*)), this, SLOT(childDestroyed(QObject*))); + + displayInitProgress(tr("Preparing studio controller")); + m_studioControllerView = new StudioControllerView; + connect(m_studioControllerView, SIGNAL(destroyed(QObject*)), this, SLOT(childDestroyed(QObject*))); + displayInitProgress(tr("Preparing controller rack")); m_controllerRackView = new ControllerRackView; connect(m_controllerRackView, SIGNAL(destroyed(QObject*)), this, SLOT(childDestroyed(QObject*))); @@ -191,6 +202,14 @@ void GuiApplication::childDestroyed(QObject *obj) { m_fxMixerView = nullptr; } + else if (obj == m_grooveView) + { + m_grooveView = nullptr; + } + else if (obj == m_studioControllerView) + { + m_studioControllerView = nullptr; + } else if (obj == m_songEditor) { m_songEditor = nullptr; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 5153b1951..f564119a4 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -49,6 +49,8 @@ #include "FileBrowser.h" #include "FileDialog.h" #include "FxMixerView.h" +#include "GrooveView.h" +#include "StudioControllerView.h" #include "GuiApplication.h" #include "ImportFilter.h" #include "PianoRoll.h" @@ -563,7 +565,7 @@ void MainWindow::finalize() ToolButton * project_notes_window = new ToolButton( embed::getIconPixmap( "project_notes" ), - tr( "Show/hide project notes" ) + + tr( "Show/hide Project Notes" ) + " (F10)", this, SLOT( toggleProjectNotesWin() ), m_toolBar ); @@ -581,6 +583,22 @@ void MainWindow::finalize() m_toolBar ); controllers_window->setShortcut( Qt::Key_F11 ); + ToolButton * studio_controller_window = new ToolButton( + embed::getIconPixmap( "note_double_whole" ), + tr ( "Show/hide Studio Controller" ) + + " (F12)", + this, SLOT( toggleStudioControllerView() ), + m_toolBar); + studio_controller_window->setShortcut( Qt::Key_F12 ); + + ToolButton * groove_view = new ToolButton( + embed::getIconPixmap( "groove" ), + tr ( "Show/hide Groove" ) + + " (Calc)", + this, SLOT( toggleGrooveView() ), + m_toolBar); + groove_view->setShortcut( Qt::Key_Calculator ); + m_toolBarLayout->addWidget( song_editor_window, 1, 1 ); m_toolBarLayout->addWidget( bb_editor_window, 1, 2 ); m_toolBarLayout->addWidget( piano_roll_window, 1, 3 ); @@ -588,6 +606,8 @@ void MainWindow::finalize() m_toolBarLayout->addWidget( fx_mixer_window, 1, 5 ); m_toolBarLayout->addWidget( project_notes_window, 1, 6 ); m_toolBarLayout->addWidget( controllers_window, 1, 7 ); + m_toolBarLayout->addWidget( studio_controller_window, 1, 8 ); + m_toolBarLayout->addWidget( groove_view, 1, 9 ); m_toolBarLayout->setColumnStretch( 100, 1 ); // setup-dialog opened before? @@ -1199,6 +1219,15 @@ void MainWindow::toggleFxMixerWin() toggleWindow( gui->fxMixerView() ); } +void MainWindow::toggleGrooveView( void ) +{ + toggleWindow( gui->grooveView() ); +} + +void MainWindow::toggleStudioControllerView( void ) +{ + toggleWindow( gui->studioControllerView() ); +} void MainWindow::updateViewMenu() { diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index 320003178..ee9022167 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -339,7 +339,19 @@ void SongEditor::keyPressEvent( QKeyEvent * ke ) } else if( ke->key() == Qt::Key_Home ) { - m_song->setPlayPos( 0, Song::Mode_PlaySong ); + gui->songEditor()->home(); + } + else if( ke->key() == Qt::Key_PageUp || ke->key() == Qt::Key_MediaPrevious ) + { + gui->songEditor()->prev(); + } + else if( ke->key() == Qt::Key_PageDown || ke->key() == Qt::Key_MediaNext ) + { + gui->songEditor()->next(); + } + else if( ke->key() == Qt::Key_End ) + { + gui->songEditor()->end(); } else if( ke->key() == Qt::Key_Delete ) { @@ -641,6 +653,36 @@ bool SongEditor::allowRubberband() const } +void SongEditor::scrollToPos( const MidiTime & t ) +{ + int widgetWidth, trackOpWidth; + if( ConfigManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) + { + widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT; + trackOpWidth = TRACK_OP_WIDTH_COMPACT; + } + else + { + widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH; + trackOpWidth = TRACK_OP_WIDTH; + } + + m_smoothScroll = ConfigManager::inst()->value( "ui", "smoothscroll" ).toInt(); + const int w = width() - widgetWidth + - trackOpWidth + - contentWidget()->verticalScrollBar()->width(); // width of right scrollbar + if( t > m_currentPosition + w * MidiTime::ticksPerTact() / + pixelsPerTact() ) + { + animateScroll( m_leftRightScroll, t.getTact(), m_smoothScroll ); + } + else if( t < m_currentPosition ) + { + animateScroll( m_leftRightScroll, t.getTact(), m_smoothScroll ); + } + m_scrollBack = false; +} + ComboBoxModel *SongEditor::zoomingModel() const @@ -767,7 +809,7 @@ void SongEditorWindow::play() void SongEditorWindow::record() { - m_editor->m_song->record(); + Engine::getSong()->record(); } @@ -775,7 +817,7 @@ void SongEditorWindow::record() void SongEditorWindow::recordAccompany() { - m_editor->m_song->playAndRecord(); + Engine::getSong()->playAndRecord(); } @@ -783,11 +825,46 @@ void SongEditorWindow::recordAccompany() void SongEditorWindow::stop() { - m_editor->m_song->stop(); + Engine::getSong()->stop(); gui->pianoRoll()->stopRecording(); } +void SongEditorWindow::home() +{ + MidiTime mTime = MidiTime(0, 0); + Engine::getSong()->setPlayPos(mTime.getTicks(), Song::Mode_PlaySong ); + m_editor->m_timeLine->updatePosition( mTime ); + m_editor->scrollToPos( mTime ); +} + + +void SongEditorWindow::next() +{ + MidiTime mTime = MidiTime(Engine::getSong()->getPlayPos(Song::Mode_PlaySong).getTact() + 1, 0); + Engine::getSong()->setPlayPos(mTime.getTicks(), Song::Mode_PlaySong ); + m_editor->m_timeLine->updatePosition( mTime ); + m_editor->scrollToPos( mTime ); +} + + +void SongEditorWindow::prev() +{ + int tact = Engine::getSong()->getPlayPos(Song::Mode_PlaySong).getTact() - 1; + MidiTime mTime = MidiTime(tact > 0 ? tact : 0, 0); + Engine::getSong()->setPlayPos(mTime.getTicks(), Song::Mode_PlaySong ); + m_editor->m_timeLine->updatePosition( mTime ); + m_editor->scrollToPos( mTime ); +} + + +void SongEditorWindow::end() +{ + MidiTime mTime = MidiTime(Engine::getSong()->length(), 0); + Engine::getSong()->setPlayPos(mTime.getTicks(), Song::Mode_PlaySong ); + m_editor->m_timeLine->updatePosition( mTime ); + m_editor->scrollToPos( mTime ); +} void SongEditorWindow::lostFocus() diff --git a/src/gui/widgets/AutomatableControlButton.cpp b/src/gui/widgets/AutomatableControlButton.cpp new file mode 100644 index 000000000..41957304f --- /dev/null +++ b/src/gui/widgets/AutomatableControlButton.cpp @@ -0,0 +1,107 @@ +/* + * AutomatableControlButton.cpp - A button with a model that accepts + * values from 0 - 127 for MIDI + * + * Copyright (c) 2004-2014 teknopaul + * + * This file is part of LMMS - http://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 "AutomatableControlButton.h" + +#include +#include +#include + +#include "CaptionMenu.h" +#include "Engine.h" +#include "embed.h" +#include "MainWindow.h" +#include "StringPairDrag.h" + + +AutomatableControlButton::AutomatableControlButton( QWidget * _parent, const QString & _name ) : + QPushButton( _parent ), + FloatModelView( new FloatModel( 0.0, 0.0, 127.0, 1.0 ), this ) +{ + setWindowTitle( _name ); + doConnections(); + setFocusPolicy( Qt::NoFocus ); +} + + +AutomatableControlButton::~AutomatableControlButton() +{ + +} + + +void AutomatableControlButton::modelChanged() +{ + +} + + +void AutomatableControlButton::update() +{ + +} + + +void AutomatableControlButton::contextMenuEvent( QContextMenuEvent * _me ) +{ + // for the case, the user clicked right while pressing left mouse- + // button, the context-menu appears while mouse-cursor is still hidden + // and it isn't shown again until user does something which causes + // an QApplication::restoreOverrideCursor()-call... + mouseReleaseEvent( NULL ); + + CaptionMenu contextMenu( model()->displayName() ); + addDefaultActions( &contextMenu ); + contextMenu.exec( QCursor::pos() ); + +} + + +void AutomatableControlButton::mousePressEvent( QMouseEvent * _me ) +{ + if( _me->button() == Qt::LeftButton && + ! ( _me->modifiers() & Qt::ControlModifier ) ) + { + // User simply clicked + _me->accept(); + } + else + { + // Otherwise, drag the standalone button + AutomatableModelView::mousePressEvent( _me ); + QPushButton::mousePressEvent( _me ); + } +} + +void AutomatableControlButton::mouseReleaseEvent( QMouseEvent * _me ) +{ + if( _me && _me->button() == Qt::LeftButton ) + { + emit clicked(); + } +} + + + diff --git a/src/gui/widgets/GrooveView.cpp b/src/gui/widgets/GrooveView.cpp new file mode 100644 index 000000000..f10ab252c --- /dev/null +++ b/src/gui/widgets/GrooveView.cpp @@ -0,0 +1,157 @@ + +#include +#include +#include +#include +#include +#include + +#include + +#include "embed.h" +#include "Engine.h" +#include "MainWindow.h" +#include "Song.h" +#include "GuiApplication.h" + +#include "Groove.h" +#include "GrooveView.h" +#include "HydrogenSwing.h" +#include "HalfSwing.h" +#include "GrooveExperiments.h" +#include "MidiSwing.h" + +//GrooveView::GrooveView(QWidget *parent) : +// QWidget(parent) +GrooveView::GrooveView( ) : + QWidget() +{ + setWindowIcon( embed::getIconPixmap( "groove" ) ); + setWindowTitle( tr( "Groove" ) ); + + m_layout = new QVBoxLayout(); + this->setLayout( m_layout ); + + m_dropDown = new QComboBox(this); + // Insert reverse order. + m_dropDown->insertItem(0, tr("Experiment swing") , QVariant::fromValue(5) ); + m_dropDown->insertItem(0, tr("Hydrogen swing") , QVariant::fromValue(4) ); + m_dropDown->insertItem(0, tr("Half swing") , QVariant::fromValue(3) ); + m_dropDown->insertItem(0, tr("MIDI swing") , QVariant::fromValue(2) ); + m_dropDown->insertItem(0, tr("No swing") , QVariant::fromValue(1) ); + m_dropDown->setCurrentIndex(0); + + m_layout->addWidget( m_dropDown ); + m_layout->addWidget( new QLabel("") ); + + QMdiSubWindow * subWin = gui->mainWindow()->addWindowedWidget( this ); + + // No maximize button. + Qt::WindowFlags flags = subWin->windowFlags(); + flags &= ~Qt::WindowMaximizeButtonHint; + subWin->setWindowFlags( flags ); + + subWin->setFixedSize( 170, 121 ); + + parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false ); + parentWidget()->move( 1080, 450 ); + + connect( m_dropDown, SIGNAL( activated(int) ), + this, SLOT( grooveChanged(int) ) ); + + connect( Engine::getSong(), SIGNAL( dataChanged() ), + this, SLOT( update() ) ); + + connect( Engine::getSong(), SIGNAL( projectLoaded() ), + this, SLOT( update() ) ); + + update(); +} + +GrooveView::~GrooveView() +{ + delete m_dropDown; +} + +void GrooveView::update() +{ + Groove * groove = Engine::getSong()->globalGroove(); + if (groove->nodeName() == "none") + { + m_dropDown->setCurrentIndex(0); + } + if (groove->nodeName() == "midi") + { + m_dropDown->setCurrentIndex(1); + } + if (groove->nodeName() == "half") + { + m_dropDown->setCurrentIndex(2); + } + if (groove->nodeName() == "hydrogen") + { + m_dropDown->setCurrentIndex(3); + } + if (groove->nodeName() == "experiment") + { + m_dropDown->setCurrentIndex(4); + } + setView(groove); +} + +void GrooveView::clear() +{ + QLayoutItem * li = m_layout->takeAt(1); + delete li->widget(); + delete li; + + m_dropDown->setCurrentIndex(0); + m_layout->addWidget(new QLabel("")); +} + +void GrooveView::grooveChanged(int index) +{ + Groove * groove = NULL; + + int selectedIdx = m_dropDown->currentIndex(); + switch (selectedIdx) { + case 0 : + { + groove = new Groove(); + break; + } + case 1 : + { + groove = new MidiSwing(); + break; + } + case 2 : + { + groove = new HalfSwing(); + break; + } + case 3 : + { + groove = new HydrogenSwing(); + break; + } + case 4 : + { + groove = new GrooveExperiments(); + break; + } + } + Song * song = Engine::getSong(); + song->setGlobalGroove(groove); // TODO: This can fail. + setView(groove); +} + +void GrooveView::setView(Groove * groove) +{ + QWidget * view = groove->instantiateView(this); + QLayoutItem * li = m_layout->takeAt(1); + delete li->widget(); + delete li; + + m_layout->addWidget(view); +} diff --git a/src/gui/widgets/StudioControllerView.cpp b/src/gui/widgets/StudioControllerView.cpp new file mode 100644 index 000000000..2c73f6f4b --- /dev/null +++ b/src/gui/widgets/StudioControllerView.cpp @@ -0,0 +1,220 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "embed.h" +#include "Engine.h" +#include "MainWindow.h" +#include "Song.h" +#include "SongEditor.h" +#include "GuiApplication.h" +#include "StudioControllerView.h" +#include "ControllerConnection.h" +#include "MidiController.h" + +StudioControllerView::StudioControllerView( ) : + QWidget() +{ + setWindowIcon( embed::getIconPixmap( "note_double_whole" ) ); + setWindowTitle( tr( "Studio Controller" ) ); + + m_layout = new QVBoxLayout(); + this->setLayout( m_layout ); + + m_controllerLabel = new QLabel( tr( "Controller:" ), this ); + m_actionsLabel = new QLabel( tr( "Actions:" ), this ); + + m_dropDown = new QComboBox(this); + m_dropDown->setInsertPolicy(QComboBox::InsertAtTop); + // Insert reverse order. + m_dropDown->insertItem(0, "nanoKontrol" , QVariant::fromValue(1)); + m_dropDown->insertItem(0, tr("No controller") , QVariant::fromValue(0)); + + m_dropDown->setCurrentIndex(0); + + m_layout->addWidget( m_controllerLabel ); + m_layout->addWidget( m_dropDown ); + m_layout->addWidget( m_actionsLabel ); + + QMdiSubWindow * subWin = gui->mainWindow()->addWindowedWidget( this ); + + // No maximize button. + Qt::WindowFlags flags = subWin->windowFlags(); + flags &= ~Qt::WindowMaximizeButtonHint; + subWin->setWindowFlags( flags ); + + subWin->setFixedSize( 170, 340 ); + + parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false ); + parentWidget()->move( 1080, 90 ); + + // LMMS MIDI configurable actions. + m_homeButton = new AutomatableControlButton(this, "home"); + m_homeButton->setText(tr("Home")); + m_layout->addWidget( m_homeButton ); + connect( m_homeButton->model(), SIGNAL( dataChanged() ), this, SLOT( doHome() ) ); + + m_playButton = new AutomatableControlButton(this, "play"); + m_playButton->setText(tr("Play")); + m_layout->addWidget( m_playButton ); + connect( m_playButton->model(), SIGNAL( dataChanged() ), this, SLOT( doPlay() ) ); + + m_stopButton = new AutomatableControlButton(this, "stop"); + m_stopButton->setText(tr("Stop")); + m_layout->addWidget( m_stopButton ); + connect( m_stopButton->model(), SIGNAL( dataChanged() ), this, SLOT( doStop() ) ); + + m_recordButton = new AutomatableControlButton(this, "record"); + m_recordButton->setText(tr("Record")); + m_layout->addWidget( m_recordButton ); + connect( m_recordButton->model(), SIGNAL( dataChanged() ), this, SLOT( doRecord() ) ); + + m_nextButton = new AutomatableControlButton(this, "next"); + m_nextButton->setText(tr("Next")); + m_layout->addWidget( m_nextButton ); + connect( m_nextButton->model(), SIGNAL( dataChanged() ), this, SLOT( doNext() ) ); + + m_prevButton = new AutomatableControlButton(this, "prev"); + m_prevButton->setText(tr("Previous")); + m_layout->addWidget( m_prevButton ); + connect( m_prevButton->model(), SIGNAL( dataChanged() ), this, SLOT( doPrev() ) ); + + m_scrollLast = 0.0f; + m_scrollButton = new AutomatableControlButton(this, "scroll"); + m_scrollButton->setText(tr("Scroll")); + m_layout->addWidget( m_scrollButton ); + connect( m_scrollButton->model(), SIGNAL( dataChanged() ), this, SLOT( doScroll() ) ); + + QPushButton* m_saveButton = new QPushButton(tr("Save"), this); + m_layout->addWidget( m_saveButton ); + connect( m_saveButton, SIGNAL( clicked() ), this, SLOT( saveControllers() ) ); + + // Controller actions. + connect( m_dropDown, SIGNAL( activated(int) ), this, SLOT( controllerChanged(int) ) ); + + update(); +} + +StudioControllerView::~StudioControllerView() +{ + delete m_dropDown; +} + + +void StudioControllerView::doHome() +{ + // Korg: NOTE ON=0.496094, OFF=126.503906 + // TODO: Confirm for other controllers + bool click = m_homeButton->model()->value() < 1.0f; + if (click) gui->songEditor()->home(); +} + + +void StudioControllerView::doPlay() +{ + bool click = m_playButton->model()->value() < 1.0f; + if (click) gui->songEditor()->play(); +} + + +void StudioControllerView::doStop() +{ + // Receive 127 for note on (pressed) and 0 for released + bool click = m_stopButton->model()->value() < 1.0f; + if (click) gui->songEditor()->stop(); +} + + +void StudioControllerView::doRecord() +{ + bool click = m_recordButton->model()->value() < 1.0f; + if (click) gui->songEditor()->record(); +} + + +void StudioControllerView::doNext() +{ + bool click = m_playButton->model()->value() < 1.0f; + if (click) gui->songEditor()->next(); +} + + +void StudioControllerView::doPrev() +{ + bool click = m_playButton->model()->value() < 1.0f; + if (click) gui->songEditor()->prev(); +} + + +void StudioControllerView::doScroll() +{ + // Korg: Value range 1-127, LMMS: 0.0-126.0. + float pos = m_scrollButton->model()->value(); + if (pos > m_scrollLast ) gui->songEditor()->next(); + if (pos < m_scrollLast ) gui->songEditor()->prev(); + m_scrollLast = pos; +} + + + + +void StudioControllerView::controllerChanged(int index) +{ + // TODO: load a file of mappings. +} + +QDomElement saveSettings(QDomDocument & doc, AutomatableControlButton* button) +{ + QDomElement elem = doc.createElement( button->text() ); + AutomatableModel* amp = dynamic_cast(button->model()); + amp->saveSettings(doc, elem, button->text() ); + return elem; +} + +void StudioControllerView::saveControllers() +{ + QDomDocument doc = QDomDocument( "lmms-studio-controller" ); + QDomElement root = doc.createElement( "lmms-studio-controller" ); + root.setAttribute( "creator", "LMMS" ); + doc.appendChild(root); + + root.appendChild(saveSettings(doc, m_homeButton)); + root.appendChild(saveSettings(doc, m_playButton)); + root.appendChild(saveSettings(doc, m_stopButton)); + root.appendChild(saveSettings(doc, m_recordButton)); + root.appendChild(saveSettings(doc, m_nextButton)); + root.appendChild(saveSettings(doc, m_prevButton)); + root.appendChild(saveSettings(doc, m_scrollButton)); + root.appendChild(saveSettings(doc, m_saveButton)); + + // FIXME: Make this platform independent. + QFile outfile( "/var/tmp/lmms-studio-controller.xml" ); + if (! outfile.open(QIODevice::WriteOnly | QIODevice::Text) ) { + QMessageBox::critical( NULL, "Could not open file", "Could not open file /var/tmp/lmms-studio-controller.xml" ); + } + else { + QTextStream ts( &outfile ); + ts << doc.toString(); + outfile.close(); + } +} + + +void StudioControllerView::loadControllers() +{ + QDomDocument doc = QDomDocument( "lmms-studio-controller" ); + QDomElement root = doc.createElement( "lmms-studio-controller" ); + + AutomatableModel* am = dynamic_cast(m_homeButton->model()); + am->setControllerConnection(NULL); +} diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 90dbf11a6..23285cd08 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -101,6 +101,8 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) : m_volumeModel( DefaultVolume, MinVolume, MaxVolume, 0.1f, this, tr( "Volume" ) ), m_panningModel( DefaultPanning, PanningLeft, PanningRight, 0.1f, this, tr( "Panning" ) ), m_audioPort( tr( "unnamed_track" ), true, &m_volumeModel, &m_panningModel, &m_mutedModel ), + m_groove( NULL ), + m_noGroove( NULL ), m_pitchModel( 0, MinPitchDefault, MaxPitchDefault, 1, this, tr( "Pitch" ) ), m_pitchRangeModel( 1, 1, 60, this, tr( "Pitch range" ) ), m_effectChannelModel( 0, 0, 0, this, tr( "FX channel" ) ), @@ -149,6 +151,10 @@ InstrumentTrack::~InstrumentTrack() // now we're save deleting the instrument if( m_instrument ) delete m_instrument; + + if (m_noGroove != NULL) { + delete m_noGroove; + } } @@ -641,6 +647,10 @@ bool InstrumentTrack::play( const MidiTime & _start, const fpp_t _frames, return false; } + // pluggable algorithm for playing notes that are + // posated within the current sample-frame + Groove * globalGroove = Engine::getSong()->globalGroove(); + bool played_a_note = false; // will be return variable for( tcoVector::Iterator it = tcos.begin(); it != tcos.end(); ++it ) @@ -662,10 +672,20 @@ bool InstrumentTrack::play( const MidiTime & _start, const fpp_t _frames, // ...and set our index to zero NoteVector::ConstIterator nit = notes.begin(); + Groove * groove = this->groove(); + if ( groove == NULL ) { + groove = globalGroove; + } + int groove_offset = 0; + // very effective algorithm for playing notes that are // posated within the current sample-frame + + // FIXME: Uncomment once groove is straight. + // Perhaps have groove supply max shift so we can skip some notes if not all +/* if( cur_start > 0 ) { // skip notes which are posated before start-tact @@ -674,34 +694,68 @@ bool InstrumentTrack::play( const MidiTime & _start, const fpp_t _frames, ++nit; } } - +*/ Note * cur_note; - while( nit != notes.end() && - ( cur_note = *nit )->pos() == cur_start ) + while( nit != notes.end() ) { - const f_cnt_t note_frames = - cur_note->length().frames( frames_per_tick ); - - NotePlayHandle* notePlayHandle = NotePlayHandleManager::acquire( this, _offset, note_frames, *cur_note ); - notePlayHandle->setBBTrack( bb_track ); - // are we playing global song? - if( _tco_num < 0 ) + cur_note = *nit; + groove_offset = groove->isInTick(&cur_start, _frames, _offset, cur_note, p); + if (groove_offset >= 0) { - // then set song-global offset of pattern in order to - // properly perform the note detuning - notePlayHandle->setSongGlobalParentOffset( p->startPosition() ); - } + const f_cnt_t note_frames = + cur_note->length().frames( frames_per_tick ); - Engine::mixer()->addPlayHandle( notePlayHandle ); - played_a_note = true; - ++nit; + NotePlayHandle* notePlayHandle = NotePlayHandleManager::acquire( this, groove_offset, note_frames, *cur_note ); + notePlayHandle->setBBTrack( bb_track ); + // are we playing global song? + if( _tco_num < 0 ) + { + // then set song-global offset of pattern in order to + // properly perform the note detuning + notePlayHandle->setSongGlobalParentOffset( p->startPosition() ); + } + + Engine::mixer()->addPlayHandle( notePlayHandle ); + played_a_note = true; + } + ++nit; } } unlock(); return played_a_note; } +Groove * InstrumentTrack::groove() +{ + // TODO: Track-specific groove (may sound weird) + if (m_grooveOn) + { + // NULL: Use global groove + return m_groove; + } + else + { + // Disable global groove + return m_noGroove; + } +} + + +void InstrumentTrack::disableGroove() +{ + if (m_noGroove == NULL) + { + m_noGroove = new Groove(); + } + m_grooveOn = false; +} + + +void InstrumentTrack::enableGroove() +{ + m_grooveOn = true; +} TrackContentObject * InstrumentTrack::createTCO( const MidiTime & )