Skip to content

Commit

Permalink
Sample-exact models: improve
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
diizy committed Jun 29, 2014
1 parent 71217c0 commit 23433a7
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 78 deletions.
27 changes: 20 additions & 7 deletions include/AutomatableModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#define AUTOMATABLE_MODEL_H

#include <math.h>
#include <QtCore/QMutex>

#include "JournallingObject.h"
#include "Model.h"
Expand Down Expand Up @@ -103,9 +104,6 @@ class EXPORT AutomatableModel : public Model, public JournallingObject
{
return isAutomated() || m_controllerConnection != NULL;
}

bool hasSampleExactData() const;


ControllerConnection* controllerConnection() const
{
Expand Down Expand Up @@ -142,10 +140,8 @@ class EXPORT AutomatableModel : public Model, public JournallingObject

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>
Expand Down Expand Up @@ -264,6 +260,16 @@ class EXPORT AutomatableModel : public Model, public JournallingObject
{
m_hasStrictStepSize = b;
}

static void incrementPeriodCounter()
{
++s_periodCounter;
}

static void resetPeriodCounter()
{
s_periodCounter = 0;
}

public slots:
virtual void reset();
Expand Down Expand Up @@ -332,6 +338,13 @@ public slots:
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 );
Expand Down
19 changes: 4 additions & 15 deletions plugins/Amplifier/Amplifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
{
Expand Down
77 changes: 35 additions & 42 deletions src/core/AutomatableModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
#include "lmms_math.h"

float AutomatableModel::s_copiedValue = 0;

long AutomatableModel::s_periodCounter = 0;



Expand All @@ -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 );
Expand Down Expand Up @@ -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 )
{
Expand Down Expand Up @@ -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() )
{
Expand Down Expand Up @@ -581,6 +567,8 @@ ValueBuffer * AutomatableModel::valueBuffer()
"lacks implementation for a scale type");
break;
}
m_lastUpdatedPeriod = s_periodCounter;
m_hasSampleExactData = true;
return &m_valueBuffer;
}
}
Expand All @@ -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;
}


Expand Down
12 changes: 3 additions & 9 deletions src/core/FxMixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 )
{
Expand Down
2 changes: 1 addition & 1 deletion src/core/LfoController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions src/core/Mixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
37 changes: 33 additions & 4 deletions src/tracks/InstrumentTrack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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
Expand All @@ -195,19 +200,43 @@ 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 )
{
panning += n->getPanning();
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 );
}

Expand Down

0 comments on commit 23433a7

Please sign in to comment.