Compare commits

...

26 Commits

Author SHA1 Message Date
Hyunjin Song
013aef5513 Remove connections to a nonexistent signal 2024-04-16 10:04:53 +09:00
Hyunjin Song
cbcde94d61 Merge branch 'master' into groove 2024-04-16 09:55:07 +09:00
Hyunjin Song
dfc7eabcb9 Fix namespace comments 2023-01-07 10:59:39 +09:00
Hyunjin Song
9b3fe5e2ad Reformat and modernize a bit 2023-01-06 22:03:17 +09:00
Hyunjin Song
94221651d4 Merge branch 'master' into groove 2023-01-06 21:41:51 +09:00
Hyunjin Song
193c30f888 Merge branch 'master' into groove 2022-06-18 15:47:07 +09:00
Hyunjin Song
1dbf6aabf7 Fix includes 2022-03-03 10:54:55 +09:00
Hyunjin Song
fe9c8b6bf6 Merge branch 'master' into groove 2022-03-03 10:29:20 +09:00
Hyunjin Song
b2057aee0f Merge branch 'master' into groove 2022-02-05 14:54:42 +09:00
Hyunjin Song
170c8b9a34 Minor formatting changes 2019-11-19 15:18:32 +09:00
Hyunjin Song
14f894c3ee Remove exprtk.hpp.patch 2019-11-19 15:16:00 +09:00
Hyunjin Song
6122d0f3f2 Merge branch 'master' into groove 2019-11-19 15:10:01 +09:00
Hyunjin Song
df329e6f5e Merge branch 'master' into groove 2018-06-15 10:55:51 +09:00
Hussam Eddin Alhomsi
1b9f28254a Merge remote-tracking branch 'lmms/master' into groove 2018-05-22 18:07:59 +03:00
Hyunin Song
7117d90c00 Merge branch 'master' into groove 2018-04-30 14:42:38 +09:00
Hussam Eddin Alhomsi
3760e4642f Move enable/disable groove per instrument track...
...from the TrackOperationsWidget to the InstrumentMiscView.
2018-04-28 18:28:18 +03:00
Hussam Eddin Alhomsi
9e23b2a03b Remove unused code
And some improvements
2018-03-31 13:23:01 +03:00
Hussam Eddin Alhomsi
8fa33a8970 Reorder the swings
Put Half after Hydrogen because Half is based on Hydrogen.
2018-03-28 20:51:59 +03:00
Hyunin Song
81e0fe23b0 Do more reformattings 2018-03-28 10:14:28 +09:00
Hyunin Song
217c31af8c Reformat code 2018-03-28 09:44:55 +09:00
Hyunin Song
ad9ea6f362 Reduce code duplications 2018-03-15 07:36:02 +09:00
Hyunin Song
d6529ae1e8 Update old homepage URL and program name 2018-03-15 00:01:58 +09:00
Hussam Eddin Alhomsi
46a2f78733 Use AutomatableSlider & IntModel...
...instead of Knob & FloatModel
2018-03-14 16:36:51 +03:00
Hussam Eddin Alhomsi
3866cef8b3 Move Groove to global toolbar 2018-03-14 15:35:48 +03:00
Hussam Eddin Alhomsi
46fa1803d3 Remove Studio Controller
Per https://github.com/LMMS/lmms/pull/3296#issuecomment-366713198
2018-03-11 15:50:35 +03:00
Tres Finocchiaro
957ec6b611 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.
2018-03-09 11:47:18 -05:00
26 changed files with 1560 additions and 26 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

88
include/Groove.h Normal file
View File

@@ -0,0 +1,88 @@
/*
* 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 LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef GROOVE_H
#define GROOVE_H
#include <QWidget>
#include "lmms_basics.h"
#include "TimePos.h"
#include "Note.h"
#include "MidiClip.h"
#include "SerializingObject.h"
namespace lmms
{
class Groove : public QObject, public SerializingObject
{
Q_OBJECT
public:
Groove(QObject* parent = nullptr);
/*
* 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(TimePos* curStart, fpp_t frames, f_cnt_t offset,
Note* n, MidiClip* c);
int amount() const { return m_amount; }
virtual void saveSettings(QDomDocument & doc, QDomElement & element);
virtual void loadSettings(const QDomElement & element);
virtual QWidget* instantiateView(QWidget* parent);
QString nodeName() const override
{
return "none";
}
signals:
void amountChanged(int newAmount);
public slots:
void setAmount(int amount);
protected:
int m_amount;
float m_swingFactor; // = (m_amount / 127)
};
} // namespace lmms
#endif // GROOVE_H

View File

@@ -0,0 +1,93 @@
/*
* GrooveExperiments.h - A groove that's new
*
* Copyright (c) 2005-2008 teknopaul <teknopaul/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
**/
#ifndef GROOVEEXPERIMENTS_H
#define GROOVEEXPERIMENTS_H
#include <QObject>
#include "AutomatableSlider.h"
#include "Groove.h"
#include "lmms_basics.h"
#include "TimePos.h"
#include "Note.h"
#include "MidiClip.h"
namespace lmms
{
/**
* A groove that's new
*/
class GrooveExperiments : public Groove
{
Q_OBJECT
public:
GrooveExperiments(QObject* parent = nullptr);
~GrooveExperiments() override;
void init();
int isInTick(TimePos* curStart, const fpp_t frames, const f_cnt_t offset, Note* n, MidiClip* c);
inline virtual QString nodeName() const
{
return "experiment";
}
QWidget* instantiateView(QWidget* parent);
public slots:
// valid values are from 0 - 127
void update();
private:
int m_framesPerTick;
};
namespace gui
{
class GrooveExperimentsView : public QWidget
{
Q_OBJECT
public:
GrooveExperimentsView(GrooveExperiments* groove, QWidget* parent = nullptr);
~GrooveExperimentsView() override;
public slots:
void valueChanged();
private:
GrooveExperiments* m_groove;
IntModel* m_sliderModel;
AutomatableSlider* m_slider;
};
} // namespace gui
} // namespace lmms
#endif // GROOVEEXPERIMENTS_H

