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.
This commit is contained in:
BIN
data/themes/default/groove.png
Normal file
BIN
data/themes/default/groove.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 231 B |
62
include/AutomatableControlButton.h
Normal file
62
include/AutomatableControlButton.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* AutomatableControlButton.h - A button with a model that accepts
|
||||
* values from 0 - 127 for MIDI
|
||||
*
|
||||
* Copyright (c) 2004-2014 teknopaul <teknopaul/at/users.sourceforge.net>
|
||||
*
|
||||
* 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 <QPushButton>
|
||||
|
||||
#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
|
||||
71
include/Groove.h
Normal file
71
include/Groove.h
Normal file
@@ -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 <teknopaul/at/users.sourceforge.net>
|
||||
*
|
||||
* 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 <QWidget>
|
||||
|
||||
#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
|
||||
74
include/GrooveExperiments.h
Normal file
74
include/GrooveExperiments.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#ifndef GROOVEEXPERIMENTS_H
|
||||
#define GROOVEEXPERIMENTS_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#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
|
||||
41
include/GrooveFactory.h
Normal file
41
include/GrooveFactory.h
Normal file
@@ -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 <teknopaul/at/users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
37
include/GrooveView.h
Normal file
37
include/GrooveView.h
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
|
||||
#ifndef GROOVEVIEW_H
|
||||
#define GROOVEVIEW_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QCloseEvent>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#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
|
||||
@@ -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;
|
||||
|
||||
72
include/HalfSwing.h
Normal file
72
include/HalfSwing.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#ifndef HALFSWING_H
|
||||
#define HALFSWING_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#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
|
||||
74
include/HydrogenSwing.h
Normal file
74
include/HydrogenSwing.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#ifndef HYDROGENSWING_H
|
||||
#define HYDROGENSWING_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#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
|
||||
@@ -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;
|
||||
|
||||
@@ -166,6 +166,8 @@ public slots:
|
||||
void toggleFxMixerWin();
|
||||
void togglePianoRollWin();
|
||||
void toggleControllerRack();
|
||||
void toggleStudioControllerView();
|
||||
void toggleGrooveView();
|
||||
|
||||
void updatePlayPauseIcons();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
43
include/MidiSwing.h
Normal file
43
include/MidiSwing.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef MIDISWING_H
|
||||
#define MIDISWING_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#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
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
53
include/StudioControllerView.h
Normal file
53
include/StudioControllerView.h
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
|
||||
#ifndef STUDIOCONTROLLERVIEW_H
|
||||
#define STUDIOCONTROLLERVIEW_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QCloseEvent>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#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
|
||||
@@ -441,6 +441,9 @@ private slots:
|
||||
void recordingOn();
|
||||
void recordingOff();
|
||||
void clearTrack();
|
||||
QMenu * grooveMenu();
|
||||
void enableGroove();
|
||||
void disableGroove();
|
||||
|
||||
private:
|
||||
static QPixmap * s_grip;
|
||||
|
||||
13
plugins/Xpressive/exprtk.hpp.patch
Normal file
13
plugins/Xpressive/exprtk.hpp.patch
Normal file
@@ -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 <typename T>
|
||||
inline bool string_to_real(const std::string& s, T& t)
|
||||
{
|
||||
- const typename numeric::details::number_type<T>::type num_type;
|
||||
+ typename numeric::details::number_type<T>::type num_type;
|
||||
|
||||
const char_t* begin = s.data();
|
||||
const char_t* end = s.data() + s.size();
|
||||
@@ -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
|
||||
|
||||
40
src/core/Groove.cpp
Normal file
40
src/core/Groove.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
#include <QObject>
|
||||
#include <QLabel>
|
||||
|
||||
#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("");
|
||||
}
|
||||
216
src/core/GrooveExperiments.cpp
Normal file
216
src/core/GrooveExperiments.cpp
Normal file
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* GrooveExperiments.cpp - Try to find new groove algos that sound interesting
|
||||
*
|
||||
* Copyright (c) 2004-2014 teknopaul <teknopaul/at/users.sourceforge.net>
|
||||
*
|
||||
* 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 <QObject>
|
||||
#include <QDomElement>
|
||||
#include <QLabel>
|
||||
|
||||
#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());
|
||||
}
|
||||
31
src/core/GrooveFactory.cpp
Normal file
31
src/core/GrooveFactory.cpp
Normal file
@@ -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();
|
||||
}
|
||||
|
||||
222
src/core/HalfSwing.cpp
Normal file
222
src/core/HalfSwing.cpp
Normal file
@@ -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 <teknopaul/at/users.sourceforge.net>
|
||||
*
|
||||
* 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 <QObject>
|
||||
#include <QDomElement>
|
||||
#include <QLabel>
|
||||
|
||||
#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());
|
||||
}
|
||||
227
src/core/HydrogenSwing.cpp
Normal file
227
src/core/HydrogenSwing.cpp
Normal file
@@ -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 <teknopaul/at/users.sourceforge.net>
|
||||
*
|
||||
* 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 <QObject>
|
||||
#include <QDomElement>
|
||||
#include <QLabel>
|
||||
|
||||
#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());
|
||||
}
|
||||
102
src/core/MidiSwing.cpp
Normal file
102
src/core/MidiSwing.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* MidiSwing.cpp - Swing algo using fixed integer step adjustments that
|
||||
* could be represented as midi.
|
||||
*
|
||||
* Copyright (c) 2004-2014 teknopaul <teknopaul/at/users.sourceforge.net>
|
||||
*
|
||||
* 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 <QLabel>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -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<AutomationTrack *>(
|
||||
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
|
||||
|
||||
@@ -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<InstrumentTrack *>( 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<InstrumentTrack *>( 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<AutomationTrackView *>( 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()
|
||||
{
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
107
src/gui/widgets/AutomatableControlButton.cpp
Normal file
107
src/gui/widgets/AutomatableControlButton.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* AutomatableControlButton.cpp - A button with a model that accepts
|
||||
* values from 0 - 127 for MIDI
|
||||
*
|
||||
* Copyright (c) 2004-2014 teknopaul <teknopaul/at/users.sourceforge.net>
|
||||
*
|
||||
* 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 <QCursor>
|
||||
#include <QMouseEvent>
|
||||
#include <QPushButton>
|
||||
|
||||
#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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
157
src/gui/widgets/GrooveView.cpp
Normal file
157
src/gui/widgets/GrooveView.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
|
||||
#include <QApplication>
|
||||
#include <QLayout>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
#include <QMdiArea>
|
||||
|
||||
#include <QDomElement>
|
||||
|
||||
#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);
|
||||
}
|
||||
220
src/gui/widgets/StudioControllerView.cpp
Normal file
220
src/gui/widgets/StudioControllerView.cpp
Normal file
@@ -0,0 +1,220 @@
|
||||
|
||||
#include <QApplication>
|
||||
#include <QLayout>
|
||||
#include <QPushButton>
|
||||
#include <QComboBox>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
#include <QMdiArea>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <QDomElement>
|
||||
|
||||
#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<AutomatableModel*>(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<AutomatableModel*>(m_homeButton->model());
|
||||
am->setControllerConnection(NULL);
|
||||
}
|
||||
@@ -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 & )
|
||||
|
||||
Reference in New Issue
Block a user