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:
Tres Finocchiaro
2018-03-09 11:47:18 -05:00
committed by GitHub
parent d2c370a953
commit 957ec6b611
36 changed files with 2212 additions and 31 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

View 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
View 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

View 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
View 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
View 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

View File

@@ -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
View 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
View 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

View File

@@ -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;

View File

@@ -166,6 +166,8 @@ public slots:
void toggleFxMixerWin();
void togglePianoRollWin();
void toggleControllerRack();
void toggleStudioControllerView();
void toggleGrooveView();
void updatePlayPauseIcons();

View File

@@ -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
View 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

View File

@@ -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;

View File

@@ -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();

View 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

View File

@@ -441,6 +441,9 @@ private slots:
void recordingOn();
void recordingOff();
void clearTrack();
QMenu * grooveMenu();
void enableGroove();
void disableGroove();
private:
static QPixmap * s_grip;

View 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();

View File

@@ -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
View 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("");
}

View 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());
}

View 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
View 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
View 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
View 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;
}

View File

@@ -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

View File

@@ -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()
{

View File

@@ -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 )

View File

@@ -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

View File

@@ -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;

View File

@@ -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()
{

View File

@@ -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()

View 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();
}
}

View 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);
}

View 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);
}

View File

@@ -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 & )