Merge pull request #938 from diizy/master-sf2-accurate

SF2: Make timing more accurate, also some fixes
This commit is contained in:
Vesa V
2014-07-06 16:21:22 +03:00
4 changed files with 172 additions and 100 deletions

View File

@@ -1246,7 +1246,7 @@ void MonstroInstrument::playNote( NotePlayHandle * _n,
sampleFrame * _working_buffer )
{
const fpp_t frames = _n->framesLeftForCurrentPeriod();
const f_cnt_t offset = _n->offset();
const f_cnt_t offset = _n->noteOffset();
if ( _n->totalFramesPlayed() == 0 || _n->m_pluginData == NULL )
{

View File

@@ -69,6 +69,9 @@ struct SF2PluginData
int lastPanning;
float lastVelocity;
fluid_voice_t * fluidVoice;
bool isNew;
f_cnt_t offset;
bool noteOffSent;
} ;
@@ -526,112 +529,114 @@ void sf2Instrument::updateSampleRate()
void sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * )
{
const float LOG440 = 2.643452676f;
const f_cnt_t tfp = _n->totalFramesPlayed();
int midiNote = (int)floor( 12.0 * ( log2( _n->unpitchedFrequency() ) - LOG440 ) - 4.0 );
// out of range?
if( midiNote <= 0 || midiNote >= 128 )
{
return;
}
if( tfp == 0 )
{
const float LOG440 = 2.643452676f;
int midiNote = (int)floor( 12.0 * ( log2( _n->unpitchedFrequency() ) - LOG440 ) - 4.0 );
// out of range?
if( midiNote <= 0 || midiNote >= 128 )
{
return;
}
const int baseVelocity = instrumentTrack()->midiPort()->baseVelocity();
SF2PluginData * pluginData = new SF2PluginData;
pluginData->midiNote = midiNote;
pluginData->lastPanning = 0;
pluginData->lastVelocity = 127;
pluginData->lastVelocity = _n->midiVelocity( baseVelocity );
pluginData->fluidVoice = NULL;
pluginData->isNew = true;
pluginData->offset = _n->offset();
pluginData->noteOffSent = false;
_n->m_pluginData = pluginData;
m_synthMutex.lock();
// get list of current voice IDs so we can easily spot the new
// voice after the fluid_synth_noteon() call
const int poly = fluid_synth_get_polyphony( m_synth );
fluid_voice_t * voices[poly];
unsigned int id[poly];
fluid_synth_get_voicelist( m_synth, voices, poly, -1 );
for( int i = 0; i < poly; ++i )
{
id[i] = 0;
}
for( int i = 0; i < poly && voices[i]; ++i )
{
id[i] = fluid_voice_get_id( voices[i] );
}
const int baseVelocity = instrumentTrack()->midiPort()->baseVelocity();
fluid_synth_noteon( m_synth, m_channel, midiNote, _n->midiVelocity( baseVelocity ) );
// get new voice and save it
fluid_synth_get_voicelist( m_synth, voices, poly, -1 );
for( int i = 0; i < poly && voices[i]; ++i )
{
const unsigned int newID = fluid_voice_get_id( voices[i] );
if( id[i] != newID || newID == 0 )
{
pluginData->fluidVoice = voices[i];
break;
}
}
m_synthMutex.unlock();
m_notesRunningMutex.lock();
++m_notesRunning[midiNote];
m_notesRunningMutex.unlock();
// insert the nph to the playing notes vector
m_playingNotesMutex.lock();
m_playingNotes.append( _n );
m_playingNotesMutex.unlock();
}
/* SF2PluginData * pluginData = static_cast<SF2PluginData *>(
_n->m_pluginData );
#ifdef SOMEONE_FIXED_PER_NOTE_PANNING
if( pluginData->fluidVoice &&
pluginData->lastPanning != _n->getPanning() )
else if( _n->isReleased() ) // note is released during this period
{
const float pan = -500 +
( (float)( _n->getPanning() - PanningLeft ) ) /
( (float)( PanningRight - PanningLeft ) ) * 1000;
SF2PluginData * pluginData = static_cast<SF2PluginData *>( _n->m_pluginData );
pluginData->offset = _n->framesBeforeRelease();
pluginData->isNew = false;
m_playingNotesMutex.lock();
m_playingNotes.append( _n );
m_playingNotesMutex.unlock();
m_synthMutex.lock();
fluid_voice_gen_set( pluginData->fluidVoice, GEN_PAN, pan );
fluid_voice_update_param( pluginData->fluidVoice, GEN_PAN );
m_synthMutex.unlock();
pluginData->lastPanning = _n->getPanning();
}
#endif
const float currentVelocity = _n->volumeLevel( tfp ) * instrumentTrack()->midiPort()->baseVelocity();
if( pluginData->fluidVoice &&
pluginData->lastVelocity != currentVelocity )
{
m_synthMutex.lock();
fluid_voice_gen_set( pluginData->fluidVoice, GEN_VELOCITY, currentVelocity );
fluid_voice_update_param( pluginData->fluidVoice, GEN_VELOCITY );
// make sure, FluidSynth modulates our changed GEN_VELOCITY via internal
// attenuation modulator, so changes take effect (7=Volume CC)
fluid_synth_cc( m_synth, m_channel, 7, 127 );
m_synthMutex.unlock();
pluginData->lastVelocity = currentVelocity;
}*/
}
void sf2Instrument::noteOn( SF2PluginData * n )
{
m_synthMutex.lock();
// get list of current voice IDs so we can easily spot the new
// voice after the fluid_synth_noteon() call
const int poly = fluid_synth_get_polyphony( m_synth );
fluid_voice_t * voices[poly];
unsigned int id[poly];
fluid_synth_get_voicelist( m_synth, voices, poly, -1 );
for( int i = 0; i < poly; ++i )
{
id[i] = 0;
}
for( int i = 0; i < poly && voices[i]; ++i )
{
id[i] = fluid_voice_get_id( voices[i] );
}
fluid_synth_noteon( m_synth, m_channel, n->midiNote, n->lastVelocity );
// get new voice and save it
fluid_synth_get_voicelist( m_synth, voices, poly, -1 );
for( int i = 0; i < poly && voices[i]; ++i )
{
const unsigned int newID = fluid_voice_get_id( voices[i] );
if( id[i] != newID || newID == 0 )
{
n->fluidVoice = voices[i];
break;
}
}
m_synthMutex.unlock();
m_notesRunningMutex.lock();
++m_notesRunning[ n->midiNote ];
m_notesRunningMutex.unlock();
}
void sf2Instrument::noteOff( SF2PluginData * n )
{
n->noteOffSent = true;
m_notesRunningMutex.lock();
const int notes = --m_notesRunning[n->midiNote];
m_notesRunningMutex.unlock();
if( notes <= 0 )
{
m_synthMutex.lock();
fluid_synth_noteoff( m_synth, m_channel, n->midiNote );
m_synthMutex.unlock();
}
}
void sf2Instrument::play( sampleFrame * _working_buffer )
{
const fpp_t frames = engine::mixer()->framesPerPeriod();
m_synthMutex.lock();
// set midi pitch for this period
const int currentMidiPitch = instrumentTrack()->midiPitch();
if( m_lastMidiPitch != currentMidiPitch )
{
@@ -645,7 +650,71 @@ void sf2Instrument::play( sampleFrame * _working_buffer )
m_lastMidiPitchRange = currentMidiPitchRange;
fluid_synth_pitch_wheel_sens( m_synth, m_channel, m_lastMidiPitchRange );
}
// if we have no new noteons/noteoffs, just render a period and call it a day
if( m_playingNotes.isEmpty() )
{
renderFrames( frames, _working_buffer );
instrumentTrack()->processAudioBuffer( _working_buffer, frames, NULL );
return;
}
// processing loop
// go through noteplayhandles in processing order
f_cnt_t currentFrame = 0;
while( ! m_playingNotes.isEmpty() )
{
// find the note with lowest offset
NotePlayHandle * currentNote = m_playingNotes[0];
for( int i = 1; i < m_playingNotes.size(); ++i )
{
SF2PluginData * currentData = static_cast<SF2PluginData *>( currentNote->m_pluginData );
SF2PluginData * iData = static_cast<SF2PluginData *>( m_playingNotes[i]->m_pluginData );
if( currentData->offset > iData->offset )
{
currentNote = m_playingNotes[i];
}
}
// process the current note:
// first see if we're synced in frame count
SF2PluginData * currentData = static_cast<SF2PluginData *>( currentNote->m_pluginData );
if( currentData->offset > currentFrame )
{
renderFrames( currentData->offset - currentFrame, _working_buffer + currentFrame );
currentFrame = currentData->offset;
}
if( currentData->isNew )
{
noteOn( currentData );
if( currentNote->isReleased() ) // if the note is released during the same period, we have to process it again for noteoff
{
currentData->isNew = false;
currentData->offset = currentNote->framesBeforeRelease();
}
else // otherwise remove the handle
{
m_playingNotes.remove( m_playingNotes.indexOf( currentNote ) );
}
}
else
{
noteOff( currentData );
m_playingNotes.remove( m_playingNotes.indexOf( currentNote ) );
}
}
if( currentFrame < frames )
{
renderFrames( frames - currentFrame, _working_buffer + currentFrame );
}
instrumentTrack()->processAudioBuffer( _working_buffer, frames, NULL );
}
void sf2Instrument::renderFrames( f_cnt_t frames, sampleFrame * buf )
{
m_synthMutex.lock();
if( m_internalSampleRate < engine::mixer()->processingSampleRate() &&
m_srcState != NULL )
{
@@ -658,8 +727,8 @@ void sf2Instrument::play( sampleFrame * _working_buffer )
fluid_synth_write_float( m_synth, f, tmp, 0, 2, tmp, 1, 2 );
SRC_DATA src_data;
src_data.data_in = tmp[0];
src_data.data_out = _working_buffer[0];
src_data.data_in = (float *)tmp;
src_data.data_out = (float *)buf;
src_data.input_frames = f;
src_data.output_frames = frames;
src_data.src_ratio = (double) frames / f;
@@ -679,11 +748,9 @@ void sf2Instrument::play( sampleFrame * _working_buffer )
}
else
{
fluid_synth_write_float( m_synth, frames, _working_buffer, 0, 2, _working_buffer, 1, 2 );
fluid_synth_write_float( m_synth, frames, buf, 0, 2, buf, 1, 2 );
}
m_synthMutex.unlock();
instrumentTrack()->processAudioBuffer( _working_buffer, frames, NULL );
}
@@ -692,17 +759,11 @@ void sf2Instrument::play( sampleFrame * _working_buffer )
void sf2Instrument::deleteNotePluginData( NotePlayHandle * _n )
{
SF2PluginData * pluginData = static_cast<SF2PluginData *>( _n->m_pluginData );
m_notesRunningMutex.lock();
const int n = --m_notesRunning[pluginData->midiNote];
m_notesRunningMutex.unlock();
if( n <= 0 )
if( ! pluginData->noteOffSent ) // if we for some reason haven't noteoffed the note before it gets deleted,
// do it here
{
m_synthMutex.lock();
fluid_synth_noteoff( m_synth, m_channel, pluginData->midiNote );
m_synthMutex.unlock();
noteOff( pluginData );
}
delete pluginData;
}