43
include/GrooveFactory.h Normal file
View File

@@ -0,0 +1,43 @@
/*
* GrooveFactory.h - a factory class for grooves
*
* Copyright (c) 2005-2008 teknopaul <teknopaul/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
**/
#ifndef GROOVEFACTORY_H
#define GROOVEFACTORY_H
#include "Groove.h"
namespace lmms
{
class GrooveFactory
{
public:
static Groove* create(const QString& grooveType);
private:
GrooveFactory();
};
} // namespace lmms
#endif // GROOVEFACTORY_H

61
include/GrooveView.h Normal file
View File

@@ -0,0 +1,61 @@
/*
* GrooveView.h - a view class for grooves
*
* Copyright (c) 2005-2008 teknopaul <teknopaul/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
**/
#ifndef GROOVEVIEW_H
#define GROOVEVIEW_H
#include <QWidget>
#include <QVBoxLayout>
#include <QComboBox>
#include "Groove.h"
#include "SerializingObject.h"
namespace lmms::gui
{
class GrooveView : public QWidget
{
Q_OBJECT
public:
GrooveView(QWidget* parent);
~GrooveView() override;
void clear();
signals:
public slots:
void update();
void grooveChanged();
private:
void setView(Groove* groove);
QVBoxLayout* m_layout;
QComboBox* m_comboBox;
};
} // namespace lmms::gui
#endif // GROOVEVIEW_H

View File

@@ -37,6 +37,7 @@ namespace lmms::gui
class AutomationEditorWindow;
class ControllerRackView;
class GrooveView;
class MixerView;
class MainWindow;
class MicrotunerConfig;
@@ -58,6 +59,7 @@ public:
#endif
MainWindow* mainWindow() { return m_mainWindow; }
GrooveView* grooveView() { return m_grooveView; }
MixerView* mixerView() { return m_mixerView; }
SongEditorWindow* songEditor() { return m_songEditor; }
PatternEditorWindow* patternEditor() { return m_patternEditor; }
@@ -77,6 +79,7 @@ private:
static GuiApplication* s_instance;
MainWindow* m_mainWindow;
GrooveView* m_grooveView;
MixerView* m_mixerView;
SongEditorWindow* m_songEditor;
AutomationEditorWindow* m_automationEditor;

92
include/HalfSwing.h Normal file
View File

@@ -0,0 +1,92 @@
/*
* HalfSwing.h - A groove that uses just the latter half of the Hydrogen Swing algorithm
*
* Copyright (c) 2005-2008 teknopaul <teknopaul/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
**/
#ifndef HALFSWING_H
#define HALFSWING_H
#include <QObject>
#include "AutomatableSlider.h"
#include "Groove.h"
#include "lmms_basics.h"
#include "TimePos.h"
#include "Note.h"
#include "MidiClip.h"
namespace lmms
{
/**
* A groove that uses just the latter half of the Hydrogen Swing algorithm
*/
class HalfSwing : public Groove
{
Q_OBJECT
public:
HalfSwing(QObject* parent = nullptr);
~HalfSwing() override;
void init();
int isInTick(TimePos* curStart, const fpp_t frames, const f_cnt_t offset, Note* n, MidiClip* c);
QString nodeName() const override
{
return "half";
}
QWidget* instantiateView(QWidget* parent);
public slots:
// valid values are from 0 - 127
void update();
private:
int m_framesPerTick;
} ;
namespace gui
{
class HalfSwingView : public QWidget
{
Q_OBJECT
public:
HalfSwingView(HalfSwing* halfSwing, QWidget* parent = nullptr);
~HalfSwingView() override;
public slots:
void valueChanged();
private:
HalfSwing* m_swing;
IntModel* m_sliderModel;
AutomatableSlider* m_slider;
};
} // namespace gui
} // namespace lmms
#endif // HALFSWING_H

95
include/HydrogenSwing.h Normal file
View File

