Sample-exact models: improve
- Remove the redundant hasSampleExactData() function. Instead, signal lack of s.ex.data by returning a NULL in valueBuffer() - Cache s.ex.buffers and only update them once per period - Make valueBuffer() in AutomatableModel threadsafe so that it can be used for NPH's sharing the same model - Add sample-exactness to instrumenttrack's vol & pan knobs
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
#define AUTOMATABLE_MODEL_H
|
||||
|
||||
#include <math.h>
|
||||
#include <QtCore/QMutex>
|
||||
|
||||
#include "JournallingObject.h"
|
||||
#include "Model.h"
|
||||
@@ -103,9 +104,6 @@ public:
|
||||
{
|
||||
return isAutomated() || m_controllerConnection != NULL;
|
||||
}
|
||||
|
||||
bool hasSampleExactData() const;
|
||||
|
||||
|
||||
ControllerConnection* controllerConnection() const
|
||||
{
|
||||
@@ -142,10 +140,8 @@ public:
|
||||
|
||||
float controllerValue( int frameOffset ) const;
|
||||
|
||||
// returns sample-exact data as a ValueBuffer
|
||||
// should only be called when sample-exact data exists
|
||||
// in other cases (eg. for automation), the receiving end should interpolate
|
||||
// the values themselves
|
||||
//! @brief Function that returns sample-exact data as a ValueBuffer
|
||||
//! @return pointer to model's valueBuffer when s.ex.data exists, NULL otherwise
|
||||
ValueBuffer * valueBuffer();
|
||||
|
||||
template<class T>
|
||||
@@ -264,6 +260,16 @@ public:
|
||||
{
|
||||
m_hasStrictStepSize = b;
|
||||
}
|
||||
|
||||
static void incrementPeriodCounter()
|
||||
{
|
||||
++s_periodCounter;
|
||||
}
|
||||
|
||||
static void resetPeriodCounter()
|
||||
{
|
||||
s_periodCounter = 0;
|
||||
}
|
||||
|
||||
public slots:
|
||||
virtual void reset();
|
||||
@@ -332,6 +338,13 @@ private:
|
||||
static float s_copiedValue;
|
||||
|
||||
ValueBuffer m_valueBuffer;
|
||||
long m_lastUpdatedPeriod;
|
||||
static long s_periodCounter;
|
||||
|
||||
bool m_hasSampleExactData;
|
||||
|
||||
// prevent several threads from attempting to write the same vb at the same time
|
||||
QMutex m_valueBufferMutex;
|
||||
|
||||
signals:
|
||||
void initValueChanged( float val );
|
||||
|
||||
@@ -75,21 +75,10 @@ bool AmplifierEffect::processAudioBuffer( sampleFrame* buf, const fpp_t frames )
|
||||
const float d = dryLevel();
|
||||
const float w = wetLevel();
|
||||
|
||||
ValueBuffer * volBuf = m_ampControls.m_volumeModel.hasSampleExactData()
|
||||
? m_ampControls.m_volumeModel.valueBuffer()
|
||||
: NULL;
|
||||
|
||||
ValueBuffer * panBuf = m_ampControls.m_panModel.hasSampleExactData()
|
||||
? m_ampControls.m_panModel.valueBuffer()
|
||||
: NULL;
|
||||
|
||||
ValueBuffer * leftBuf = m_ampControls.m_leftModel.hasSampleExactData()
|
||||
? m_ampControls.m_leftModel.valueBuffer()
|
||||
: NULL;
|
||||
|
||||
ValueBuffer * rightBuf = m_ampControls.m_rightModel.hasSampleExactData()
|
||||
? m_ampControls.m_rightModel.valueBuffer()
|
||||
: NULL;
|
||||
ValueBuffer * volBuf = m_ampControls.m_volumeModel.valueBuffer();
|
||||
ValueBuffer * panBuf = m_ampControls.m_panModel.valueBuffer();
|
||||
ValueBuffer * leftBuf = m_ampControls.m_leftModel.valueBuffer();
|
||||
ValueBuffer * rightBuf = m_ampControls.m_rightModel.valueBuffer();
|
||||
|
||||
for( fpp_t f = 0; f < frames; ++f )
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#include "lmms_math.h"
|
||||
|
||||
float AutomatableModel::s_copiedValue = 0;
|
||||
|
||||
long AutomatableModel::s_periodCounter = 0;
|
||||
|
||||
|
||||
|
||||
@@ -51,7 +51,9 @@ AutomatableModel::AutomatableModel( DataType type,
|
||||
m_hasStrictStepSize( false ),
|
||||
m_hasLinkedModels( false ),
|
||||
m_controllerConnection( NULL ),
|
||||
m_valueBuffer( static_cast<int>( engine::mixer()->framesPerPeriod() ) )
|
||||
m_valueBuffer( static_cast<int>( engine::mixer()->framesPerPeriod() ) ),
|
||||
m_lastUpdatedPeriod( -1 ),
|
||||
m_hasSampleExactData( false )
|
||||
|
||||
{
|
||||
setInitValue( val );
|
||||
@@ -86,39 +88,6 @@ bool AutomatableModel::isAutomated() const
|
||||
return AutomationPattern::isAutomated( this );
|
||||
}
|
||||
|
||||
bool AutomatableModel::hasSampleExactData() const
|
||||
{
|
||||
// if a controller is connected...
|
||||
if( m_controllerConnection != NULL )
|
||||
{
|
||||
// ...and is sample-exact, then return true
|
||||
if( m_controllerConnection->getController()->isSampleExact() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// check also the same for the first linked model
|
||||
if( hasLinkedModels() )
|
||||
{
|
||||
AutomatableModel* lm = m_linkedModels.first();
|
||||
if( lm->m_controllerConnection != NULL )
|
||||
{
|
||||
if( lm->m_controllerConnection->getController()->isSampleExact() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we have values we can interpolate return true
|
||||
if( m_oldValue != m_value )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// otherwise, return false
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, const QString& name )
|
||||
{
|
||||
@@ -554,6 +523,23 @@ float AutomatableModel::controllerValue( int frameOffset ) const
|
||||
|
||||
ValueBuffer * AutomatableModel::valueBuffer()
|
||||
{
|
||||
// if we've already calculated the valuebuffer this period, return the cached buffer
|
||||
if( m_lastUpdatedPeriod == s_periodCounter )
|
||||
{
|
||||
return m_hasSampleExactData
|
||||
? &m_valueBuffer
|
||||
: NULL;
|
||||
}
|
||||
QMutexLocker m( &m_valueBufferMutex );
|
||||
if( m_lastUpdatedPeriod == s_periodCounter )
|
||||
{
|
||||
return m_hasSampleExactData
|
||||
? &m_valueBuffer
|
||||
: NULL;
|
||||
}
|
||||
|
||||
float val = m_value; // make sure our m_value doesn't change midway
|
||||
|
||||
ValueBuffer * vb;
|
||||
if( m_controllerConnection && m_controllerConnection->getController()->isSampleExact() )
|
||||
{
|
||||
@@ -581,6 +567,8 @@ ValueBuffer * AutomatableModel::valueBuffer()
|
||||
"lacks implementation for a scale type");
|
||||
break;
|
||||
}
|
||||
m_lastUpdatedPeriod = s_periodCounter;
|
||||
m_hasSampleExactData = true;
|
||||
return &m_valueBuffer;
|
||||
}
|
||||
}
|
||||
@@ -598,20 +586,25 @@ ValueBuffer * AutomatableModel::valueBuffer()
|
||||
{
|
||||
nvalues[i] = fittedValue( values[i], false );
|
||||
}
|
||||
m_lastUpdatedPeriod = s_periodCounter;
|
||||
m_hasSampleExactData = true;
|
||||
return &m_valueBuffer;
|
||||
}
|
||||
|
||||
if( m_oldValue != m_value )
|
||||
if( m_oldValue != val )
|
||||
{
|
||||
m_valueBuffer.interpolate( m_oldValue, m_value );
|
||||
m_oldValue = m_value;
|
||||
m_valueBuffer.interpolate( m_oldValue, val );
|
||||
m_oldValue = val;
|
||||
m_lastUpdatedPeriod = s_periodCounter;
|
||||
m_hasSampleExactData = true;
|
||||
return &m_valueBuffer;
|
||||
}
|
||||
|
||||
// if we have no sample-exact source for a ValueBuffer, create one and fill it with current value
|
||||
// ideally, recipients should check first if we hasSampleExactData before fetching ValueBuffers
|
||||
m_valueBuffer.fill( m_value );
|
||||
return &m_valueBuffer;
|
||||
// if we have no sample-exact source for a ValueBuffer, return NULL to signify that no data is available at the moment
|
||||
// in which case the recipient knows to use the static value() instead
|
||||
m_lastUpdatedPeriod = s_periodCounter;
|
||||
m_hasSampleExactData = false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -126,12 +126,8 @@ void FxChannel::doProcessing( sampleFrame * _buf )
|
||||
if( sender->m_hasInput || sender->m_stillRunning )
|
||||
{
|
||||
// figure out if we're getting sample-exact input
|
||||
ValueBuffer * sendBuf = sendModel->hasSampleExactData()
|
||||
? sendModel->valueBuffer()
|
||||
: NULL;
|
||||
ValueBuffer * volBuf = sender->m_volumeModel.hasSampleExactData()
|
||||
? sender->m_volumeModel.valueBuffer()
|
||||
: NULL;
|
||||
ValueBuffer * sendBuf = sendModel->valueBuffer();
|
||||
ValueBuffer * volBuf = sender->m_volumeModel.valueBuffer();
|
||||
|
||||
// mix it's output with this one's output
|
||||
sampleFrame * ch_buf = sender->m_buffer;
|
||||
@@ -526,9 +522,7 @@ void FxMixer::masterMix( sampleFrame * _buf )
|
||||
//m_sendsMutex.unlock();
|
||||
|
||||
// handle sample-exact data in master volume fader
|
||||
ValueBuffer * volBuf = m_fxChannels[0]->m_volumeModel.hasSampleExactData()
|
||||
? m_fxChannels[0]->m_volumeModel.valueBuffer()
|
||||
: NULL;
|
||||
ValueBuffer * volBuf = m_fxChannels[0]->m_volumeModel.valueBuffer();
|
||||
|
||||
if( volBuf )
|
||||
{
|
||||
|
||||
@@ -109,7 +109,7 @@ void LfoController::updateValueBuffer()
|
||||
? m_sampleFunction( phase )
|
||||
: m_userDefSampleBuffer->userWaveSample( phase );
|
||||
|
||||
values[i] = m_baseModel.value() + ( m_amountModel.value() * currentSample / 2.0f );
|
||||
values[i] = qBound( 0.0f, m_baseModel.value() + ( m_amountModel.value() * currentSample / 2.0f ), 1.0f );
|
||||
|
||||
phase += 1.0 / m_duration;
|
||||
}
|
||||
|
||||
@@ -418,6 +418,7 @@ const surroundSampleFrame * Mixer::renderNextBuffer()
|
||||
// and trigger LFOs
|
||||
EnvelopeAndLfoParameters::instances()->trigger();
|
||||
Controller::triggerFrameCounter();
|
||||
AutomatableModel::incrementPeriodCounter();
|
||||
|
||||
const float new_cpu_load = timer.elapsed() / 10000.0f *
|
||||
processingSampleRate() / m_framesPerPeriod;
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
#include "tab_widget.h"
|
||||
#include "tooltip.h"
|
||||
#include "track_label_button.h"
|
||||
|
||||
#include "ValueBuffer.h"
|
||||
|
||||
|
||||
const char * volume_help = QT_TRANSLATE_NOOP( "InstrumentTrack",
|
||||
@@ -186,7 +186,12 @@ void InstrumentTrack::processAudioBuffer( sampleFrame* buf, const fpp_t frames,
|
||||
// now
|
||||
m_audioPort.effects()->startRunning();
|
||||
|
||||
float v_scale = (float) getVolume() / DefaultVolume;
|
||||
// get volume knob data
|
||||
static const float DefaultVolumeRatio = 1.0f / DefaultVolume;
|
||||
ValueBuffer * volBuf = m_volumeModel.valueBuffer();
|
||||
float v_scale = volBuf
|
||||
? 1.0f
|
||||
: getVolume() * DefaultVolumeRatio;
|
||||
|
||||
// instruments using instrument-play-handles will call this method
|
||||
// without any knowledge about notes, so they pass NULL for n, which
|
||||
@@ -195,12 +200,16 @@ void InstrumentTrack::processAudioBuffer( sampleFrame* buf, const fpp_t frames,
|
||||
{
|
||||
const f_cnt_t offset = n->noteOffset();
|
||||
m_soundShaping.processAudioBuffer( buf + offset, frames - offset, n );
|
||||
v_scale *= ( (float) n->getVolume() / DefaultVolume );
|
||||
v_scale *= ( (float) n->getVolume() * DefaultVolumeRatio );
|
||||
}
|
||||
|
||||
m_audioPort.setNextFxChannel( m_effectChannelModel.value() );
|
||||
|
||||
int panning = m_panningModel.value();
|
||||
// get panning knob data
|
||||
ValueBuffer * panBuf = m_panningModel.valueBuffer();
|
||||
int panning = panBuf
|
||||
? 0
|
||||
: m_panningModel.value();
|
||||
|
||||
if( n )
|
||||
{
|
||||
@@ -208,6 +217,26 @@ void InstrumentTrack::processAudioBuffer( sampleFrame* buf, const fpp_t frames,
|
||||
panning = tLimit<int>( panning, PanningLeft, PanningRight );
|
||||
}
|
||||
|
||||
// apply sample-exact volume/panning data
|
||||
if( volBuf )
|
||||
{
|
||||
for( f_cnt_t f = 0; f < frames; ++f )
|
||||
{
|
||||
float v = volBuf->values()[ f ] * 0.01f;
|
||||
buf[f][0] *= v;
|
||||
buf[f][1] *= v;
|
||||
}
|
||||
}
|
||||
if( panBuf )
|
||||
{
|
||||
for( f_cnt_t f = 0; f < frames; ++f )
|
||||
{
|
||||
float p = panBuf->values()[ f ] * 0.01f;
|
||||
buf[f][0] *= ( p <= 0 ? 1.0f : 1.0f - p );
|
||||
buf[f][1] *= ( p >= 0 ? 1.0f : 1.0f + p );
|
||||
}
|
||||
}
|
||||
|
||||
engine::mixer()->bufferToPort( buf, frames, panningToVolumeVector( panning, v_scale ), &m_audioPort );
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user