Play automation pattern when midi controller connected (#5657)
* Play automation pattern when midi controller connected * empty line removed * Improved for clarity * removed midi specific controller logic * formatting changes * comments added Co-authored-by: IanCaio <iancaio_dev@hotmail.com> Co-authored-by: IanCaio <iancaio_dev@hotmail.com>
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
#include "ValueBuffer.h"
|
||||
#include "MemoryManager.h"
|
||||
#include "ModelVisitor.h"
|
||||
#include "ControllerConnection.h"
|
||||
|
||||
// simple way to map a property of a view to a model
|
||||
#define mapPropertyFromModelPtr(type,getfunc,setfunc,modelname) \
|
||||
@@ -148,7 +149,18 @@ public:
|
||||
template<class T>
|
||||
inline T value( int frameOffset = 0 ) const
|
||||
{
|
||||
if( hasLinkedModels() || m_controllerConnection != NULL )
|
||||
if (m_controllerConnection)
|
||||
{
|
||||
if (!m_useControllerValue)
|
||||
{
|
||||
return castValue<T>(m_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return castValue<T>(controllerValue(frameOffset));
|
||||
}
|
||||
}
|
||||
else if (hasLinkedModels())
|
||||
{
|
||||
return castValue<T>( controllerValue( frameOffset ) );
|
||||
}
|
||||
@@ -298,9 +310,15 @@ public:
|
||||
s_periodCounter = 0;
|
||||
}
|
||||
|
||||
bool useControllerValue()
|
||||
{
|
||||
return m_useControllerValue;
|
||||
}
|
||||
|
||||
public slots:
|
||||
virtual void reset();
|
||||
void unlinkControllerConnection();
|
||||
void setUseControllerValue(bool b = true);
|
||||
|
||||
|
||||
protected:
|
||||
@@ -395,6 +413,8 @@ private:
|
||||
// prevent several threads from attempting to write the same vb at the same time
|
||||
QMutex m_valueBufferMutex;
|
||||
|
||||
bool m_useControllerValue;
|
||||
|
||||
signals:
|
||||
void initValueChanged( float val );
|
||||
void destroyed( jo_id_t id );
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QVector>
|
||||
|
||||
#include "AutomatableModel.h"
|
||||
#include "Controller.h"
|
||||
#include "JournallingObject.h"
|
||||
#include "ValueBuffer.h"
|
||||
@@ -47,7 +48,7 @@ class LMMS_EXPORT ControllerConnection : public QObject, public JournallingObjec
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
ControllerConnection( Controller * _controller );
|
||||
ControllerConnection(Controller * _controller, AutomatableModel * contmod);
|
||||
ControllerConnection( int _controllerId );
|
||||
|
||||
virtual ~ControllerConnection();
|
||||
@@ -98,7 +99,6 @@ public:
|
||||
return classNodeName();
|
||||
}
|
||||
|
||||
|
||||
public slots:
|
||||
void deleteConnection();
|
||||
|
||||
@@ -112,6 +112,8 @@ protected:
|
||||
|
||||
static ControllerConnectionVector s_connections;
|
||||
|
||||
AutomatableModel * m_controlledModel;
|
||||
|
||||
signals:
|
||||
// The value changed while the mixer isn't running (i.e: MIDI CC)
|
||||
void valueChanged();
|
||||
|
||||
@@ -470,6 +470,8 @@ private:
|
||||
TimePos m_exportSongEnd;
|
||||
TimePos m_exportEffectiveLength;
|
||||
|
||||
AutomatedValueMap m_oldAutomatedValues;
|
||||
|
||||
friend class LmmsCore;
|
||||
friend class SongEditor;
|
||||
friend class mainWindow;
|
||||
|
||||
@@ -53,7 +53,8 @@ AutomatableModel::AutomatableModel(
|
||||
m_controllerConnection( NULL ),
|
||||
m_valueBuffer( static_cast<int>( Engine::mixer()->framesPerPeriod() ) ),
|
||||
m_lastUpdatedPeriod( -1 ),
|
||||
m_hasSampleExactData( false )
|
||||
m_hasSampleExactData(false),
|
||||
m_useControllerValue(true)
|
||||
|
||||
{
|
||||
m_value = fittedValue( val );
|
||||
@@ -214,7 +215,7 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString&
|
||||
}
|
||||
if( thisConnection.isElement() )
|
||||
{
|
||||
setControllerConnection( new ControllerConnection( (Controller*)NULL ) );
|
||||
setControllerConnection(new ControllerConnection((Controller*)NULL, this));
|
||||
m_controllerConnection->loadSettings( thisConnection.toElement() );
|
||||
//m_controllerConnection->setTargetName( displayName() );
|
||||
}
|
||||
@@ -371,6 +372,8 @@ void AutomatableModel::roundAt( T& value, const T& where ) const
|
||||
|
||||
void AutomatableModel::setAutomatedValue( const float value )
|
||||
{
|
||||
setUseControllerValue(false);
|
||||
|
||||
m_oldValue = m_value;
|
||||
++m_setValueDepth;
|
||||
const float oldValue = m_value;
|
||||
@@ -382,13 +385,13 @@ void AutomatableModel::setAutomatedValue( const float value )
|
||||
if( oldValue != m_value )
|
||||
{
|
||||
// notify linked models
|
||||
for( AutoModelVector::Iterator it = m_linkedModels.begin();
|
||||
it != m_linkedModels.end(); ++it )
|
||||
for (AutoModelVector::Iterator it = m_linkedModels.begin();
|
||||
it != m_linkedModels.end(); ++it)
|
||||
{
|
||||
if( (*it)->m_setValueDepth < 1 &&
|
||||
(*it)->fittedValue( m_value ) != (*it)->m_value )
|
||||
if (!((*it)->controllerConnection()) && (*it)->m_setValueDepth < 1 &&
|
||||
(*it)->fittedValue(m_value) != (*it)->m_value)
|
||||
{
|
||||
(*it)->setAutomatedValue( value );
|
||||
(*it)->setAutomatedValue(value);
|
||||
}
|
||||
}
|
||||
m_valueChanged = true;
|
||||
@@ -584,7 +587,7 @@ float AutomatableModel::controllerValue( int frameOffset ) const
|
||||
}
|
||||
|
||||
AutomatableModel* lm = m_linkedModels.first();
|
||||
if( lm->controllerConnection() )
|
||||
if (lm->controllerConnection() && lm->useControllerValue())
|
||||
{
|
||||
return fittedValue( lm->controllerValue( frameOffset ) );
|
||||
}
|
||||
@@ -607,7 +610,7 @@ ValueBuffer * AutomatableModel::valueBuffer()
|
||||
float val = m_value; // make sure our m_value doesn't change midway
|
||||
|
||||
ValueBuffer * vb;
|
||||
if( m_controllerConnection && m_controllerConnection->getController()->isSampleExact() )
|
||||
if (m_controllerConnection && m_useControllerValue && m_controllerConnection->getController()->isSampleExact())
|
||||
{
|
||||
vb = m_controllerConnection->valueBuffer();
|
||||
if( vb )
|
||||
@@ -638,23 +641,28 @@ ValueBuffer * AutomatableModel::valueBuffer()
|
||||
return &m_valueBuffer;
|
||||
}
|
||||
}
|
||||
AutomatableModel* lm = NULL;
|
||||
if( hasLinkedModels() )
|
||||
|
||||
if (!m_controllerConnection)
|
||||
{
|
||||
lm = m_linkedModels.first();
|
||||
}
|
||||
if( lm && lm->controllerConnection() && lm->controllerConnection()->getController()->isSampleExact() )
|
||||
{
|
||||
vb = lm->valueBuffer();
|
||||
float * values = vb->values();
|
||||
float * nvalues = m_valueBuffer.values();
|
||||
for( int i = 0; i < vb->length(); i++ )
|
||||
AutomatableModel* lm = NULL;
|
||||
if (hasLinkedModels())
|
||||
{
|
||||
nvalues[i] = fittedValue( values[i] );
|
||||
lm = m_linkedModels.first();
|
||||
}
|
||||
if (lm && lm->controllerConnection() && lm->useControllerValue() &&
|
||||
lm->controllerConnection()->getController()->isSampleExact())
|
||||
{
|
||||
vb = lm->valueBuffer();
|
||||
float * values = vb->values();
|
||||
float * nvalues = m_valueBuffer.values();
|
||||
for (int i = 0; i < vb->length(); i++)
|
||||
{
|
||||
nvalues[i] = fittedValue(values[i]);
|
||||
}
|
||||
m_lastUpdatedPeriod = s_periodCounter;
|
||||
m_hasSampleExactData = true;
|
||||
return &m_valueBuffer;
|
||||
}
|
||||
m_lastUpdatedPeriod = s_periodCounter;
|
||||
m_hasSampleExactData = true;
|
||||
return &m_valueBuffer;
|
||||
}
|
||||
|
||||
if( m_oldValue != val )
|
||||
@@ -766,6 +774,20 @@ float AutomatableModel::globalAutomationValueAt( const TimePos& time )
|
||||
}
|
||||
}
|
||||
|
||||
void AutomatableModel::setUseControllerValue(bool b)
|
||||
{
|
||||
if (b)
|
||||
{
|
||||
m_useControllerValue = true;
|
||||
emit dataChanged();
|
||||
}
|
||||
else if (m_controllerConnection && m_useControllerValue)
|
||||
{
|
||||
m_useControllerValue = false;
|
||||
emit dataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
float FloatModel::getRoundedValue() const
|
||||
{
|
||||
return qRound( value() / step<float>() ) * step<float>();
|
||||
|
||||
@@ -36,10 +36,11 @@ ControllerConnectionVector ControllerConnection::s_connections;
|
||||
|
||||
|
||||
|
||||
ControllerConnection::ControllerConnection( Controller * _controller ) :
|
||||
ControllerConnection::ControllerConnection(Controller * _controller, AutomatableModel * contmod) :
|
||||
m_controller( NULL ),
|
||||
m_controllerId( -1 ),
|
||||
m_ownsController( false )
|
||||
m_ownsController(false),
|
||||
m_controlledModel(contmod)
|
||||
{
|
||||
if( _controller != NULL )
|
||||
{
|
||||
@@ -121,7 +122,13 @@ void ControllerConnection::setController( Controller * _controller )
|
||||
}
|
||||
|
||||
m_ownsController =
|
||||
( _controller->type() == Controller::MidiController );
|
||||
(_controller->type() == Controller::MidiController);
|
||||
|
||||
connect(Engine::getSong(), SIGNAL(stopped()),
|
||||
m_controlledModel, SLOT(setUseControllerValue()),
|
||||
Qt::UniqueConnection);
|
||||
|
||||
m_controlledModel->setUseControllerValue(true);
|
||||
|
||||
// If we don't own the controller, allow deletion of controller
|
||||
// to delete the connection
|
||||
|
||||
@@ -94,7 +94,8 @@ Song::Song() :
|
||||
m_elapsedTicks( 0 ),
|
||||
m_elapsedBars( 0 ),
|
||||
m_loopRenderCount(1),
|
||||
m_loopRenderRemaining(1)
|
||||
m_loopRenderRemaining(1),
|
||||
m_oldAutomatedValues()
|
||||
{
|
||||
for(int i = 0; i < Mode_Count; ++i) m_elapsedMilliSeconds[i] = 0;
|
||||
connect( &m_tempoModel, SIGNAL( dataChanged() ),
|
||||
@@ -404,6 +405,21 @@ void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, fpp
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if an automated model stopped being automated by automation patterns
|
||||
// so we can move the control back to any connected controller again
|
||||
if (!m_oldAutomatedValues.isEmpty())
|
||||
{
|
||||
for (auto it = m_oldAutomatedValues.begin(); it != m_oldAutomatedValues.end(); it++)
|
||||
{
|
||||
AutomatableModel * am = it.key();
|
||||
if (am->controllerConnection() && !values.contains(am))
|
||||
{
|
||||
am->setUseControllerValue(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_oldAutomatedValues = values;
|
||||
|
||||
// Apply values
|
||||
for (auto it = values.begin(); it != values.end(); it++)
|
||||
{
|
||||
@@ -411,6 +427,10 @@ void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, fpp
|
||||
{
|
||||
it.key()->setAutomatedValue(it.value());
|
||||
}
|
||||
else if (!it.key()->useControllerValue())
|
||||
{
|
||||
it.key()->setUseControllerValue(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "embed.h"
|
||||
#include "GuiApplication.h"
|
||||
#include "MainWindow.h"
|
||||
#include "Song.h"
|
||||
#include "StringPairDrag.h"
|
||||
#include "Clipboard.h"
|
||||
|
||||
@@ -228,7 +229,7 @@ void AutomatableModelViewSlots::execConnectionDialog()
|
||||
// New
|
||||
else
|
||||
{
|
||||
ControllerConnection* cc = new ControllerConnection( d.chosenController() );
|
||||
ControllerConnection* cc = new ControllerConnection(d.chosenController(), m);
|
||||
m->setControllerConnection( cc );
|
||||
//cc->setTargetName( m->displayName() );
|
||||
}
|
||||
@@ -250,8 +251,12 @@ void AutomatableModelViewSlots::removeConnection()
|
||||
|
||||
if( m->controllerConnection() )
|
||||
{
|
||||
disconnect(Engine::getSong(), SIGNAL(stopped()),
|
||||
m, SLOT(setUseControllerValue()));
|
||||
|
||||
delete m->controllerConnection();
|
||||
m->setControllerConnection( NULL );
|
||||
emit m->dataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,7 +300,6 @@ void AutomatableModelViewSlots::pasteFromClipboard()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Attempt to parse a float from the clipboard
|
||||
static float floatFromClipboard(bool* ok)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user