diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 206cdafa8..d0c0aab87 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -30,6 +30,7 @@ #include "JournallingObject.h" #include "Model.h" #include "MidiTime.h" +#include "ValueBuffer.h" // simple way to map a property of a view to a model @@ -98,6 +99,13 @@ public: } bool isAutomated() const; + bool isAutomatedOrControlled() const + { + return isAutomated() || m_controllerConnection != NULL; + } + + bool hasSampleExactData() const; + ControllerConnection* controllerConnection() const { @@ -134,6 +142,12 @@ 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 + ValueBuffer * valueBuffer(); + template T initValue() const { @@ -241,6 +255,16 @@ public: } float globalAutomationValueAt( const MidiTime& time ); + + bool hasStrictStepSize() const + { + return m_hasStrictStepSize; + } + + void setStrictStepSize( const bool b ) + { + m_hasStrictStepSize = b; + } public slots: virtual void reset(); @@ -254,7 +278,7 @@ protected: //! max() and aligned according to the step size (step size 0.05 -> value //! 0.12345 becomes 0.10 etc.). You should always call it at the end after //! doing your own calculations. - float fittedValue( float value ) const; + float fittedValue( float value, bool forceStep = false ) const; private: @@ -290,10 +314,13 @@ private: float m_range; float m_centerValue; - // most objects will need this temporarily (until sampleExact is - // standard) + // currently unused? float m_oldValue; int m_setValueDepth; + + // used to determine if step size should be applied strictly (ie. always) + // or only when value set from gui (default) + bool m_hasStrictStepSize; AutoModelVector m_linkedModels; bool m_hasLinkedModels; @@ -305,6 +332,7 @@ private: static float s_copiedValue; + ValueBuffer m_valueBuffer; signals: void initValueChanged( float val ); diff --git a/include/Controller.h b/include/Controller.h index 01f791321..4a7a754e4 100644 --- a/include/Controller.h +++ b/include/Controller.h @@ -31,6 +31,7 @@ #include "Mixer.h" #include "Model.h" #include "JournallingObject.h" +#include "ValueBuffer.h" class ControllerDialog; class Controller; @@ -62,6 +63,8 @@ public: virtual ~Controller(); virtual float currentValue( int _offset ); + // The per-controller get-value-in-buffers function + virtual ValueBuffer * valueBuffer(); inline bool isSampleExact() const { @@ -111,6 +114,10 @@ public: return tLimit( _val, 0.0f, 1.0f ); } + static long runningPeriods() + { + return s_periods; + } static unsigned int runningFrames(); static float runningTime(); @@ -138,6 +145,15 @@ protected: // The internal per-controller get-value function virtual float value( int _offset ); + virtual void updateValueBuffer(); + + // buffer for storing sample-exact values in case there + // are more than one model wanting it, so we don't have to create it + // again every time + ValueBuffer m_valueBuffer; + // when we last updated the valuebuffer - so we know if we have to update it + long m_bufferLastUpdated; + float m_currentValue; bool m_sampleExact; int m_connectionCount; @@ -147,7 +163,7 @@ protected: static ControllerVector s_controllers; - static unsigned int s_frames; + static long s_periods; signals: diff --git a/include/ControllerConnection.h b/include/ControllerConnection.h index 648c1711a..a0eec6cb4 100644 --- a/include/ControllerConnection.h +++ b/include/ControllerConnection.h @@ -27,14 +27,15 @@ */ -#ifndef _CONTROLLER_CONNECTION_H -#define _CONTROLLER_CONNECTION_H +#ifndef CONTROLLER_CONNECTION_H +#define CONTROLLER_CONNECTION_H #include #include #include "Controller.h" #include "JournallingObject.h" +#include "ValueBuffer.h" class ControllerConnection; @@ -64,6 +65,11 @@ public: { return m_controller->currentValue( _offset ); } + + ValueBuffer * valueBuffer() + { + return m_controller->valueBuffer(); + } inline void setTargetName( const QString & _name ); diff --git a/include/LfoController.h b/include/LfoController.h index 59bea8689..d5437d7b0 100644 --- a/include/LfoController.h +++ b/include/LfoController.h @@ -22,8 +22,8 @@ * */ -#ifndef _LFO_CONTROLLER_H -#define _LFO_CONTROLLER_H +#ifndef LFO_CONTROLLER_H +#define LFO_CONTROLLER_H #include @@ -59,8 +59,8 @@ public slots: protected: - // The internal per-controller get-value function - virtual float value( int _offset ); + // The internal per-controller value updating function + virtual void updateValueBuffer(); FloatModel m_baseModel; TempoSyncKnobModel m_speedModel; @@ -69,17 +69,19 @@ protected: IntModel m_waveModel; IntModel m_multiplierModel; - int m_duration; - int m_phaseCorrection; - int m_phaseOffset; - + float m_duration; + float m_phaseOffset; + float m_currentPhase; + sample_t (*m_sampleFunction)( const float ); private: SampleBuffer * m_userDefSampleBuffer; protected slots: + void updatePhase(); void updateSampleFunction(); + void updateDuration(); friend class LfoControllerDialog; diff --git a/include/MidiController.h b/include/MidiController.h index 2271e8659..cb0fb6b88 100644 --- a/include/MidiController.h +++ b/include/MidiController.h @@ -22,8 +22,8 @@ * */ -#ifndef _MIDI_CONTROLLER_H -#define _MIDI_CONTROLLER_H +#ifndef MIDI_CONTROLLER_H +#define MIDI_CONTROLLER_H #include @@ -67,13 +67,14 @@ public slots: protected: // The internal per-controller get-value function - virtual float value( int _offset ); + virtual void updateValueBuffer(); MidiPort m_midiPort; float m_lastValue; + float m_previousValue; friend class ControllerConnectionDialog; friend class AutoDetectMidiController; diff --git a/include/PeakController.h b/include/PeakController.h index 89691aa11..a235bcca5 100644 --- a/include/PeakController.h +++ b/include/PeakController.h @@ -22,8 +22,8 @@ * */ -#ifndef _PEAK_CONTROLLER_H -#define _PEAK_CONTROLLER_H +#ifndef PEAK_CONTROLLER_H +#define PEAK_CONTROLLER_H #include @@ -65,7 +65,7 @@ public slots: protected: // The internal per-controller get-value function - virtual float value( int _offset ); + virtual void updateValueBuffer(); PeakControllerEffect * m_peakEffect; diff --git a/include/ValueBuffer.h b/include/ValueBuffer.h new file mode 100644 index 000000000..b82844bca --- /dev/null +++ b/include/ValueBuffer.h @@ -0,0 +1,137 @@ +/* + * ValueBuffer.h - a container class for passing buffers of model values around + * + * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2008-2014 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef VALUE_BUFFER_H +#define VALUE_BUFFER_H + +#include "interpolation.h" +#include + +class ValueBuffer +{ +public: + ValueBuffer() + { + m_values = NULL; + m_length = 0; + } + + ValueBuffer( int length ) + { + m_values = new float[length]; + m_length = length; + } + + ValueBuffer( float * values, int length ) + { + m_values = new float[length]; + m_length = length; + memcpy( m_values, values, sizeof(float) * length ); + } + + ValueBuffer( float value, int length ) + { + m_values = new float[length]; + m_length = length; + for( int i = 0; i < length; i++ ) + { + m_values[i] = value; + } + } + + virtual ~ValueBuffer() + { + delete[] m_values; + } + + void clear() + { + delete[] m_values; + m_values = NULL; + m_length = 0; + } + + void fill( float value ) + { + for( int i = 0; i < m_length; i++ ) + { + m_values[i] = value; + } + } + + float value( int offset ) const + { + return m_values[ offset % m_length ]; + } + + void setValue( int offset, float value ) + { + m_values[ offset % m_length ] = value; + } + + float * values() const + { + return m_values; + } + + void setValues( float * values ) + { + m_values = values; + } + + int length() const + { + return m_length; + } + + void setLength( const int length ) + { + m_length = length; + } + + void interpolate( float start, float end ) + { + float f = 0.0f; + const float fstep = 1.0f / static_cast( m_length ); + for( int i = 0; i < m_length; i++ ) + { + f += fstep; + m_values[i] = linearInterpolate( start, end, f ); + } + } + + static ValueBuffer interpolatedBuffer( float start, float end, int length ) + { + ValueBuffer vb = ValueBuffer( length ); + vb.interpolate( start, end ); + return vb; + } + +private: + float * m_values; + int m_length; +}; + +#endif diff --git a/include/song.h b/include/song.h index dc58a8d94..e5f938712 100644 --- a/include/song.h +++ b/include/song.h @@ -22,8 +22,8 @@ * */ -#ifndef _SONG_H -#define _SONG_H +#ifndef SONG_H +#define SONG_H #include #include @@ -131,6 +131,10 @@ public: { return currentTick(); } + inline f_cnt_t getFrames() const + { + return currentFrame(); + } inline bool isTempoAutomated() { return m_tempoModel.isAutomated(); @@ -310,6 +314,12 @@ private: { return m_playPos[m_playMode].getTicks(); } + + inline f_cnt_t currentFrame() const + { + return m_playPos[m_playMode].getTicks() * engine::framesPerTick() + m_playPos[m_playMode].currentFrame(); + } + void setPlayPos( tick_t _ticks, PlayModes _play_mode ); void saveControllerStates( QDomDocument & _doc, QDomElement & _this ); @@ -362,6 +372,7 @@ private: signals: void projectLoaded(); void playbackStateChanged(); + void playbackPositionChanged(); void lengthChanged( int _tacts ); void tempoChanged( bpm_t _new_bpm ); void timeSignatureChanged( int _old_ticks_per_tact, diff --git a/plugins/Amplifier/Amplifier.cpp b/plugins/Amplifier/Amplifier.cpp index 380fde0ef..b256f7889 100644 --- a/plugins/Amplifier/Amplifier.cpp +++ b/plugins/Amplifier/Amplifier.cpp @@ -74,29 +74,62 @@ bool AmplifierEffect::processAudioBuffer( sampleFrame* buf, const fpp_t frames ) double outSum = 0.0; 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; + for( fpp_t f = 0; f < frames; ++f ) { // qDebug( "offset %d, value %f", f, m_ampControls.m_volumeModel.value( f ) ); sample_t s[2] = { buf[f][0], buf[f][1] }; - // convert vol/pan values to left/right values - const float left1 = m_ampControls.m_volumeModel.value( f ) * - ( m_ampControls.m_panModel.value( f ) <= 0 - ? 1.0 - : 1.0 - m_ampControls.m_panModel.value( f ) / 100.0 ); - const float right1 = m_ampControls.m_volumeModel.value( f ) * - ( m_ampControls.m_panModel.value( f ) >= 0 - ? 1.0 - : 1.0 + m_ampControls.m_panModel.value( f ) / 100.0 ); + // vol knob + if( volBuf ) + { + s[0] *= volBuf->values()[ f ] * 0.01f; + s[1] *= volBuf->values()[ f ] * 0.01f; + } + else + { + s[0] *= m_ampControls.m_volumeModel.value() * 0.01f; + s[1] *= m_ampControls.m_volumeModel.value() * 0.01f; + } - // first stage amplification - s[0] *= ( left1 / 100.0 ); - s[1] *= ( right1 / 100.0 ); + // convert pan values to left/right values + const float pan = panBuf + ? panBuf->values()[ f ] + : m_ampControls.m_panModel.value(); + const float left1 = pan <= 0 + ? 1.0 + : 1.0 - m_ampControls.m_panModel.value( f ) * 0.01f; + const float right1 = pan >= 0 + ? 1.0 + : 1.0 + m_ampControls.m_panModel.value( ) * 0.01f; // second stage amplification - s[0] *= ( m_ampControls.m_leftModel.value( f ) / 100.0 ); - s[1] *= ( m_ampControls.m_rightModel.value( f ) / 100.0 ); + const float left2 = leftBuf + ? leftBuf->values()[ f ] + : m_ampControls.m_leftModel.value(); + const float right2 = rightBuf + ? rightBuf->values()[ f ] + : m_ampControls.m_rightModel.value(); + + s[0] *= left1 * left2 * 0.01; + s[1] *= right1 * right2 * 0.01; buf[f][0] = d * buf[f][0] + w * s[0]; buf[f][1] = d * buf[f][1] + w * s[1]; diff --git a/plugins/Amplifier/Amplifier.h b/plugins/Amplifier/Amplifier.h index e9a78e9d5..6895d9c67 100644 --- a/plugins/Amplifier/Amplifier.h +++ b/plugins/Amplifier/Amplifier.h @@ -29,7 +29,7 @@ #include "Effect.h" #include "AmplifierControls.h" - +#include "ValueBuffer.h" class AmplifierEffect : public Effect { diff --git a/plugins/peak_controller_effect/peak_controller_effect.cpp b/plugins/peak_controller_effect/peak_controller_effect.cpp index 0d8238262..2689db00c 100644 --- a/plugins/peak_controller_effect/peak_controller_effect.cpp +++ b/plugins/peak_controller_effect/peak_controller_effect.cpp @@ -63,6 +63,7 @@ PeakControllerEffect::PeakControllerEffect( m_effectId( rand() ), m_peakControls( this ), m_lastSample( 0 ), + m_previousSample( 0 ), m_lastRMS( -1 ), m_lastRMSavail(false), m_autoController( NULL ) @@ -89,11 +90,15 @@ namespace helpers { //! returns 1.0f if val > 0.0f, -1.0 else - inline float sign(float val) { return -1.0f + 2.0f * (val > 0.0f); } + inline float sign( float val ) + { + return -1.0f + 2.0f * ( val > 0.0f ); + } //! if val >= 0.0f, returns sqrtf(val), else: -sqrtf(-val) - inline float sqrt_neg(float val) { - return sqrtf(fabs(val)) * helpers::sign(val); + inline float sqrt_neg( float val ) + { + return sqrtf( fabs( val ) ) * helpers::sign( val ); } } @@ -113,7 +118,7 @@ bool PeakControllerEffect::processAudioBuffer( sampleFrame * _buf, // RMS: double sum = 0; - if(c.m_absModel.value()) + if( c.m_absModel.value() ) { for( int i = 0; i < _frames; ++i ) { @@ -127,8 +132,8 @@ bool PeakControllerEffect::processAudioBuffer( sampleFrame * _buf, { // the value is absolute because of squaring, // so we need to correct it - sum += _buf[i][0]*_buf[i][0]*helpers::sign(_buf[i][0]) - + _buf[i][1]*_buf[i][1]*helpers::sign(_buf[i][1]); + sum += _buf[i][0] * _buf[i][0] * helpers::sign( _buf[i][0] ) + + _buf[i][1] * _buf[i][1] * helpers::sign( _buf[i][1] ); } } @@ -157,6 +162,7 @@ bool PeakControllerEffect::processAudioBuffer( sampleFrame * _buf, curRMS = (1-a)*curRMS + a*m_lastRMS; const float amount = c.m_amountModel.value() * c.m_amountMultModel.value(); + m_previousSample = m_lastSample; m_lastSample = c.m_baseModel.value() + amount*curRMS; m_lastRMS = curRMS; diff --git a/plugins/peak_controller_effect/peak_controller_effect.h b/plugins/peak_controller_effect/peak_controller_effect.h index acbe58e2f..ad05fb645 100644 --- a/plugins/peak_controller_effect/peak_controller_effect.h +++ b/plugins/peak_controller_effect/peak_controller_effect.h @@ -23,8 +23,8 @@ */ -#ifndef _PEAK_CONTROLLER_EFFECT_H -#define _PEAK_CONTROLLER_EFFECT_H +#ifndef PEAK_CONTROLLER_EFFECT_H +#define PEAK_CONTROLLER_EFFECT_H #include "Effect.h" #include "peak_controller_effect_controls.h" @@ -48,6 +48,11 @@ public: return m_lastSample; } + float previousSample() + { + return m_previousSample; + } + PeakController * controller() { return m_autoController; @@ -59,6 +64,7 @@ private: PeakControllerEffectControls m_peakControls; float m_lastSample; + float m_previousSample; float m_lastRMS; bool m_lastRMSavail; diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index b11374e9a..81649be87 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -48,8 +48,11 @@ AutomatableModel::AutomatableModel( DataType type, m_range( max - min ), m_centerValue( m_minValue ), m_setValueDepth( 0 ), + m_hasStrictStepSize( false ), m_hasLinkedModels( false ), - m_controllerConnection( NULL ) + m_controllerConnection( NULL ), + m_valueBuffer( static_cast( engine::mixer()->framesPerPeriod() ) ) + { setInitValue( val ); } @@ -69,6 +72,8 @@ AutomatableModel::~AutomatableModel() { delete m_controllerConnection; } + + m_valueBuffer.clear(); emit destroyed( id() ); } @@ -81,7 +86,38 @@ 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 ) @@ -215,10 +251,11 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString& void AutomatableModel::setValue( const float value ) { + m_oldValue = m_value; ++m_setValueDepth; const float old_val = m_value; - m_value = fittedValue( value ); + m_value = fittedValue( value, true ); if( old_val != m_value ) { // add changes to history so user can undo it @@ -290,10 +327,11 @@ void AutomatableModel::roundAt( T& value, const T& where ) const void AutomatableModel::setAutomatedValue( const float value ) { + m_oldValue = m_value; ++m_setValueDepth; const float oldValue = m_value; - const float scaled_value = + const float scaledValue = ( m_scaleType == Linear ) ? value : logToLinearScale( @@ -301,7 +339,7 @@ void AutomatableModel::setAutomatedValue( const float value ) (value - minValue()) / maxValue() ); - m_value = fittedValue( scaled_value ); + m_value = fittedValue( scaledValue ); if( oldValue != m_value ) { @@ -363,11 +401,11 @@ void AutomatableModel::setStep( const float step ) -float AutomatableModel::fittedValue( float value ) const +float AutomatableModel::fittedValue( float value, bool forceStep ) const { value = tLimit( value, m_minValue, m_maxValue ); - if( m_step != 0 ) + if( m_step != 0 && ( m_hasStrictStepSize || forceStep ) ) { value = nearbyintf( value / m_step ) * m_step; } @@ -488,7 +526,7 @@ float AutomatableModel::controllerValue( int frameOffset ) const "lacks implementation for a scale type"); break; } - if( typeInfo::isEqual( m_step, 1 ) ) + if( typeInfo::isEqual( m_step, 1 ) && m_hasStrictStepSize ) { return qRound( v ); } @@ -505,6 +543,67 @@ float AutomatableModel::controllerValue( int frameOffset ) const } +ValueBuffer * AutomatableModel::valueBuffer() +{ + ValueBuffer * vb; + if( m_controllerConnection && m_controllerConnection->getController()->isSampleExact() ) + { + vb = m_controllerConnection->valueBuffer(); + if( vb ) + { + float * values = vb->values(); + float * nvalues = m_valueBuffer.values(); + switch( m_scaleType ) + { + case Linear: + for( int i = 0; i < m_valueBuffer.length(); i++ ) + { + nvalues[i] = minValue() + ( range() * values[i] ); + } + break; + case Logarithmic: + for( int i = 0; i < m_valueBuffer.length(); i++ ) + { + nvalues[i] = logToLinearScale( values[i] ); + } + break; + default: + qFatal("AutomatableModel::valueBuffer() " + "lacks implementation for a scale type"); + break; + } + return &m_valueBuffer; + } + } + AutomatableModel* lm = NULL; + if( m_hasLinkedModels ) + { + 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++ ) + { + nvalues[i] = fittedValue( values[i], false ); + } + return &m_valueBuffer; + } + + if( m_oldValue != m_value ) + { + m_valueBuffer.interpolate( m_oldValue, m_value ); + m_oldValue = m_value; + 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; +} void AutomatableModel::unlinkControllerConnection() @@ -604,14 +703,14 @@ float AutomatableModel::globalAutomationValueAt( const MidiTime& time ) { // scale/fit the value appropriately and return it const float value = latestPattern->valueAt( time - latestPattern->startPosition() ); - const float scaled_value = + const float scaledValue = ( m_scaleType == Linear ) ? value : logToLinearScale( // fits value into [0,1]: (value - minValue()) / maxValue() ); - return fittedValue( scaled_value ); + return fittedValue( scaledValue ); } // if we still find no pattern, the value at that time is undefined so // just return current value as the best we can do diff --git a/src/core/Controller.cpp b/src/core/Controller.cpp index 4356cb9b3..2618fe588 100644 --- a/src/core/Controller.cpp +++ b/src/core/Controller.cpp @@ -40,7 +40,7 @@ #include "PeakController.h" -unsigned int Controller::s_frames = 0; +long Controller::s_periods = 0; QVector Controller::s_controllers; @@ -49,6 +49,8 @@ Controller::Controller( ControllerTypes _type, Model * _parent, const QString & _display_name ) : Model( _parent, _display_name ), JournallingObject(), + m_valueBuffer( engine::mixer()->framesPerPeriod() ), + m_bufferLastUpdated( -1 ), m_connectionCount( 0 ), m_type( _type ) { @@ -80,6 +82,7 @@ Controller::Controller( ControllerTypes _type, Model * _parent, } } } + updateValueBuffer(); } @@ -97,6 +100,7 @@ Controller::~Controller() engine::getSong()->removeController( this ); } + m_valueBuffer.clear(); // Remove connections by destroyed signal } @@ -115,17 +119,41 @@ float Controller::currentValue( int _offset ) -float Controller::value( int _offset ) +float Controller::value( int offset ) { - return 0.5f; + if( m_bufferLastUpdated != s_periods ) + { + updateValueBuffer(); + } + return m_valueBuffer.values()[ offset ]; } +ValueBuffer * Controller::valueBuffer() +{ + if( m_bufferLastUpdated != s_periods ) + { + updateValueBuffer(); + } + return &m_valueBuffer; +} + + +void Controller::updateValueBuffer() +{ + float * values = m_valueBuffer.values(); + for( int i = 0; i < m_valueBuffer.length(); i++ ) + { + values[i] = 0.5f; + } + m_bufferLastUpdated = s_periods; +} + // Get position in frames unsigned int Controller::runningFrames() { - return s_frames; + return s_periods * engine::mixer()->framesPerPeriod(); } @@ -133,7 +161,7 @@ unsigned int Controller::runningFrames() // Get position in seconds float Controller::runningTime() { - return s_frames / engine::mixer()->processingSampleRate(); + return runningFrames() / engine::mixer()->processingSampleRate(); } @@ -149,7 +177,7 @@ void Controller::triggerFrameCounter() emit s_controllers.at(i)->valueChanged(); } - s_frames += engine::mixer()->framesPerPeriod(); + s_periods ++; //emit s_signaler.triggerValueChanged(); } @@ -157,7 +185,11 @@ void Controller::triggerFrameCounter() void Controller::resetFrameCounter() { - s_frames = 0; + for( int i = 0; i < s_controllers.size(); ++i ) + { + s_controllers.at( i )->m_bufferLastUpdated = 0; + } + s_periods = 0; } diff --git a/src/core/LfoController.cpp b/src/core/LfoController.cpp index 30f2b7322..adf2b536a 100644 --- a/src/core/LfoController.cpp +++ b/src/core/LfoController.cpp @@ -34,6 +34,7 @@ #include "Mixer.h" #include "LfoController.h" #include "ControllerDialog.h" +#include "lmms_math.h" //const float TWO_PI = 6.28318531f; @@ -47,14 +48,28 @@ LfoController::LfoController( Model * _parent ) : this, tr( "Oscillator waveform" ) ), m_multiplierModel( 0, 0, 2, this, tr( "Frequency Multiplier" ) ), m_duration( 1000 ), - m_phaseCorrection( 0 ), - m_phaseOffset( 0 ), + m_phaseOffset( 0 ), + m_currentPhase( 0 ), m_sampleFunction( &Oscillator::sinSample ), m_userDefSampleBuffer( new SampleBuffer ) { - + setSampleExact( true ); connect( &m_waveModel, SIGNAL( dataChanged() ), this, SLOT( updateSampleFunction() ) ); + + connect( &m_speedModel, SIGNAL( dataChanged() ), + this, SLOT( updateDuration() ) ); + connect( &m_multiplierModel, SIGNAL( dataChanged() ), + this, SLOT( updateDuration() ) ); + connect( engine::mixer(), SIGNAL( sampleRateChanged() ), + this, SLOT( updateDuration() ) ); + + connect( engine::getSong(), SIGNAL( playbackStateChanged() ), + this, SLOT( updatePhase() ) ); + connect( engine::getSong(), SIGNAL( playbackPositionChanged() ), + this, SLOT( updatePhase() ) ); + + updateDuration(); } @@ -72,84 +87,65 @@ LfoController::~LfoController() } - - -// This code took forever to get right. It can -// definately be optimized. -// The code should probably be integrated with the oscillator class. But I -// don't know how to use oscillator because it is so confusing -float LfoController::value( int _offset ) +void LfoController::updateValueBuffer() { - int frame = runningFrames() + _offset + m_phaseCorrection; + m_phaseOffset = m_phaseModel.value() / 360.0; + float * values = m_valueBuffer.values(); + float phase = m_currentPhase + m_phaseOffset; - //If the song is playing, sync the value with the time of the song. - if( engine::getSong()->isPlaying() || engine::getSong()->isExporting() ) + // roll phase up until we're in sync with period counter + m_bufferLastUpdated++; + if( m_bufferLastUpdated < s_periods ) { - // The new duration in frames - // (Samples/Second) / (periods/second) = (Samples/cycle) - float newDurationF = - engine::mixer()->processingSampleRate() * - m_speedModel.value(); - - switch(m_multiplierModel.value() ) - { - case 1: - newDurationF /= 100.0; - break; - - case 2: - newDurationF *= 100.0; - break; - - default: - break; - } - - m_phaseOffset = qRound( - m_phaseModel.value() * newDurationF / 360.0 ); - - - int newDuration = static_cast( newDurationF ); - m_phaseCorrection = static_cast(engine::getSong()->getTicks()*engine::framesPerTick())%newDuration - + m_phaseOffset; - - // re-run the first calculation again - frame = m_phaseCorrection + _offset; + int diff = s_periods - m_bufferLastUpdated; + phase += static_cast( engine::framesPerTick() * diff ) / m_duration; + m_bufferLastUpdated += diff; } - // speedModel 0..1 fast..slow 0ms..20000ms - // duration m_duration - // - // frames / (20seconds of frames) - float sampleFrame = float( frame+m_phaseOffset ) / - (engine::mixer()->processingSampleRate() * m_speedModel.value() ); + for( int i = 0; i < m_valueBuffer.length(); i++ ) + { + const float currentSample = m_sampleFunction != NULL + ? m_sampleFunction( phase ) + : m_userDefSampleBuffer->userWaveSample( phase ); + + values[i] = m_baseModel.value() + ( m_amountModel.value() * currentSample / 2.0f ); + + phase += 1.0 / m_duration; + } + + m_currentPhase = absFraction( phase - m_phaseOffset ); +} + + +void LfoController::updatePhase() +{ + m_currentPhase = ( engine::getSong()->getFrames() ) / m_duration; + m_bufferLastUpdated = s_periods - 1; +} + + +void LfoController::updateDuration() +{ + float newDurationF = engine::mixer()->processingSampleRate() * m_speedModel.value(); switch(m_multiplierModel.value() ) { case 1: - sampleFrame *= 100.0; + newDurationF /= 100.0; break; case 2: - sampleFrame /= 100.0; + newDurationF *= 100.0; break; default: break; } - - // 44100 frames/sec - return m_baseModel.value() + ( m_amountModel.value() * - ( m_sampleFunction != NULL ? - m_sampleFunction(sampleFrame): - m_userDefSampleBuffer->userWaveSample(sampleFrame) ) - / 2.0f ); + + m_duration = newDurationF; } - - - void LfoController::updateSampleFunction() { switch( m_waveModel.value() ) diff --git a/src/core/PeakController.cpp b/src/core/PeakController.cpp index 508174897..e5f239d7d 100644 --- a/src/core/PeakController.cpp +++ b/src/core/PeakController.cpp @@ -51,6 +51,7 @@ PeakController::PeakController( Model * _parent, Controller( Controller::PeakController, _parent, tr( "Peak Controller" ) ), m_peakEffect( _peak_effect ) { + setSampleExact( true ); if( m_peakEffect ) { connect( m_peakEffect, SIGNAL( destroyed( ) ), @@ -74,18 +75,20 @@ PeakController::~PeakController() } - -float PeakController::value( int _offset ) +void PeakController::updateValueBuffer() { if( m_peakEffect ) { - return m_peakEffect->lastSample(); + m_valueBuffer.interpolate( m_peakEffect->previousSample(), m_peakEffect->lastSample() ); } - return( 0 ); + else + { + m_valueBuffer.fill( 0 ); + } + m_bufferLastUpdated = s_periods; } - void PeakController::handleDestroyedEffect( ) { // possible race condition... diff --git a/src/core/midi/MidiController.cpp b/src/core/midi/MidiController.cpp index 5e65b5cd0..b6e3f44dc 100644 --- a/src/core/midi/MidiController.cpp +++ b/src/core/midi/MidiController.cpp @@ -41,6 +41,7 @@ MidiController::MidiController( Model * _parent ) : engine::mixer()->midiClient(), this, this, MidiPort::Input ), m_lastValue( 0.0f ) { + setSampleExact( true ); connect( &m_midiPort, SIGNAL( modeChanged() ), this, SLOT( updateName() ) ); } @@ -55,14 +56,21 @@ MidiController::~MidiController() -float MidiController::value( int _offset ) +void MidiController::updateValueBuffer() { - return m_lastValue; + if( m_previousValue != m_lastValue ) + { + m_valueBuffer.interpolate( m_previousValue, m_lastValue ); + m_previousValue = m_lastValue; + } + else + { + m_valueBuffer.fill( m_lastValue ); + } + m_bufferLastUpdated = s_periods; } - - void MidiController::updateName() { setName( QString("MIDI ch%1 ctrl%2"). @@ -86,6 +94,7 @@ void MidiController::processInEvent( const MidiEvent& event, const MidiTime& tim m_midiPort.inputChannel() == 0 ) ) { unsigned char val = event.controllerValue(); + m_previousValue = m_lastValue; m_lastValue = (float)( val ) / 127.0f; emit valueChanged(); } diff --git a/src/core/song.cpp b/src/core/song.cpp index 5f3719a8a..735bfdba4 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -523,6 +523,12 @@ void song::setPlayPos( tick_t _ticks, PlayModes _play_mode ) m_elapsedMilliSeconds += (((( _ticks - m_playPos[_play_mode].getTicks()))*60*1000/48)/getTempo()); m_playPos[_play_mode].setTicks( _ticks ); m_playPos[_play_mode].setCurrentFrame( 0.0f ); + +// send a signal if playposition changes during playback + if( isPlaying() ) + { + emit playbackPositionChanged(); + } }