@@ -0,0 +1,95 @@
/*
* HydrogenSwing.h - A groove that mimics Hydrogen drum machine's swing feature
*
* Copyright (c) 2005-2008 teknopaul <teknopaul/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
**/
#ifndef HYDROGENSWING_H
#define HYDROGENSWING_H
#include <QObject>
#include "AutomatableSlider.h"
#include "Groove.h"
#include "lmms_basics.h"
#include "TimePos.h"
#include "Note.h"
#include "MidiClip.h"
namespace lmms
{
/**
* A groove that mimics Hydrogen drum machine's swing feature
*/
class HydrogenSwing : public Groove
{
Q_OBJECT
public:
HydrogenSwing(QObject *parent = nullptr);
~HydrogenSwing() override;
void init();
int isInTick(TimePos* curStart, const fpp_t frames, const f_cnt_t offset, Note* n, MidiClip* c);
QString nodeName() const override
{
return "hydrogen";
}
QWidget* instantiateView(QWidget* parent);
public slots:
// valid values are from 0 - 127
void update();
private:
int m_framesPerTick;
} ;
namespace gui
{
class HydrogenSwingView : public QWidget
{
Q_OBJECT
public:
HydrogenSwingView(HydrogenSwing* swing, QWidget* parent = NULL);
~HydrogenSwingView();
public slots:
void valueChanged();
private:
HydrogenSwing* m_swing;
IntModel* m_sliderModel;
AutomatableSlider* m_slider;
} ;
} // namespace gui
} // namespace lmms
#endif // HYDROGENSWING_H

View File

@@ -69,8 +69,7 @@ private:
QToolButton * m_wpBtn;
LcdSpinBox* m_baseVelocitySpinBox;
} ;
};
} // namespace gui

View File

@@ -27,6 +27,7 @@
#define LMMS_INSTRUMENT_TRACK_H
#include "AudioPort.h"
#include "Groove.h"
#include "InstrumentFunctions.h"
#include "InstrumentSoundShaping.h"
#include "Microtuner.h"
@@ -83,6 +84,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
@@ -148,6 +151,8 @@ public:
return &m_audioPort;
}
Groove * groove();
MidiPort * midiPort()
{
return &m_midiPort;
@@ -294,10 +299,16 @@ private:
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;
IntModel m_mixerChannelModel;
BoolModel m_useMasterPitchModel;
BoolModel m_useGrooveModel;
Instrument * m_instrument;
InstrumentSoundShaping m_soundShaping;
@@ -317,7 +328,9 @@ private:
friend class gui::InstrumentTuningView;
friend class gui::MidiCCRackView;
} ;
private slots:
void updateGroove();
};

View File

@@ -60,6 +60,8 @@ public:
LedCheckBox *rangeImportCheckbox() {return m_rangeImportCheckbox;}
GroupBox *grooveGroupBox() {return m_grooveGroupBox;}
private:
GroupBox *m_pitchGroupBox;
GroupBox *m_microtunerGroupBox;
@@ -70,6 +72,8 @@ private:
ComboBox *m_keymapCombo;
LedCheckBox *m_rangeImportCheckbox;
GroupBox * m_grooveGroupBox;
};

63
include/MidiSwing.h Normal file
View File

@@ -0,0 +1,63 @@
/*
* MidiSwing.h - A swing groove that adjusts by whole ticks
*
* Copyright (c) 2005-2008 teknopaul <teknopaul/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
**/
#ifndef MIDISWING_H
#define MIDISWING_H
#include <QObject>
#include "Groove.h"
#include "lmms_basics.h"
#include "TimePos.h"
#include "Note.h"
#include "MidiClip.h"
namespace lmms
{
/*
* 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 Groove
{
Q_OBJECT
public:
MidiSwing(QObject* parent = nullptr);
~MidiSwing() override;
// TODO why declaring this should it not come from super class?
int isInTick(TimePos* cur_start, const fpp_t frames, const f_cnt_t offset, Note* n, MidiClip* c);
int isInTick(TimePos* curStart, Note* n, MidiClip* c);
QString nodeName() const override
{
return "midi";
}
QWidget* instantiateView(QWidget* parent);
};
} // namespace lmms
#endif // MIDISWING_H

View File

@@ -38,11 +38,13 @@
#include "Timeline.h"
#include "TrackContainer.h"
#include "VstSyncController.h"
#include "Groove.h"
namespace lmms
{
class AutomationTrack;
class Groove;
class Keymap;
class MidiClip;
class Scale;
@@ -292,6 +294,11 @@ public:
return m_globalAutomationTrack;
}
Groove* globalGroove()
{
return m_globalGroove;
}
//TODO: Add Q_DECL_OVERRIDE when Qt4 is dropped
AutomatedValueMap automatedValuesAt(TimePos time, int clipNum = -1) const override;
@@ -302,6 +309,7 @@ public:
bool guiSaveProject();
bool guiSaveProjectAs(const QString & filename);
bool saveProjectFile(const QString & filename, bool withResources = false);
void setGlobalGroove(Groove* groove);
const QString & projectFileName() const
{
@@ -383,6 +391,7 @@ public slots:
void playMidiClip( const lmms::MidiClip * midiClipToPlay, bool loop = true );
void togglePause();
void stop();
void setPlayPos( tick_t ticks, PlayMode playMode );
void startExport();
void stopExport();
@@ -434,8 +443,6 @@ private:
getPlayPos(m_playMode).currentFrame();
}
void setPlayPos( tick_t ticks, PlayMode playMode );
void saveControllerStates( QDomDocument & doc, QDomElement & element );
void restoreControllerStates( const QDomElement & element );
@@ -454,6 +461,7 @@ private:
void setProjectFileName(QString const & projectFileName);
AutomationTrack * m_globalAutomationTrack;
Groove* m_globalGroove;
IntModel m_tempoModel;
MeterModel m_timeSigModel;

View File

@@ -24,6 +24,11 @@ set(LMMS_SRCS
core/EnvelopeAndLfoParameters.cpp
core/fft_helpers.cpp
core/Mixer.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
@@ -40,6 +45,7 @@ set(LMMS_SRCS
core/LocklessAllocator.cpp
core/MemoryHelper.cpp
core/MeterModel.cpp
core/MidiSwing.cpp
core/MicroTimer.cpp
core/Microtuner.cpp
core/MixHelpers.cpp

70
src/core/Groove.cpp Normal file
View File

@@ -0,0 +1,70 @@
#include <QDomElement>
#include <QObject>
#include <QLabel>
#include "Groove.h"
#include "lmms_basics.h"
#include "TimePos.h"
#include "Note.h"
#include "MidiClip.h"
#include "Song.h"
namespace lmms
{
Groove::Groove(QObject* parent) :
QObject(parent)
{
}
/*
* Default groove is no groove. Not even a wiggle.
* @return 0 or -1
*/
int Groove::isInTick(TimePos* curStart, fpp_t frames, f_cnt_t offset,
Note* n, MidiClip* c)
{
return n->pos().getTicks() == curStart->getTicks() ? 0 : -1;
}
void Groove::setAmount(int amount)
{
if (amount < 0) {
amount = 0;
}
if (amount > 127) {
amount = 127;
}
m_amount = amount;
m_swingFactor = ((float)m_amount) / 127.0f;
emit amountChanged(m_amount);
}
void Groove::saveSettings(QDomDocument & doc, QDomElement & element)
{
Q_UNUSED(doc);
element.setAttribute("amount", m_amount);
}
void Groove::loadSettings(const QDomElement & element)
{
bool ok;
int amount = element.attribute("amount").toInt(&ok);
if (ok)
{
setAmount(amount);
}
else
{
setAmount(0);
}
}
QWidget* Groove::instantiateView(QWidget* parent)
{
return new QLabel("");
}
} // namespace lmms

