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:
Vesa
2014-06-30 01:59:18 +03:00
parent 71217c0d85
commit 23433a70b5
7 changed files with 97 additions and 78 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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