From 7f9b4c2401975b4ab962cea6e30ba43248dec096 Mon Sep 17 00:00:00 2001 From: necrashter Date: Tue, 19 May 2020 05:34:54 +0300 Subject: [PATCH] Implement fade in to prevent Triple Osc from clicking (#5199) --- include/Instrument.h | 3 + include/NotePlayHandle.h | 3 + .../triple_oscillator/TripleOscillator.cpp | 1 + src/core/Instrument.cpp | 97 ++++++++++++++++++- 4 files changed, 100 insertions(+), 4 deletions(-) diff --git a/include/Instrument.h b/include/Instrument.h index 438197cd8..1dbf1ba58 100644 --- a/include/Instrument.h +++ b/include/Instrument.h @@ -132,6 +132,9 @@ public: protected: + // fade in to prevent clicks + void applyFadeIn(sampleFrame * buf, NotePlayHandle * n); + // instruments may use this to apply a soft fade out at the end of // notes - method does this only if really less or equal // desiredReleaseFrames() frames are left diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index 3dba0f277..e17a5e3a2 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -49,6 +49,9 @@ public: void * m_pluginData; std::unique_ptr> m_filter; + // length of the declicking fade in + fpp_t m_fadeInLength; + // specifies origin of NotePlayHandle enum Origins { diff --git a/plugins/triple_oscillator/TripleOscillator.cpp b/plugins/triple_oscillator/TripleOscillator.cpp index 2a1eccdd7..8093d218c 100644 --- a/plugins/triple_oscillator/TripleOscillator.cpp +++ b/plugins/triple_oscillator/TripleOscillator.cpp @@ -364,6 +364,7 @@ void TripleOscillator::playNote( NotePlayHandle * _n, osc_l->update( _working_buffer + offset, frames, 0 ); osc_r->update( _working_buffer + offset, frames, 1 ); + applyFadeIn(_working_buffer, _n); applyRelease( _working_buffer, _n ); instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); diff --git a/src/core/Instrument.cpp b/src/core/Instrument.cpp index ba608da14..3e8dc8074 100644 --- a/src/core/Instrument.cpp +++ b/src/core/Instrument.cpp @@ -23,8 +23,12 @@ */ #include "Instrument.h" -#include "InstrumentTrack.h" + +#include + #include "DummyInstrument.h" +#include "InstrumentTrack.h" +#include "lmms_constants.h" Instrument::Instrument(InstrumentTrack * _instrument_track, @@ -78,8 +82,96 @@ bool Instrument::isFromTrack( const Track * _track ) const return( m_instrumentTrack == _track ); } +// helper function for Instrument::applyFadeIn +static int countZeroCrossings(sampleFrame *buf, fpp_t start, fpp_t frames) +{ + // zero point crossing counts of all channels + int zeroCrossings[DEFAULT_CHANNELS] = {0}; + // maximum zero point crossing of all channels + int maxZeroCrossings = 0; + + // determine the zero point crossing counts + for (fpp_t f = start; f < frames; ++f) + { + for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch) + { + // we don't want to count [-1, 0, 1] as two crossings + if ((buf[f - 1][ch] <= 0.0 && buf[f][ch] > 0.0) || + (buf[f - 1][ch] >= 0.0 && buf[f][ch] < 0.0)) + { + ++zeroCrossings[ch]; + if (zeroCrossings[ch] > maxZeroCrossings) + { + maxZeroCrossings = zeroCrossings[ch]; + } + } + } + } + + return maxZeroCrossings; +} + +// helper function for Instrument::applyFadeIn +fpp_t getFadeInLength(float maxLength, fpp_t frames, int zeroCrossings) +{ + // calculate the length of the fade in + // Length is inversely proportional to the max of zeroCrossings, + // because for low frequencies, we need a longer fade in to + // prevent clicking. + return (fpp_t) (maxLength / ((float) zeroCrossings / ((float) frames / 128.0f) + 1.0f)); +} +void Instrument::applyFadeIn(sampleFrame * buf, NotePlayHandle * n) +{ + const static float MAX_FADE_IN_LENGTH = 85.0; + f_cnt_t total = n->totalFramesPlayed(); + if (total == 0) + { + const fpp_t frames = n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = n->offset(); + + // We need to skip the first sample because it almost always + // produces a zero crossing; it's not helpful while + // determining the fade in length. Hence 1 + int maxZeroCrossings = countZeroCrossings(buf, offset + 1, offset + frames); + + fpp_t length = getFadeInLength(MAX_FADE_IN_LENGTH, frames, maxZeroCrossings); + n->m_fadeInLength = length; + + // apply fade in + length = length < frames ? length : frames; + for (fpp_t f = 0; f < length; ++f) + { + for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch) + { + buf[offset + f][ch] *= 0.5 - 0.5 * cosf(F_PI * (float) f / (float) n->m_fadeInLength); + } + } + } + else if (total < n->m_fadeInLength) + { + const fpp_t frames = n->framesLeftForCurrentPeriod(); + + int new_zc = countZeroCrossings(buf, 1, frames); + fpp_t new_length = getFadeInLength(MAX_FADE_IN_LENGTH, frames, new_zc); + + for (fpp_t f = 0; f < frames; ++f) + { + for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch) + { + float currentLength = n->m_fadeInLength * (1.0f - (float) f / frames) + new_length * ((float) f / frames); + buf[f][ch] *= 0.5 - 0.5 * cosf(F_PI * (float) (total + f) / currentLength); + if (total + f >= currentLength) + { + n->m_fadeInLength = currentLength; + return; + } + } + } + n->m_fadeInLength = new_length; + } +} void Instrument::applyRelease( sampleFrame * buf, const NotePlayHandle * _n ) { @@ -109,6 +201,3 @@ QString Instrument::fullDisplayName() const { return instrumentTrack()->displayName(); } - - -