diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index b09ecaa6f..ea10447c5 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -74,7 +74,7 @@ public: virtual void processInEvent( const MidiEvent& event, const MidiTime& time = MidiTime(), f_cnt_t offset = 0 ); virtual void processOutEvent( const MidiEvent& event, const MidiTime& time = MidiTime(), f_cnt_t offset = 0 ); // silence all running notes played by this track - void silenceAllNotes(); + void silenceAllNotes( bool removeIPH = false ); bool isSustainPedalPressed() const { @@ -160,6 +160,8 @@ public: { return &m_baseNoteModel; } + + int baseNote() const; Piano *pianoModel() { diff --git a/include/Mixer.h b/include/Mixer.h index 9a20cae5a..7007e1801 100644 --- a/include/Mixer.h +++ b/include/Mixer.h @@ -229,7 +229,7 @@ public: return m_playHandles; } - void removePlayHandles( track * _track ); + void removePlayHandles( track * _track, bool removeIPHs = true ); bool hasNotePlayHandles(); diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index d2a5170b7..c564b1fc8 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -94,7 +94,7 @@ public: /*! Returns whether playback of note is finished and thus handle can be deleted */ virtual bool isFinished() const { - return m_released && framesLeft() <= 0; + return m_released && framesLeft() <= 0 && m_scheduledNoteOff < 0; } /*! Returns number of frames left for playback */ @@ -264,6 +264,7 @@ 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/src/core/Mixer.cpp b/src/core/Mixer.cpp index 9c35ee852..c9fd9b83c 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -686,13 +686,13 @@ void Mixer::removePlayHandle( PlayHandle * _ph ) -void Mixer::removePlayHandles( track * _track ) +void Mixer::removePlayHandles( track * _track, bool removeIPHs ) { lock(); PlayHandleList::Iterator it = m_playHandles.begin(); while( it != m_playHandles.end() ) { - if( ( *it )->isFromTrack( _track ) ) + if( ( *it )->isFromTrack( _track ) && ( removeIPHs || ( *it )->type() != PlayHandle::TypeInstrumentPlayHandle ) ) { delete *it; it = m_playHandles.erase( it ); diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index db535474a..8806d65e3 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -61,13 +61,14 @@ 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 ), m_muted( false ), m_bbTrack( NULL ), m_origTempo( engine::getSong()->getTempo() ), - m_origBaseNote( instrumentTrack->baseNoteModel()->value() ), + m_origBaseNote( instrumentTrack->baseNote() ), m_frequency( 0 ), m_unpitchedFrequency( 0 ), m_baseDetuning( NULL ), @@ -100,7 +101,7 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, m_instrumentTrack->midiNoteOn( *this ); } - if( !isMasterNote() || !instrumentTrack->isArpeggioEnabled() ) + if( hasParent() || !instrumentTrack->isArpeggioEnabled() ) { const int baseVelocity = m_instrumentTrack->midiPort()->baseVelocity(); @@ -118,7 +119,14 @@ 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 ) { delete m_baseDetuning; @@ -174,7 +182,7 @@ void NotePlayHandle::setPanning( panning_t panning ) int NotePlayHandle::midiKey() const { - return key() - m_origBaseNote + instrumentTrack()->baseNoteModel()->value(); + return key() - m_origBaseNote + instrumentTrack()->baseNote(); } @@ -182,6 +190,15 @@ 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; @@ -189,7 +206,7 @@ void NotePlayHandle::play( sampleFrame * _working_buffer ) if( m_released == false && instrumentTrack()->isSustainPedalPressed() == false && - m_totalFramesPlayed + engine::mixer()->framesPerPeriod() >= m_frames ) + m_totalFramesPlayed + engine::mixer()->framesPerPeriod() > m_frames ) { noteOff( m_frames - m_totalFramesPlayed ); } @@ -289,9 +306,9 @@ f_cnt_t NotePlayHandle::framesLeft() const { return m_framesBeforeRelease; } - else if( m_released && actualReleaseFramesToDo() >= m_releaseFramesDone ) + else if( m_released ) { - return m_framesBeforeRelease + actualReleaseFramesToDo() - m_releaseFramesDone; + return m_framesBeforeRelease + m_releaseFramesToDo - m_releaseFramesDone; } return m_frames+actualReleaseFramesToDo()-m_totalFramesPlayed; } @@ -330,15 +347,23 @@ void NotePlayHandle::noteOff( const f_cnt_t _s ) // then set some variables indicating release-state m_framesBeforeRelease = _s; - m_releaseFramesToDo = qMax( 0, m_instrumentTrack->m_soundShaping.releaseFrames() ); + m_releaseFramesToDo = qMax( 0, actualReleaseFramesToDo() ); if( hasParent() || !instrumentTrack()->isArpeggioEnabled() ) { // send MidiNoteOff event - m_instrumentTrack->processOutEvent( - MidiEvent( MidiNoteOff, midiChannel(), midiKey(), 0 ), - MidiTime::fromFrames( m_framesBeforeRelease, engine::framesPerTick() ), - _s ); + 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( + 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(); + } } // inform attached components about MIDI finished (used for recording in Piano Roll) diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 532431e6c..be2de2497 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -135,12 +135,17 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) : } +int InstrumentTrack::baseNote() const +{ + return m_baseNoteModel.value() - engine::getSong()->masterPitch(); +} + InstrumentTrack::~InstrumentTrack() { - // kill all running notes - silenceAllNotes(); + // kill all running notes and the iph + silenceAllNotes( true ); // now we're save deleting the instrument delete m_instrument; @@ -397,7 +402,7 @@ void InstrumentTrack::processOutEvent( const MidiEvent& event, const MidiTime& t -void InstrumentTrack::silenceAllNotes() +void InstrumentTrack::silenceAllNotes( bool removeIPH ) { engine::mixer()->lock(); for( int i = 0; i < NumKeys; ++i ) @@ -408,7 +413,7 @@ void InstrumentTrack::silenceAllNotes() // invalidate all NotePlayHandles linked to this track m_processHandles.clear(); - engine::mixer()->removePlayHandles( this ); + engine::mixer()->removePlayHandles( this, removeIPH ); engine::mixer()->unlock(); } @@ -537,7 +542,7 @@ void InstrumentTrack::updatePitchRange() int InstrumentTrack::masterKey( int _midi_key ) const { - int key = m_baseNoteModel.value() - engine::getSong()->masterPitch(); + int key = baseNote(); return tLimit( _midi_key - ( key - DefaultKey ), 0, NumKeys ); } @@ -695,7 +700,7 @@ void InstrumentTrack::saveTrackSpecificSettings( QDomDocument& doc, QDomElement void InstrumentTrack::loadTrackSpecificSettings( const QDomElement & thisElement ) { - silenceAllNotes(); + silenceAllNotes( true ); engine::mixer()->lock(); @@ -771,7 +776,7 @@ void InstrumentTrack::loadTrackSpecificSettings( const QDomElement & thisElement Instrument * InstrumentTrack::loadInstrument( const QString & _plugin_name ) { - silenceAllNotes(); + silenceAllNotes( true ); engine::mixer()->lock(); delete m_instrument;