View File

@@ -0,0 +1,167 @@
/*
* 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 - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include <QObject>
#include <QLabel>
#include "Engine.h"
#include "Groove.h"
#include "GrooveExperiments.h"
#include "lmms_basics.h"
#include "TimePos.h"
#include "Note.h"
#include "MidiClip.h"
#include "Song.h"
#include "stdio.h"
namespace lmms
{
GrooveExperiments::GrooveExperiments(QObject* parent) :
Groove(parent)
{
m_amount = 0;
m_swingFactor = 0.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(lmms::bpm_t)), this, SLOT(update()));
connect(s, SIGNAL(timeSignatureChanged(int, int)), this, SLOT(update()));
}
void GrooveExperiments::update()
{
m_framesPerTick = Engine::framesPerTick();
}
int GrooveExperiments::isInTick(TimePos* curStart, const fpp_t frames, const f_cnt_t offset, Note* n, MidiClip* c)
{
// TODO why is this wrong on boot how do we set it once not every loop
if (m_framesPerTick == 0)
{
m_framesPerTick = 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 < curStart->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 posInBeat = n->pos().getTicks() % 48;
int posInEigth = -1;
if (posInBeat >= 36 && posInBeat < 48)
{
// third quarter
posInEigth = posInBeat - 36; // 0-11
}
if (posInEigth >= 0)
{
float ticksToShift = ((posInEigth - 12) * -m_swingFactor);
f_cnt_t framesToShift = (int)(ticksToShift* m_framesPerTick);
int tickOffset = (int)(framesToShift / m_framesPerTick); // round down
if (curStart->getTicks() == (n->pos().getTicks() + tickOffset))
{
// play in this tick
f_cnt_t newOffset = (framesToShift % m_framesPerTick) + offset;
return newOffset;
}
else
{
// this note does not play in this tick
return -1;
}
}
// else no groove adjustments
return n->pos().getTicks() == curStart->getTicks() ? 0 : -1;
}
QWidget* GrooveExperiments::instantiateView(QWidget* parent)
{
return new gui::GrooveExperimentsView(this, parent);
}
namespace gui
{
// VIEW //
GrooveExperimentsView::GrooveExperimentsView(GrooveExperiments* groove, QWidget* parent) :
QWidget(parent)
{
m_sliderModel = new IntModel(0, 0, 127); // Unused
m_sliderModel->setValue(groove->amount());
m_slider = new AutomatableSlider(this, tr("Swinginess"));
m_slider->setOrientation(Qt::Horizontal);
m_slider->setFixedSize(90, 26);
m_slider->setPageStep(1);
m_slider->setModel(m_sliderModel);
m_groove = groove;
connect(m_sliderModel, SIGNAL(dataChanged()), this, SLOT(valueChanged()));
}
GrooveExperimentsView::~GrooveExperimentsView()
{
delete m_slider;
delete m_sliderModel;
}
void GrooveExperimentsView::valueChanged()
{
m_groove->setAmount(m_sliderModel->value());
}
} // namespace gui
} // namespace lmms

View File

@@ -0,0 +1,43 @@
#include "GrooveFactory.h"
#include "Groove.h"
#include "GrooveExperiments.h"
#include "MidiSwing.h"
#include "HalfSwing.h"
#include "HydrogenSwing.h"
namespace lmms
{
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(const QString& grooveType) {
if (grooveType.isEmpty() || grooveType == "none") {
return new Groove();
}
if (grooveType == "hydrogen") {
return new HydrogenSwing();
}
if (grooveType == "midi") {
return new MidiSwing();
}
if (grooveType == "half") {
return new HalfSwing();
}
if (grooveType == "experiment") {
return new GrooveExperiments();
}
return new Groove();
}
} // namespace lmms

175
src/core/HalfSwing.cpp Normal file
View File

@@ -0,0 +1,175 @@
/*
* 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 - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include <QObject>
#include <QLabel>
#include "Engine.h"
#include "Groove.h"
#include "HalfSwing.h"
#include "lmms_basics.h"
#include "TimePos.h"
#include "Note.h"
#include "MidiClip.h"
#include "Song.h"
#include "stdio.h"
namespace lmms
{
HalfSwing::HalfSwing(QObject* parent) :
Groove(parent)
{
m_amount = 0;
m_swingFactor = 0.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(lmms::bpm_t)), this, SLOT(update()));
connect(s, SIGNAL(timeSignatureChanged(int, int)), this, SLOT(update()));
}
void HalfSwing::update()
{
m_framesPerTick = Engine::framesPerTick();
}
int HalfSwing::isInTick(TimePos* curStart, const fpp_t frames, const f_cnt_t offset,
Note* n, MidiClip* c)
{
// TODO why is this wrong on boot how do we set it once not every loop
if (m_framesPerTick == 0)
{
m_framesPerTick = 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 < curStart->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 algorithm.
// Basically we delay (shift) notes on the the 4th quarter of the beat.
int posInEigth = -1;
if (pos_in_beat >= 36 && pos_in_beat < 42)
{
// 1st half of third quarter
posInEigth = pos_in_beat - 36; // 0-5
}
if (posInEigth >= 0)
{
float ticksToShift = ((posInEigth - 6) * -m_swingFactor);
f_cnt_t framesToShift = (int)(ticksToShift* m_framesPerTick);
int tickOffset = (int)(framesToShift / m_framesPerTick); // round down
if (curStart->getTicks() == (n->pos().getTicks() + tickOffset))
{
// play in this tick
f_cnt_t newOffset = (framesToShift % m_framesPerTick) + offset;
return newOffset;
}
else
{
// this note does not play in this tick
return -1;
}
}
// else no groove adjustments
return n->pos().getTicks() == curStart->getTicks() ? 0 : -1;
}
QWidget* HalfSwing::instantiateView(QWidget* parent)
{
return new gui::HalfSwingView(this, parent);
}
namespace gui
{
// VIEW //
HalfSwingView::HalfSwingView(HalfSwing* halfSwing, QWidget* parent) :
QWidget(parent)
{
m_sliderModel = new IntModel(0, 0, 127); // Unused
m_sliderModel->setValue(halfSwing->amount());
m_slider = new AutomatableSlider(this, tr("Swinginess"));
m_slider->setOrientation(Qt::Horizontal);
m_slider->setFixedSize(90, 26);
m_slider->setPageStep(1);
m_slider->setModel(m_sliderModel);
m_swing = halfSwing;
connect(m_sliderModel, SIGNAL(dataChanged()), this, SLOT(valueChanged()));
}
HalfSwingView::~HalfSwingView()
{
delete m_slider;
delete m_sliderModel;
}
void HalfSwingView::valueChanged()
{
m_swing->setAmount(m_sliderModel->value());
}
} // namespace gui
} // namespace lmms

178
src/core/HydrogenSwing.cpp Normal file
View File

@@ -0,0 +1,178 @@
/*
* 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 - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include <QObject>
#include <QLabel>
#include "Engine.h"
#include "Groove.h"
#include "HydrogenSwing.h"
#include "lmms_basics.h"
#include "TimePos.h"
#include "Note.h"
#include "MidiClip.h"
#include "Song.h"
#include "stdio.h"
namespace lmms
{
HydrogenSwing::HydrogenSwing(QObject* _parent) :
Groove(_parent)
{
m_amount = 0;
m_swingFactor = 0.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(lmms::bpm_t)), this, SLOT(update()));
connect(s, SIGNAL(timeSignatureChanged(int, int)), this, SLOT(update()));
}
void HydrogenSwing::update()
{
m_framesPerTick = Engine::framesPerTick();
}
int HydrogenSwing::isInTick(TimePos* curStart, const fpp_t frames, const f_cnt_t offset,
Note* n, MidiClip* c)
{
// TODO why is this wrong on boot how do we set it once not every loop
if (m_framesPerTick == 0)
{
m_framesPerTick = 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 < curStart->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 posInBeat = n->pos().getTicks() % 48;
// The Hydrogen Swing algorithm.
// 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 posInEigth = -1;
if (posInBeat >= 12 && posInBeat < 18)
{
// 1st half of second quarter
posInEigth = posInBeat - 12; // 0-5
}
else if (posInBeat >= 36 && posInBeat < 42)
{
// 1st half of third quarter
posInEigth = posInBeat - 36; // 0-5
}
if (posInEigth >= 0)
{
float ticksToShift = ((posInEigth - 6) * -m_swingFactor);
f_cnt_t framesToShift = (int)(ticksToShift* m_framesPerTick);
int tickOffset = (int)(framesToShift / m_framesPerTick); // round down
if (curStart->getTicks() == (n->pos().getTicks() + tickOffset))
{
// play in this tick
f_cnt_t newOffset = (framesToShift % m_framesPerTick) + offset;
return newOffset;
}
else
{
// this note does not play in this tick
return -1;
}
}
// else no groove adjustments
return n->pos().getTicks() == curStart->getTicks() ? 0 : -1;
}
QWidget* HydrogenSwing::instantiateView(QWidget* parent)
{
return new gui::HydrogenSwingView(this, parent);
}
namespace gui
{
// VIEW //
HydrogenSwingView::HydrogenSwingView(HydrogenSwing* swing, QWidget* parent) :
QWidget(parent)
{
m_sliderModel = new IntModel(0, 0, 127); // Unused
m_sliderModel->setValue(swing->amount());
m_slider = new AutomatableSlider(this, tr("Swinginess"));
m_slider->setOrientation(Qt::Horizontal);
m_slider->setFixedSize(90, 26);
m_slider->setPageStep(1);
m_slider->setModel(m_sliderModel);
m_swing = swing;
connect(m_sliderModel, SIGNAL(dataChanged()), this, SLOT(valueChanged()));
}
HydrogenSwingView::~HydrogenSwingView()
{
delete m_slider;
delete m_sliderModel;
}
void HydrogenSwingView::valueChanged()
{
m_swing->setAmount(m_sliderModel->value());
}
} // namespace gui
} // namespace lmms

96
src/core/MidiSwing.cpp Normal file
View File

@@ -0,0 +1,96 @@
/*
* 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 - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include <QLabel>
#include "MidiSwing.h"
namespace lmms
{
MidiSwing::MidiSwing(QObject* parent) :
Groove(parent)
{
}
MidiSwing::~MidiSwing()
{
}
static int applyMidiSwing(int posInEight);
int MidiSwing::isInTick(TimePos* curStart, const fpp_t frames, const f_cnt_t offset,
Note* n, MidiClip* c)
{
return isInTick(curStart, n, c);
}
int MidiSwing::isInTick(TimePos* curStart, Note* n, MidiClip* c)
{
// Where are we in the beat
int posInBeat = n->pos().getTicks() % 48; // assumes 48 ticks per beat, todo verify this
// the Midi Swing algorithm.
int posInEigth = -1;
if (posInBeat >= 12 && posInBeat < 18)
{
// 1st half of second quarter
// add a 0 - 24 tick shift
posInEigth = posInBeat - 12; // 0-5
}
else if (posInBeat >= 36 && posInBeat < 42)
{
// 1st half of third quarter
posInEigth = posInBeat - 36; // 0-5
}
int swingTicks = applyMidiSwing(posInEigth);
return curStart->getTicks() == swingTicks + n->pos().getTicks() ? 0 : -1;
}
QWidget* MidiSwing::instantiateView(QWidget* parent)
{
return new QLabel("");
}
static int applyMidiSwing(int posInEight)
{
// TODO case
if (posInEight < 0) return 0;
if (posInEight == 0) return 3;
if (posInEight == 1) return 3;
if (posInEight == 2) return 4;
if (posInEight == 3) return 4;
if (posInEight == 4) return 5;
if (posInEight == 5) return 5;
return 0;
}
} // namespace lmms

View File

@@ -40,6 +40,8 @@
#include "EnvelopeAndLfoParameters.h"
#include "Mixer.h"
#include "MixerView.h"
#include "GrooveFactory.h"
#include "GrooveView.h"
#include "GuiApplication.h"
#include "ExportFilter.h"
#include "InstrumentTrack.h"
@@ -70,6 +72,7 @@ Song::Song() :
m_globalAutomationTrack( dynamic_cast<AutomationTrack *>(
Track::create( Track::Type::HiddenAutomation,
this ) ) ),
m_globalGroove(GrooveFactory::create("none")),
m_tempoModel( DefaultTempo, MinTempo, MaxTempo, this, tr( "Tempo" ) ),
m_timeSigModel( this ),
m_oldTicksPerBar( DefaultTicksPerBar ),
@@ -130,6 +133,7 @@ Song::~Song()
{
m_playing = false;
delete m_globalAutomationTrack;
delete m_globalGroove;
}
@@ -603,8 +607,6 @@ void Song::setPlayPos( tick_t ticks, PlayMode playMode )
}
void Song::togglePause()
{
if( m_paused == true )
@@ -1088,6 +1090,18 @@ 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");
}
node = dataFile.content().firstChild();
QDomNodeList tclist=dataFile.content().elementsByTagName("trackcontainer");
@@ -1227,6 +1241,12 @@ bool Song::saveProjectFile(const QString & filename, bool withResources)
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::mixer()->saveState( dataFile, dataFile.content() );
if( getGUI() != nullptr )
{
@@ -1247,6 +1267,10 @@ bool Song::saveProjectFile(const QString & filename, bool withResources)
return dataFile.writeFile(filename, withResources);
}
void Song::setGlobalGroove(Groove * groove)
{
m_globalGroove = groove;
}
// Save the current song

View File

@@ -107,6 +107,7 @@ SET(LMMS_SRCS
gui/widgets/Fader.cpp
gui/widgets/FloatModelEditorBase.cpp
gui/widgets/Graph.cpp
gui/widgets/GrooveView.cpp
gui/widgets/GroupBox.cpp
gui/widgets/Knob.cpp
gui/widgets/LcdFloatSpinBox.cpp

View File

@@ -44,6 +44,7 @@
#include "CPULoadWidget.h"
#include "DeprecationHelper.h"
#include "embed.h"
#include "GrooveView.h"
#include "GuiApplication.h"
#include "LcdSpinBox.h"
#include "MainWindow.h"
@@ -163,6 +164,11 @@ SongEditor::SongEditor( Song * song ) :
getGUI()->mainWindow()->addSpacingToToolBar( 10 );
getGUI()->mainWindow()->addWidgetToToolBar( new GrooveView( tb ) );
getGUI()->mainWindow()->addSpacingToToolBar( 10 );
auto master_vol_lbl = new QLabel(tb);
master_vol_lbl->setPixmap( embed::getIconPixmap( "master_volume" ) );

View File

@@ -110,6 +110,18 @@ InstrumentTuningView::InstrumentTuningView(InstrumentTrack *it, QWidget *parent)
m_rangeImportCheckbox->setCheckable(true);
microtunerLayout->addWidget(m_rangeImportCheckbox);
// Groove toggle
m_grooveGroupBox = new GroupBox(tr("GROOVE"));
m_grooveGroupBox->setModel(&it->m_useGrooveModel);
layout->addWidget(m_grooveGroupBox);
QHBoxLayout *grooveLayout = new QHBoxLayout(m_grooveGroupBox);
grooveLayout->setContentsMargins(8, 18, 8, 8);
QLabel *grooveLabel = new QLabel(tr("Enables the use of Groove"));
grooveLabel->setFont(adjustedToPixelSize(tlabel->font(), 8));
grooveLayout->addWidget(grooveLabel);
// Fill remaining space
layout->addStretch();
}

View File

@@ -0,0 +1,139 @@
#include "GrooveView.h"
#include <QComboBox>
#include <QLabel>
#include <QVBoxLayout>
#include "embed.h"
#include "Engine.h"
#include "MainWindow.h"
#include "Song.h"
#include "GuiApplication.h"
#include "Groove.h"
#include "HydrogenSwing.h"
#include "HalfSwing.h"
#include "GrooveExperiments.h"
#include "MidiSwing.h"
namespace lmms::gui
{
GrooveView::GrooveView(QWidget * parent) :
QWidget(parent)
{
m_layout = new QVBoxLayout(this);
m_comboBox = new QComboBox(this);
// Insert reverse order.
m_comboBox->insertItem(0, tr("Experiment swing") , QVariant::fromValue(5));
m_comboBox->insertItem(0, tr("Half swing") , QVariant::fromValue(4));
m_comboBox->insertItem(0, tr("Hydrogen swing") , QVariant::fromValue(3));
m_comboBox->insertItem(0, tr("MIDI swing") , QVariant::fromValue(2));
m_comboBox->insertItem(0, tr("No swing") , QVariant::fromValue(1));
m_comboBox->setCurrentIndex(0);
m_layout->addWidget(m_comboBox);
m_layout->addWidget(new QLabel(""));
connect(m_comboBox, SIGNAL(currentIndexChanged(int)),
this, SLOT(grooveChanged()));
connect(Engine::getSong(), SIGNAL(dataChanged()),
this, SLOT(update()));
connect(Engine::getSong(), SIGNAL(projectLoaded()),
this, SLOT(update()));
update();
}
GrooveView::~GrooveView()
{
}
void GrooveView::update()
{
Groove * groove = Engine::getSong()->globalGroove();
if (groove->nodeName() == "none")
{
m_comboBox->setCurrentIndex(0);
}
if (groove->nodeName() == "midi")
{
m_comboBox->setCurrentIndex(1);
}
if (groove->nodeName() == "hydrogen")
{
m_comboBox->setCurrentIndex(2);
}
if (groove->nodeName() == "half")
{
m_comboBox->setCurrentIndex(3);
}
if (groove->nodeName() == "experiment")
{
m_comboBox->setCurrentIndex(4);
}
setView(groove);
}
void GrooveView::clear()
{
QLayoutItem * li = m_layout->takeAt(1);
delete li->widget();
delete li;
m_comboBox->setCurrentIndex(0);
m_layout->addWidget(new QLabel(""));
}
void GrooveView::grooveChanged()
{
Groove * groove = NULL;
int currentIndex = m_comboBox->currentIndex();
switch (currentIndex) {
case 0 :
{
groove = new Groove();
break;
}
case 1 :
{
groove = new MidiSwing();
break;
}
case 2 :
{
groove = new HydrogenSwing();
break;
}
case 3 :
{
groove = new HalfSwing();
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);
}
} // namespace lmms::gui

View File

@@ -62,10 +62,13 @@ 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( nullptr ),
m_noGroove( nullptr ),
m_pitchModel( 0, MinPitchDefault, MaxPitchDefault, 1, this, tr( "Pitch" ) ),
m_pitchRangeModel( 1, 1, 60, this, tr( "Pitch range" ) ),
m_mixerChannelModel( 0, 0, 0, this, tr( "Mixer channel" ) ),
m_useMasterPitchModel( true, this, tr( "Master pitch") ),
m_useGrooveModel( true, this, tr( "Groove" ) ),
m_instrument( nullptr ),
m_soundShaping( this ),
m_arpeggio( this ),
@@ -110,6 +113,7 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) :
connect(&m_pitchModel, SIGNAL(dataChanged()), this, SLOT(updatePitch()), Qt::DirectConnection);
connect(&m_pitchRangeModel, SIGNAL(dataChanged()), this, SLOT(updatePitchRange()), Qt::DirectConnection);
connect(&m_mixerChannelModel, SIGNAL(dataChanged()), this, SLOT(updateMixerChannel()), Qt::DirectConnection);
connect(&m_useGrooveModel, SIGNAL(dataChanged()), this, SLOT(updateGroove()), Qt::DirectConnection );
}
@@ -215,6 +219,10 @@ InstrumentTrack::~InstrumentTrack()
// now we're save deleting the instrument
if( m_instrument ) delete m_instrument;
if (m_noGroove != nullptr) {
delete m_noGroove;
}
}
@@ -737,6 +745,10 @@ bool InstrumentTrack::play( const TimePos & _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 (const auto& clip : clips)
@@ -759,10 +771,19 @@ bool InstrumentTrack::play( const TimePos & _start, const fpp_t _frames,
// ...and set our index to zero
auto nit = notes.begin();
Groove * groove = this->groove();
if (!groove) {
groove = globalGroove;
}
// 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-bar
@@ -771,29 +792,32 @@ bool InstrumentTrack::play( const TimePos & _start, const fpp_t _frames,
++nit;
}
}
while (nit != notes.end() && (*nit)->pos() == cur_start)
*/
while (nit != notes.end())
{
const auto currentNote = *nit;
// If the note is a Step Note, frames will be 0 so the NotePlayHandle
// plays for the whole length of the sample
const auto noteFrames = currentNote->type() == Note::Type::Step
? 0
: currentNote->length().frames(frames_per_tick);
NotePlayHandle* notePlayHandle = NotePlayHandleManager::acquire(this, _offset, noteFrames, *currentNote);
notePlayHandle->setPatternTrack(pattern_track);
// are we playing global song?
if( _clip_num < 0 )
const auto grooveOffset = groove->isInTick(&cur_start, _frames, _offset, currentNote, c);
if (grooveOffset >= 0)
{
// then set song-global offset of clip in order to
// properly perform the note detuning
notePlayHandle->setSongGlobalParentOffset( c->startPosition() );
}
// If the note is a Step Note, frames will be 0 so the NotePlayHandle
// plays for the whole length of the sample
const auto noteFrames = currentNote->type() == Note::Type::Step
? 0
: currentNote->length().frames(frames_per_tick);
Engine::audioEngine()->addPlayHandle( notePlayHandle );
played_a_note = true;
NotePlayHandle* notePlayHandle = NotePlayHandleManager::acquire(this, grooveOffset, noteFrames, *currentNote);
notePlayHandle->setPatternTrack(pattern_track);
// are we playing global song?
if ( _clip_num < 0 )
{
// then set song-global offset of pattern in order to
// properly perform the note detuning
notePlayHandle->setSongGlobalParentOffset( c->startPosition() );
}
Engine::audioEngine()->addPlayHandle( notePlayHandle );
played_a_note = true;
}
++nit;
}
}
@@ -801,8 +825,37 @@ bool InstrumentTrack::play( const TimePos & _start, const fpp_t _frames,
return played_a_note;
}
Groove * InstrumentTrack::groove()
{
if (m_grooveOn)
{
// nullptr: Use global groove
return m_groove;
}
else
{
// Disable global groove
return m_noGroove;
}
}
void InstrumentTrack::updateGroove()
{
if (m_useGrooveModel.value())
{
m_grooveOn = true;
}
else
{
if (!m_noGroove)
{
m_noGroove = new Groove();
}
m_grooveOn = false;
}
}
Clip* InstrumentTrack::createClip(const TimePos & pos)
{
@@ -835,6 +888,7 @@ void InstrumentTrack::saveTrackSpecificSettings( QDomDocument& doc, QDomElement
m_lastKeyModel.saveSettings(doc, thisElement, "lastkey");
m_useMasterPitchModel.saveSettings( doc, thisElement, "usemasterpitch");
m_microtuner.saveSettings(doc, thisElement);
m_useGrooveModel.saveSettings( doc, thisElement, "usegroove");
// Save MIDI CC stuff
m_midiCCEnable->saveSettings(doc, thisElement, "enablecc");
@@ -904,6 +958,7 @@ void InstrumentTrack::loadTrackSpecificSettings( const QDomElement & thisElement
m_lastKeyModel.loadSettings(thisElement, "lastkey");
m_useMasterPitchModel.loadSettings( thisElement, "usemasterpitch");
m_microtuner.loadSettings(thisElement);
m_useGrooveModel.loadSettings(thisElement, "usegroove");
// clear effect-chain just in case we load an old preset without FX-data
m_audioPort.effects()->clear();