View File

@@ -45,6 +45,7 @@ class NotePlayHandle;
class patchesDialog;
class QLabel;
struct SF2PluginData;
class sf2Instrument : public Instrument
{
@@ -149,9 +150,14 @@ private:
FloatModel m_chorusSpeed;
FloatModel m_chorusDepth;
QVector<NotePlayHandle *> m_playingNotes;
QMutex m_playingNotesMutex;
private:
void freeFont();
void noteOn( SF2PluginData * n );
void noteOff( SF2PluginData * n );
void renderFrames( f_cnt_t frames, sampleFrame * buf );
friend class sf2InstrumentView;

View File

@@ -29,6 +29,7 @@
#include "DetuningHelper.h"
#include "InstrumentSoundShaping.h"
#include "InstrumentTrack.h"
#include "Instrument.h"
#include "MidiEvent.h"
#include "MidiPort.h"
#include "song.h"
@@ -197,7 +198,9 @@ void NotePlayHandle::play( sampleFrame * _working_buffer )
instrumentTrack()->isSustainPedalPressed() == false &&
m_totalFramesPlayed + framesThisPeriod > m_frames )
{
noteOff( m_frames - m_totalFramesPlayed );
noteOff( m_totalFramesPlayed == 0
? ( m_frames + offset() ) // if we have noteon and noteoff during the same period, take offset in account for release frame
: ( m_frames - m_totalFramesPlayed ) ); // otherwise, the offset is already negated and can be ignored
}
// under some circumstances we're called even if there's nothing to play
@@ -206,7 +209,9 @@ void NotePlayHandle::play( sampleFrame * _working_buffer )
if( framesLeft() > 0 )
{
// clear offset frames if we're at the first period
if( m_totalFramesPlayed == 0 )
// skip for single-streamed instruments, because in their case NPH::play() could be called from an IPH without a buffer argument
// ... also, they don't actually render the sound in NPH's, which is an even better reason to skip...
if( m_totalFramesPlayed == 0 && ! ( m_instrumentTrack->instrument()->flags() & Instrument::IsSingleStreamed ) )
{
memset( _working_buffer, 0, sizeof( sampleFrame ) * offset() );
}