Merge pull request #911 from diizy/master-nph

Revision of handling of frameoffset for NPH, SPH
This commit is contained in:
Vesa V
2014-06-30 12:52:44 +03:00
28 changed files with 235 additions and 204 deletions

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

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

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;
@@ -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<int>( end_frame, m_framesPerPeriod );
const int loop1_frame = qMin<int>( 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<int>( end_frame, m_framesPerPeriod );
port->lockSecondBuffer();
const fpp_t framesLeft = qMin<int>( 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();
}

View File

@@ -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<f_cnt_t>( framesLeft(), engine::mixer()->framesPerPeriod() - offset() );
}
return (fpp_t) qMin<f_cnt_t>( 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)

View File

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

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,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<f_cnt_t>( n->framesLeftForCurrentPeriod(), framesToMix );
offset = n->offset();
panning += n->getPanning();
panning = tLimit<int>( 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 );
}