diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 5b51f8184..95a4f635d 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 @@ 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 @@ -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 ); diff --git a/include/Mixer.h b/include/Mixer.h index 7007e1801..c10bbffbc 100644 --- a/include/Mixer.h +++ b/include/Mixer.h @@ -312,7 +312,6 @@ public: // audio-buffer-mgm void bufferToPort( const sampleFrame * _buf, const fpp_t _frames, - const f_cnt_t _offset, stereoVolumeVector _volume_vector, AudioPort * _port ); diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index c564b1fc8..fc7184525 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -74,6 +74,16 @@ public: { return m_midiChannel; } + + /*! convenience function that returns offset for the first period and zero otherwise, + used by instruments to handle the offset: instruments have to check this property and + add the correct number of empty frames in the beginning of the period */ + f_cnt_t noteOffset() const + { + return m_totalFramesPlayed == 0 + ? offset() + : 0; + } const float& frequency() const { @@ -94,7 +104,7 @@ public: /*! Returns whether playback of note is finished and thus handle can be deleted */ virtual bool isFinished() const { - return m_released && framesLeft() <= 0 && m_scheduledNoteOff < 0; + return m_released && framesLeft() <= 0; } /*! Returns number of frames left for playback */ @@ -264,7 +274,6 @@ private: // played after release f_cnt_t m_releaseFramesDone; // number of frames done after // release of note - f_cnt_t m_scheduledNoteOff; // variable for scheduling noteoff at next period NotePlayHandleList m_subNotes; // used for chords and arpeggios volatile bool m_released; // indicates whether note is released bool m_hasParent; diff --git a/include/PlayHandle.h b/include/PlayHandle.h index a9a07f113..152f82989 100644 --- a/include/PlayHandle.h +++ b/include/PlayHandle.h @@ -88,8 +88,8 @@ public: virtual void play( sampleFrame* buffer ) = 0; virtual bool isFinished( void ) const = 0; - // returns how many frames this play-handle is aligned ahead, i.e. - // at which position it is inserted in the according buffer + // returns the frameoffset at the start of the playhandle, + // ie. how many empty frames should be inserted at the start of the first period f_cnt_t offset() const { return m_offset; diff --git a/include/SamplePlayHandle.h b/include/SamplePlayHandle.h index a2ceae7c0..59537ac91 100644 --- a/include/SamplePlayHandle.h +++ b/include/SamplePlayHandle.h @@ -22,8 +22,8 @@ * */ -#ifndef _SAMPLE_PLAY_HANDLE_H -#define _SAMPLE_PLAY_HANDLE_H +#ifndef SAMPLE_PLAY_HANDLE_H +#define SAMPLE_PLAY_HANDLE_H #include "Mixer.h" #include "SampleBuffer.h" @@ -49,7 +49,7 @@ public: } - virtual void play( sampleFrame * _working_buffer ); + virtual void play( sampleFrame * buffer ); virtual bool isFinished() const; virtual bool isFromTrack( const track * _track ) const; diff --git a/plugins/Amplifier/Amplifier.cpp b/plugins/Amplifier/Amplifier.cpp index 3ba9d224c..f628b7be7 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/plugins/audio_file_processor/audio_file_processor.cpp b/plugins/audio_file_processor/audio_file_processor.cpp index 726665811..7a13f2887 100644 --- a/plugins/audio_file_processor/audio_file_processor.cpp +++ b/plugins/audio_file_processor/audio_file_processor.cpp @@ -119,6 +119,7 @@ void audioFileProcessor::playNote( NotePlayHandle * _n, sampleFrame * _working_buffer ) { const fpp_t frames = _n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = _n->noteOffset(); // Magic key - a frequency < 20 (say, the bottom piano note if using // a A4 base tuning) restarts the start point. The note is not actually @@ -165,14 +166,14 @@ void audioFileProcessor::playNote( NotePlayHandle * _n, if( ! _n->isFinished() ) { - if( m_sampleBuffer.play( _working_buffer, + if( m_sampleBuffer.play( _working_buffer + offset, (handleState *)_n->m_pluginData, frames, _n->frequency(), static_cast( m_loopModel.value() ) ) ) { applyRelease( _working_buffer, _n ); instrumentTrack()->processAudioBuffer( _working_buffer, - frames,_n ); + frames + offset, _n ); emit isPlaying( ((handleState *)_n->m_pluginData)->frameIndex() ); } diff --git a/plugins/bit_invader/bit_invader.cpp b/plugins/bit_invader/bit_invader.cpp index 682f470a6..95f06f53a 100644 --- a/plugins/bit_invader/bit_invader.cpp +++ b/plugins/bit_invader/bit_invader.cpp @@ -282,9 +282,10 @@ void bitInvader::playNote( NotePlayHandle * _n, } const fpp_t frames = _n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = _n->noteOffset(); bSynth * ps = static_cast( _n->m_pluginData ); - for( fpp_t frame = 0; frame < frames; ++frame ) + for( fpp_t frame = offset; frame < frames + offset; ++frame ) { const sample_t cur = ps->nextStringSample(); for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl ) @@ -295,7 +296,7 @@ void bitInvader::playNote( NotePlayHandle * _n, applyRelease( _working_buffer, _n ); - instrumentTrack()->processAudioBuffer( _working_buffer, frames, _n ); + instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/kicker/kicker.cpp b/plugins/kicker/kicker.cpp index 0a653a75f..96e0ff4df 100644 --- a/plugins/kicker/kicker.cpp +++ b/plugins/kicker/kicker.cpp @@ -163,6 +163,8 @@ typedef KickerOsc > SweepOsc; void kickerInstrument::playNote( NotePlayHandle * _n, sampleFrame * _working_buffer ) { + const fpp_t frames = _n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = _n->noteOffset(); const float decfr = m_decayModel.value() * engine::mixer()->processingSampleRate() / 1000.0f; const f_cnt_t tfp = _n->totalFramesPlayed(); @@ -187,10 +189,8 @@ void kickerInstrument::playNote( NotePlayHandle * _n, _n->noteOff(); } - const fpp_t frames = _n->framesLeftForCurrentPeriod(); - SweepOsc * so = static_cast( _n->m_pluginData ); - so->update( _working_buffer, frames, engine::mixer()->processingSampleRate() ); + so->update( _working_buffer + offset, frames, engine::mixer()->processingSampleRate() ); if( _n->isReleased() ) { @@ -199,12 +199,12 @@ void kickerInstrument::playNote( NotePlayHandle * _n, for( fpp_t f = 0; f < frames; ++f ) { const float fac = ( done+f < desired ) ? ( 1.0f - ( ( done+f ) / desired ) ) : 0; - _working_buffer[f][0] *= fac; - _working_buffer[f][1] *= fac; + _working_buffer[f+offset][0] *= fac; + _working_buffer[f+offset][1] *= fac; } } - instrumentTrack()->processAudioBuffer( _working_buffer, frames, _n ); + instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/monstro/Monstro.cpp b/plugins/monstro/Monstro.cpp index f3c2a86ac..c1af10cd4 100644 --- a/plugins/monstro/Monstro.cpp +++ b/plugins/monstro/Monstro.cpp @@ -1245,21 +1245,22 @@ MonstroInstrument::~MonstroInstrument() void MonstroInstrument::playNote( NotePlayHandle * _n, sampleFrame * _working_buffer ) { + const fpp_t frames = _n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = _n->offset(); + if ( _n->totalFramesPlayed() == 0 || _n->m_pluginData == NULL ) { const sample_rate_t samplerate = m_samplerate; _n->m_pluginData = new MonstroSynth( this, _n, samplerate, m_fpp ); } - const fpp_t frames = _n->framesLeftForCurrentPeriod(); - MonstroSynth * ms = static_cast( _n->m_pluginData ); - ms->renderOutput( frames, _working_buffer ); + ms->renderOutput( frames, _working_buffer + offset ); //applyRelease( _working_buffer, _n ); // we have our own release - instrumentTrack()->processAudioBuffer( _working_buffer, frames, _n ); + instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } void MonstroInstrument::deleteNotePluginData( NotePlayHandle * _n ) diff --git a/plugins/nes/Nes.cpp b/plugins/nes/Nes.cpp index 1efa56dc9..8f5de4834 100644 --- a/plugins/nes/Nes.cpp +++ b/plugins/nes/Nes.cpp @@ -556,21 +556,22 @@ NesInstrument::~NesInstrument() void NesInstrument::playNote( NotePlayHandle * n, sampleFrame * workingBuffer ) { + const fpp_t frames = n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = n->noteOffset(); + if ( n->totalFramesPlayed() == 0 || n->m_pluginData == NULL ) { NesObject * nes = new NesObject( this, engine::mixer()->processingSampleRate(), n, engine::mixer()->framesPerPeriod() ); n->m_pluginData = nes; } - const fpp_t frames = n->framesLeftForCurrentPeriod(); - NesObject * nes = static_cast( n->m_pluginData ); - nes->renderOutput( workingBuffer, frames ); + nes->renderOutput( workingBuffer + offset, frames ); applyRelease( workingBuffer, n ); - instrumentTrack()->processAudioBuffer( workingBuffer, frames, n ); + instrumentTrack()->processAudioBuffer( workingBuffer, frames + offset, n ); } diff --git a/plugins/organic/organic.cpp b/plugins/organic/organic.cpp index a023afa69..1f4bb2996 100644 --- a/plugins/organic/organic.cpp +++ b/plugins/organic/organic.cpp @@ -227,6 +227,9 @@ QString organicInstrument::nodeName() const void organicInstrument::playNote( NotePlayHandle * _n, sampleFrame * _working_buffer ) { + const fpp_t frames = _n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = _n->noteOffset(); + if( _n->totalFramesPlayed() == 0 || _n->m_pluginData == NULL ) { Oscillator * oscs_l[m_numOscillators]; @@ -296,10 +299,8 @@ void organicInstrument::playNote( NotePlayHandle * _n, Oscillator * osc_l = static_cast( _n->m_pluginData )->oscLeft; Oscillator * osc_r = static_cast( _n->m_pluginData)->oscRight; - const fpp_t frames = _n->framesLeftForCurrentPeriod(); - - osc_l->update( _working_buffer, frames, 0 ); - osc_r->update( _working_buffer, frames, 1 ); + osc_l->update( _working_buffer + offset, frames, 0 ); + osc_r->update( _working_buffer + offset, frames, 1 ); // -- fx section -- @@ -317,7 +318,7 @@ void organicInstrument::playNote( NotePlayHandle * _n, // -- -- - instrumentTrack()->processAudioBuffer( _working_buffer, frames, _n ); + instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/papu/papu_instrument.cpp b/plugins/papu/papu_instrument.cpp index 510042a4c..50ed73f7d 100644 --- a/plugins/papu/papu_instrument.cpp +++ b/plugins/papu/papu_instrument.cpp @@ -238,6 +238,7 @@ void papuInstrument::playNote( NotePlayHandle * _n, const f_cnt_t tfp = _n->totalFramesPlayed(); const int samplerate = engine::mixer()->processingSampleRate(); const fpp_t frames = _n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = _n->noteOffset(); int data = 0; int freq = _n->frequency(); @@ -400,12 +401,12 @@ void papuInstrument::playNote( NotePlayHandle * _n, for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) { sample_t s = float(buf[frame*2+ch])/32768.0; - _working_buffer[frames-framesleft+frame][ch] = s; + _working_buffer[frames-framesleft+frame+offset][ch] = s; } } framesleft -= count; } - instrumentTrack()->processAudioBuffer( _working_buffer, frames, _n ); + instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/patman/patman.cpp b/plugins/patman/patman.cpp index 58cf987bc..9df2f1d32 100644 --- a/plugins/patman/patman.cpp +++ b/plugins/patman/patman.cpp @@ -138,6 +138,7 @@ void patmanInstrument::playNote( NotePlayHandle * _n, } const fpp_t frames = _n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = _n->noteOffset(); if( !_n->m_pluginData ) { @@ -148,12 +149,12 @@ void patmanInstrument::playNote( NotePlayHandle * _n, float play_freq = hdata->tuned ? _n->frequency() : hdata->sample->frequency(); - if( hdata->sample->play( _working_buffer, hdata->state, frames, + if( hdata->sample->play( _working_buffer + offset, hdata->state, frames, play_freq, m_loopedModel.value() ? SampleBuffer::LoopOn : SampleBuffer::LoopOff ) ) { applyRelease( _working_buffer, _n ); instrumentTrack()->processAudioBuffer( _working_buffer, - frames, _n ); + frames + offset, _n ); } } diff --git a/plugins/sfxr/sfxr.cpp b/plugins/sfxr/sfxr.cpp index 538098360..40182b218 100644 --- a/plugins/sfxr/sfxr.cpp +++ b/plugins/sfxr/sfxr.cpp @@ -454,6 +454,7 @@ void sfxrInstrument::playNote( NotePlayHandle * _n, sampleFrame * _working_buffe float currentSampleRate = engine::mixer()->processingSampleRate(); fpp_t frameNum = _n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = _n->noteOffset(); if ( _n->totalFramesPlayed() == 0 || _n->m_pluginData == NULL ) { _n->m_pluginData = new SfxrSynth( this ); @@ -477,7 +478,7 @@ void sfxrInstrument::playNote( NotePlayHandle * _n, sampleFrame * _working_buffe { for( ch_cnt_t j=0; jprocessAudioBuffer( _working_buffer, frameNum, _n ); + instrumentTrack()->processAudioBuffer( _working_buffer, frameNum + offset, _n ); } diff --git a/plugins/sid/sid_instrument.cpp b/plugins/sid/sid_instrument.cpp index 637982c80..dd6cd7af5 100644 --- a/plugins/sid/sid_instrument.cpp +++ b/plugins/sid/sid_instrument.cpp @@ -317,6 +317,7 @@ void sidInstrument::playNote( NotePlayHandle * _n, _n->m_pluginData = sid; } const fpp_t frames = _n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = _n->noteOffset(); cSID *sid = static_cast( _n->m_pluginData ); int delta_t = clockrate * frames / samplerate + 4; @@ -430,11 +431,11 @@ void sidInstrument::playNote( NotePlayHandle * _n, sample_t s = float(buf[frame])/32768.0; for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) { - _working_buffer[frame][ch] = s; + _working_buffer[frame+offset][ch] = s; } } - instrumentTrack()->processAudioBuffer( _working_buffer, frames, _n ); + instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/stk/mallets/mallets.cpp b/plugins/stk/mallets/mallets.cpp index c2d91f0c9..cd20ccd7f 100644 --- a/plugins/stk/mallets/mallets.cpp +++ b/plugins/stk/mallets/mallets.cpp @@ -265,6 +265,7 @@ void malletsInstrument::playNote( NotePlayHandle * _n, } const fpp_t frames = _n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = _n->noteOffset(); malletsSynth * ps = static_cast( _n->m_pluginData ); ps->setFrequency( freq ); @@ -274,7 +275,7 @@ void malletsInstrument::playNote( NotePlayHandle * _n, { add_scale = static_cast( m_strikeModel.value() ) * freq * 2.5f; } - for( fpp_t frame = 0; frame < frames; ++frame ) + for( fpp_t frame = offset; frame < frames + offset; ++frame ) { _working_buffer[frame][0] = ps->nextSampleLeft() * ( m_scalers[m_presetsModel.value()] + add_scale ); @@ -282,7 +283,7 @@ void malletsInstrument::playNote( NotePlayHandle * _n, ( m_scalers[m_presetsModel.value()] + add_scale ); } - instrumentTrack()->processAudioBuffer( _working_buffer, frames, _n ); + instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/triple_oscillator/TripleOscillator.cpp b/plugins/triple_oscillator/TripleOscillator.cpp index 50bce3ecc..4ce5ee4d0 100644 --- a/plugins/triple_oscillator/TripleOscillator.cpp +++ b/plugins/triple_oscillator/TripleOscillator.cpp @@ -358,13 +358,14 @@ void TripleOscillator::playNote( NotePlayHandle * _n, Oscillator * osc_r = static_cast( _n->m_pluginData )->oscRight; const fpp_t frames = _n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = _n->noteOffset(); - osc_l->update( _working_buffer, frames, 0 ); - osc_r->update( _working_buffer, frames, 1 ); + osc_l->update( _working_buffer + offset, frames, 0 ); + osc_r->update( _working_buffer + offset, frames, 1 ); applyRelease( _working_buffer, _n ); - instrumentTrack()->processAudioBuffer( _working_buffer, frames, _n ); + instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/vibed/vibed.cpp b/plugins/vibed/vibed.cpp index a4d0b4d36..697812b59 100644 --- a/plugins/vibed/vibed.cpp +++ b/plugins/vibed/vibed.cpp @@ -302,10 +302,11 @@ void vibed::playNote( NotePlayHandle * _n, sampleFrame * _working_buffer ) } const fpp_t frames = _n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = _n->noteOffset(); stringContainer * ps = static_cast( _n->m_pluginData ); - for( fpp_t i = 0; i < frames; ++i ) + for( fpp_t i = offset; i < frames + offset; ++i ) { _working_buffer[i][0] = 0.0f; _working_buffer[i][1] = 0.0f; @@ -324,7 +325,7 @@ void vibed::playNote( NotePlayHandle * _n, sampleFrame * _working_buffer ) } } - instrumentTrack()->processAudioBuffer( _working_buffer, frames, _n ); + instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/watsyn/Watsyn.cpp b/plugins/watsyn/Watsyn.cpp index 72af13771..8f0abef28 100644 --- a/plugins/watsyn/Watsyn.cpp +++ b/plugins/watsyn/Watsyn.cpp @@ -343,6 +343,8 @@ void WatsynInstrument::playNote( NotePlayHandle * _n, } const fpp_t frames = _n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = _n->noteOffset(); + sampleFrame * buffer = _working_buffer + offset; WatsynObject * w = static_cast( _n->m_pluginData ); @@ -424,9 +426,9 @@ void WatsynInstrument::playNote( NotePlayHandle * _n, const float amix = 1.0 - bmix; // mix a/b streams according to mixing knob - _working_buffer[f][0] = ( abuf[f][0] * amix ) + + buffer[f][0] = ( abuf[f][0] * amix ) + ( bbuf[f][0] * bmix ); - _working_buffer[f][1] = ( abuf[f][1] * amix ) + + buffer[f][1] = ( abuf[f][1] * amix ) + ( bbuf[f][1] * bmix ); } } @@ -440,16 +442,16 @@ void WatsynInstrument::playNote( NotePlayHandle * _n, for( fpp_t f=0; f < frames; f++ ) { // mix a/b streams according to mixing knob - _working_buffer[f][0] = ( abuf[f][0] * amix ) + + buffer[f][0] = ( abuf[f][0] * amix ) + ( bbuf[f][0] * bmix ); - _working_buffer[f][1] = ( abuf[f][1] * amix ) + + buffer[f][1] = ( abuf[f][1] * amix ) + ( bbuf[f][1] * bmix ); } } applyRelease( _working_buffer, _n ); - instrumentTrack()->processAudioBuffer( _working_buffer, frames, _n ); + instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index 0effb88c1..8b93cfb9f 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 77c12c13a..86fb442c4 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/InstrumentSoundShaping.cpp b/src/core/InstrumentSoundShaping.cpp index 7e2f59799..629cdfa2a 100644 --- a/src/core/InstrumentSoundShaping.cpp +++ b/src/core/InstrumentSoundShaping.cpp @@ -133,7 +133,7 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer, if( n->isReleased() == false ) { - envReleaseBegin += engine::mixer()->framesPerPeriod(); + envReleaseBegin += frames; } // because of optimizations, there's special code for several cases: diff --git a/src/core/LfoController.cpp b/src/core/LfoController.cpp index adf2b536a..8d563e88f 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 c9fd9b83c..928129271 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; @@ -451,44 +452,40 @@ void Mixer::clear() -void Mixer::bufferToPort( const sampleFrame * _buf, - const fpp_t _frames, - const f_cnt_t _offset, - stereoVolumeVector _vv, - AudioPort * _port ) +void Mixer::bufferToPort( const sampleFrame * buf, + const fpp_t frames, + stereoVolumeVector vv, + AudioPort * port ) { - const int start_frame = _offset % m_framesPerPeriod; - int end_frame = start_frame + _frames; - const int loop1_frame = qMin( end_frame, m_framesPerPeriod ); + const int loop1_frame = qMin( frames, m_framesPerPeriod ); - _port->lockFirstBuffer(); - MixHelpers::addMultipliedStereo( _port->firstBuffer()+start_frame, // dst - _buf, // src - _vv.vol[0], _vv.vol[1], // coeff left/right - loop1_frame - start_frame ); // frame count - _port->unlockFirstBuffer(); + port->lockFirstBuffer(); + MixHelpers::addMultipliedStereo( port->firstBuffer(), // dst + buf, // src + vv.vol[0], vv.vol[1], // coeff left/right + loop1_frame ); // frame count + port->unlockFirstBuffer(); - _port->lockSecondBuffer(); - if( end_frame > m_framesPerPeriod ) + if( frames > m_framesPerPeriod ) { - const int frames_done = m_framesPerPeriod - start_frame; - end_frame -= m_framesPerPeriod; - end_frame = qMin( end_frame, m_framesPerPeriod ); + port->lockSecondBuffer(); + + const fpp_t framesLeft = qMin( frames - m_framesPerPeriod, m_framesPerPeriod ); - MixHelpers::addMultipliedStereo( _port->secondBuffer(), // dst - _buf+frames_done, // src - _vv.vol[0], _vv.vol[1], // coeff left/right - end_frame ); // frame count + MixHelpers::addMultipliedStereo( port->secondBuffer(), // dst + buf + m_framesPerPeriod, // src + vv.vol[0], vv.vol[1], // coeff left/right + framesLeft ); // frame count // we used both buffers so set flags - _port->m_bufferUsage = AudioPort::BothBuffers; + port->m_bufferUsage = AudioPort::BothBuffers; + port->unlockSecondBuffer(); } - else if( _port->m_bufferUsage == AudioPort::NoUsage ) + else if( port->m_bufferUsage == AudioPort::NoUsage ) { // only first buffer touched - _port->m_bufferUsage = AudioPort::FirstBuffer; + port->m_bufferUsage = AudioPort::FirstBuffer; } - _port->unlockSecondBuffer(); } diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index 0a3107acd..ab3bfc3fb 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -61,7 +61,6 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, m_framesBeforeRelease( 0 ), m_releaseFramesToDo( 0 ), m_releaseFramesDone( 0 ), - m_scheduledNoteOff( -1 ), m_released( false ), m_hasParent( parent != NULL ), m_hadChildren( false ), @@ -119,13 +118,6 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, NotePlayHandle::~NotePlayHandle() { noteOff( 0 ); - if( m_scheduledNoteOff >= 0 ) // ensure that scheduled noteoffs get triggered if somehow the nph got destructed prematurely - { - m_instrumentTrack->processOutEvent( - MidiEvent( MidiNoteOff, midiChannel(), midiKey(), 0 ), - MidiTime::fromFrames( m_scheduledNoteOff, engine::framesPerTick() ), - m_scheduledNoteOff ); - } if( hasParent() == false ) { @@ -190,23 +182,20 @@ int NotePlayHandle::midiKey() const void NotePlayHandle::play( sampleFrame * _working_buffer ) { - if( m_scheduledNoteOff >= 0 ) // always trigger scheduled noteoffs, because they're only scheduled if the note is released - { - m_instrumentTrack->processOutEvent( - MidiEvent( MidiNoteOff, midiChannel(), midiKey(), 0 ), - MidiTime::fromFrames( m_scheduledNoteOff, engine::framesPerTick() ), - m_scheduledNoteOff ); - m_scheduledNoteOff = -1; - } - if( m_muted ) { return; } + + // number of frames that can be played this period + f_cnt_t framesThisPeriod = m_totalFramesPlayed == 0 + ? engine::mixer()->framesPerPeriod() - offset() + : engine::mixer()->framesPerPeriod(); + // check if we start release during this period if( m_released == false && instrumentTrack()->isSustainPedalPressed() == false && - m_totalFramesPlayed + engine::mixer()->framesPerPeriod() > m_frames ) + m_totalFramesPlayed + framesThisPeriod > m_frames ) { noteOff( m_frames - m_totalFramesPlayed ); } @@ -216,13 +205,18 @@ void NotePlayHandle::play( sampleFrame * _working_buffer ) // decreasing release of an instrument-track while the note is active if( framesLeft() > 0 ) { + // clear offset frames if we're at the first period + if( m_totalFramesPlayed == 0 ) + { + memset( _working_buffer, 0, sizeof( sampleFrame ) * offset() ); + } // play note! m_instrumentTrack->playNote( this, _working_buffer ); } if( m_released ) { - f_cnt_t todo = engine::mixer()->framesPerPeriod(); + f_cnt_t todo = framesThisPeriod; // if this note is base-note for arpeggio, always set // m_releaseFramesToDo to bigger value than m_releaseFramesDone @@ -238,8 +232,7 @@ void NotePlayHandle::play( sampleFrame * _working_buffer ) { // yes, then look whether these samples can be played // within one audio-buffer - if( m_framesBeforeRelease <= - engine::mixer()->framesPerPeriod() ) + if( m_framesBeforeRelease <= framesThisPeriod ) { // yes, then we did less releaseFramesDone todo -= m_framesBeforeRelease; @@ -251,8 +244,7 @@ void NotePlayHandle::play( sampleFrame * _working_buffer ) // and wait for next loop... (we're not in // release-phase yet) todo = 0; - m_framesBeforeRelease -= - engine::mixer()->framesPerPeriod(); + m_framesBeforeRelease -= framesThisPeriod; } } // look whether we're in release-phase @@ -290,7 +282,7 @@ void NotePlayHandle::play( sampleFrame * _working_buffer ) } // update internal data - m_totalFramesPlayed += engine::mixer()->framesPerPeriod(); + m_totalFramesPlayed += framesThisPeriod; } @@ -318,6 +310,10 @@ f_cnt_t NotePlayHandle::framesLeft() const fpp_t NotePlayHandle::framesLeftForCurrentPeriod() const { + if( m_totalFramesPlayed == 0 ) + { + return (fpp_t) qMin( framesLeft(), engine::mixer()->framesPerPeriod() - offset() ); + } return (fpp_t) qMin( framesLeft(), engine::mixer()->framesPerPeriod() ); } @@ -352,18 +348,10 @@ void NotePlayHandle::noteOff( const f_cnt_t _s ) if( hasParent() || ! m_instrumentTrack->isArpeggioEnabled() ) { // send MidiNoteOff event - f_cnt_t realOffset = offset() + _s; // get actual frameoffset of release, in global time - if( realOffset < engine::mixer()->framesPerPeriod() ) // if release happens during this period, trigger midievent - { - m_instrumentTrack->processOutEvent( + m_instrumentTrack->processOutEvent( MidiEvent( MidiNoteOff, midiChannel(), midiKey(), 0 ), - MidiTime::fromFrames( realOffset, engine::framesPerTick() ), - realOffset ); - } - else // if release flows over to next period, use m_scheduledNoteOff to trigger it later - { - m_scheduledNoteOff = realOffset - engine::mixer()->framesPerPeriod(); - } + MidiTime::fromFrames( _s, engine::framesPerTick() ), + _s ); } // inform attached components about MIDI finished (used for recording in Piano Roll) diff --git a/src/core/SamplePlayHandle.cpp b/src/core/SamplePlayHandle.cpp index 95613952e..5e3a4c37e 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -96,7 +96,7 @@ SamplePlayHandle::~SamplePlayHandle() -void SamplePlayHandle::play( sampleFrame * _working_buffer ) +void SamplePlayHandle::play( sampleFrame * buffer ) { //play( 0, _try_parallelizing ); if( framesDone() >= totalFrames() ) @@ -104,17 +104,27 @@ void SamplePlayHandle::play( sampleFrame * _working_buffer ) return; } - const fpp_t frames = engine::mixer()->framesPerPeriod(); + sampleFrame * workingBuffer = buffer; + const fpp_t fpp = engine::mixer()->framesPerPeriod(); + f_cnt_t frames = fpp; + + // apply offset for the first period + if( framesDone() == 0 ) + { + buffer += offset(); + frames -= offset(); + } + if( !( m_track && m_track->isMuted() ) && !( m_bbTrack && m_bbTrack->isMuted() ) ) { stereoVolumeVector v = { { m_volumeModel->value() / DefaultVolume, m_volumeModel->value() / DefaultVolume } }; - m_sampleBuffer->play( _working_buffer, &m_state, frames, + m_sampleBuffer->play( workingBuffer, &m_state, frames, BaseFreq ); - engine::mixer()->bufferToPort( _working_buffer, frames, - offset(), v, m_audioPort ); + engine::mixer()->bufferToPort( buffer, fpp, + v, m_audioPort ); } m_frame += frames; diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index b9e1e21b1..87d0de299 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,33 +186,58 @@ 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 // is no problem for us since we just bypass the envelopes+LFOs if( m_instrument->flags().testFlag( Instrument::IsSingleStreamed ) == false && n != NULL ) { - m_soundShaping.processAudioBuffer( buf, frames, n ); - v_scale *= ( (float) n->getVolume() / DefaultVolume ); + const f_cnt_t offset = n->noteOffset(); + m_soundShaping.processAudioBuffer( buf + offset, frames - offset, n ); + v_scale *= ( (float) n->getVolume() * DefaultVolumeRatio ); } m_audioPort.setNextFxChannel( m_effectChannelModel.value() ); - int framesToMix = frames; - int offset = 0; - int panning = m_panningModel.value(); + // get panning knob data + ValueBuffer * panBuf = m_panningModel.valueBuffer(); + int panning = panBuf + ? 0 + : m_panningModel.value(); if( n ) { - framesToMix = qMin( n->framesLeftForCurrentPeriod(), framesToMix ); - offset = n->offset(); - panning += n->getPanning(); panning = tLimit( panning, PanningLeft, PanningRight ); } - engine::mixer()->bufferToPort( buf, framesToMix, offset, panningToVolumeVector( panning, v_scale ), &m_audioPort ); + // 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 ); }