diff --git a/plugins/monstro/Monstro.cpp b/plugins/monstro/Monstro.cpp index c1af10cd4..905269854 100644 --- a/plugins/monstro/Monstro.cpp +++ b/plugins/monstro/Monstro.cpp @@ -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 ) { diff --git a/plugins/sf2_player/sf2_player.cpp b/plugins/sf2_player/sf2_player.cpp index 0d82f63db..eaf3d0a86 100644 --- a/plugins/sf2_player/sf2_player.cpp +++ b/plugins/sf2_player/sf2_player.cpp @@ -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( - _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( _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( currentNote->m_pluginData ); + SF2PluginData * iData = static_cast( 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( 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( _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; } diff --git a/plugins/sf2_player/sf2_player.h b/plugins/sf2_player/sf2_player.h index 728a717cc..a50231c37 100644 --- a/plugins/sf2_player/sf2_player.h +++ b/plugins/sf2_player/sf2_player.h @@ -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 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; diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index ab3bfc3fb..934ebc129 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -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() ); }