diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 5b51f8184f4..95a4f635d03 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -26,6 +26,7 @@ #define AUTOMATABLE_MODEL_H #include +#include #include "JournallingObject.h" #include "Model.h" @@ -103,9 +104,6 @@ class EXPORT AutomatableModel : public Model, public JournallingObject { return isAutomated() || m_controllerConnection != NULL; } - - bool hasSampleExactData() const; - ControllerConnection* controllerConnection() const { @@ -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 @@ -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(); @@ -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 ); diff --git a/plugins/Amplifier/Amplifier.cpp b/plugins/Amplifier/Amplifier.cpp index 3ba9d224c40..f628b7be71c 100644 --- a/plugins/Amplifier/Amplifier.cpp +++ b/plugins/Amplifier/Amplifier.cpp @@ -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 ) { diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index 0effb88c103..8b93cfb9fee 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -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( engine::mixer()->framesPerPeriod() ) ) + m_valueBuffer( static_cast( 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; } diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index 77c12c13abb..86fb442c420 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -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 ) { diff --git a/src/core/LfoController.cpp b/src/core/LfoController.cpp index adf2b536a8d..8d563e88f77 100644 --- a/src/core/LfoController.cpp +++ b/src/core/LfoController.cpp @@ -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; } diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index a9c8cad1b90..9281292714e 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -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; diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 9ae4fe81399..87d0de299e7 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -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( 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 ); }