Revision of handling of frameoffset for NPH, SPH
Change in handling of frameoffset for multistreamed instruments and sampletracks. - Instead of holding the offset for the lifetime of the playhandle, negate the offset in the first period - Multistream-instruments require some small changes: they have to now check for the offset and accordingly leave empty space in the start of the period (already done in this commit) - There are possibly optimizations that can be done later - This change is necessary so that we can have sample-exact models, and sample-exact vol/pan knobs for all instruments. Earlier multistream instruments were always rendering some frames ahead-of-time, so applying sample-exact data for them would have been impossible, since we don't have the future-values yet...
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -451,44 +451,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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -193,26 +193,22 @@ void InstrumentTrack::processAudioBuffer( sampleFrame* buf, const fpp_t frames,
|
||||
// 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 );
|
||||
const f_cnt_t offset = n->noteOffset();
|
||||
m_soundShaping.processAudioBuffer( buf + offset, frames - offset, n );
|
||||
v_scale *= ( (float) n->getVolume() / DefaultVolume );
|
||||
}
|
||||
|
||||
m_audioPort.setNextFxChannel( m_effectChannelModel.value() );
|
||||
|
||||
int framesToMix = frames;
|
||||
int offset = 0;
|
||||
int panning = 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 );
|
||||
engine::mixer()->bufferToPort( buf, frames, panningToVolumeVector( panning, v_scale ), &m_audioPort );
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user