From 8e3908e785f26c3823d92e5c690bb585ba1079c4 Mon Sep 17 00:00:00 2001 From: Vesa Date: Wed, 22 Oct 2014 07:05:53 +0300 Subject: [PATCH 1/3] Initial commit: new filter - SV Lowpass Adds a state-variant 4-pole lowpass filter into LMMS, which I swiped from Nekobee and slightly adapted to work in LMMS It is possible that with some adjustments a highpass version could also be produced (will have to look into that) It sounds really cool, kind of like the moog filter but has more character, esp. on high Q values --- include/basic_filters.h | 67 ++++++++++++++++++++--- plugins/DualFilter/DualFilterControls.cpp | 2 + src/core/InstrumentSoundShaping.cpp | 1 + 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/include/basic_filters.h b/include/basic_filters.h index 5f1055685..9408fcfc1 100644 --- a/include/basic_filters.h +++ b/include/basic_filters.h @@ -3,6 +3,9 @@ * * original file by ??? * modified and enhanced by Tobias Doerffel + * + * Lowpass_SV code originally from Nekobee, Copyright (C) 2004 Sean Bolton and others + * adapted & modified for use in LMMS * * Copyright (c) 2004-2009 Tobias Doerffel * @@ -65,12 +68,13 @@ public: Bandpass_RC24, Highpass_RC24, Formantfilter, + Lowpass_SV, NumFilters } ; static inline float minFreq() { - return( 3.0f ); + return( 5.0f ); } static inline float minQ() @@ -108,6 +112,9 @@ public: m_rca( 0.0f ), m_rcb( 1.0f ), m_rcc( 0.0f ), + m_svf1( 0.0f ), + m_svf2( 0.0f ), + m_svq( 0.0f ), m_doubleFilter( false ), m_sampleRate( (float) _sample_rate ), m_subFilter( NULL ) @@ -140,6 +147,13 @@ public: for(int i=0; i<6; i++) m_vfbp[i][_chnl] = m_vfhp[i][_chnl] = m_vflast[i][_chnl] = 0.0f; + + // reset in/out history for Lowpass_SV + m_delay1[_chnl] = 0.0f; + m_delay2[_chnl] = 0.0f; + m_delay3[_chnl] = 0.0f; + m_delay4[_chnl] = 0.0f; + m_sva[_chnl] = 0.0f; } } @@ -179,6 +193,26 @@ public: m_y4[_chnl] * ( 1.0f / 6.0f ); break; } + + // 4-pole state-variant lowpass filter, adapted from Nekobee source code + // /* Hal Chamberlin's state variable filter */ + + case Lowpass_SV: + { + m_sva[_chnl] += ( qAbs( _in0 ) - m_sva[_chnl] ) * m_svsr; + + m_delay2[_chnl] = m_delay2[_chnl] + m_svf1 * m_delay1[_chnl]; /* delay2/4 = lowpass output */ + float highpass = _in0 - m_delay2[_chnl] - m_svq * m_delay1[_chnl]; + m_delay1[_chnl] = m_svf1 * highpass + m_delay1[_chnl]; /* delay1/3 = bandpass output */ + + m_delay4[_chnl] = m_delay4[_chnl] + m_svf2 * m_delay3[_chnl]; + highpass = m_delay2[_chnl] - m_delay4[_chnl] - m_svq * m_delay3[_chnl]; + m_delay3[_chnl] = m_svf2 * highpass + m_delay3[_chnl]; + + /* mix filter output into output buffer */ + out = atanf( 3.0f * m_delay4[_chnl] * m_sva[_chnl] ); + break; + } // 4-times oversampled simulation of an active RC-Bandpass,-Lowpass,-Highpass- @@ -351,8 +385,8 @@ public: m_vflast[2][_chnl] = in; m_vfhp[2][_chnl] = hp; - m_vfbp[2][_chnl] = bp; - + m_vfbp[2][_chnl] = bp; + in = bp + m_vfbp[4][_chnl] * m_vfq; in = qBound( -1.0f, in, 1.0f ); @@ -364,7 +398,7 @@ public: m_vflast[4][_chnl] = in; m_vfhp[4][_chnl] = hp; - m_vfbp[4][_chnl] = bp; + m_vfbp[4][_chnl] = bp; out += bp; @@ -393,7 +427,7 @@ public: m_vflast[3][_chnl] = in; m_vfhp[3][_chnl] = hp; - m_vfbp[3][_chnl] = bp; + m_vfbp[3][_chnl] = bp; in = bp + m_vfbp[5][_chnl] * m_vfq; in = qBound( -1.0f, in, 1.0f ); @@ -406,7 +440,7 @@ public: m_vflast[5][_chnl] = in; m_vfhp[5][_chnl] = hp; - m_vfbp[5][_chnl] = bp; + m_vfbp[5][_chnl] = bp; out += bp; } @@ -508,7 +542,7 @@ public: if( m_type == Moog ) { - _freq = qBound( minFreq(), _freq, 20000.0f ); + _freq = qBound( minFreq(), _freq, 20000.0f ); // [ 0 - 0.5 ] const float f = _freq / m_sampleRate; @@ -526,8 +560,17 @@ public: return; } + if( m_type == Lowpass_SV ) + { + m_svf1 = qBound( 0.0001f, _freq * 0.00004125f, 0.825f ); + m_svf2 = qBound( 0.0001f, _freq * 0.00008250f, 0.825f ); + m_svq = qMax( 0.0001f, 2.0f - ( _q * 0.1995f ) ); + m_svsr = 2025.0f / m_sampleRate; + return; + } + // other filters - _freq = qBound( minFreq(), _freq, 20000.0f ); + _freq = qBound( minFreq(), _freq, 20000.0f ); const float omega = F_2PI * _freq / m_sampleRate; const float tsin = sinf( omega ); const float tcos = cosf( omega ); @@ -604,6 +647,9 @@ private: // coeffs for formant-filters float m_vfa[4], m_vfb[4], m_vfc[4], m_vfq; + // coeffs for Lowpass_SV (state-variant lowpass) + float m_svf1, m_svf2, m_svq, m_svsr; + typedef sample_t frame[CHANNELS]; // in/out history @@ -618,7 +664,10 @@ private: // in/out history for Formant-filters frame m_vfbp[6], m_vfhp[6], m_vflast[6]; - + + // in/out history for Lowpass_SV (state-variant lowpass) + frame m_delay1, m_delay2, m_delay3, m_delay4, m_sva; + FilterTypes m_type; bool m_doubleFilter; diff --git a/plugins/DualFilter/DualFilterControls.cpp b/plugins/DualFilter/DualFilterControls.cpp index bb1abbdb0..6bcaa9e9d 100644 --- a/plugins/DualFilter/DualFilterControls.cpp +++ b/plugins/DualFilter/DualFilterControls.cpp @@ -74,6 +74,7 @@ DualFilterControls::DualFilterControls( DualFilterEffect* effect ) : m_filter1Model.addItem( tr( "RC BandPass 24dB" ), new PixmapLoader( "filter_bp" ) ); m_filter1Model.addItem( tr( "RC HighPass 24dB" ), new PixmapLoader( "filter_hp" ) ); m_filter1Model.addItem( tr( "Vocal Formant Filter" ), new PixmapLoader( "filter_hp" ) ); + m_filter1Model.addItem( tr( "SV Lowpass" ), new PixmapLoader( "filter_lp" ) ); m_filter2Model.addItem( tr( "LowPass" ), new PixmapLoader( "filter_lp" ) ); m_filter2Model.addItem( tr( "HiPass" ), new PixmapLoader( "filter_hp" ) ); @@ -90,6 +91,7 @@ DualFilterControls::DualFilterControls( DualFilterEffect* effect ) : m_filter2Model.addItem( tr( "RC BandPass 24dB" ), new PixmapLoader( "filter_bp" ) ); m_filter2Model.addItem( tr( "RC HighPass 24dB" ), new PixmapLoader( "filter_hp" ) ); m_filter2Model.addItem( tr( "Vocal Formant Filter" ), new PixmapLoader( "filter_hp" ) ); + m_filter2Model.addItem( tr( "SV Lowpass" ), new PixmapLoader( "filter_lp" ) ); connect( engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateFilters() ) ); } diff --git a/src/core/InstrumentSoundShaping.cpp b/src/core/InstrumentSoundShaping.cpp index f19b3b0f6..9e95b9828 100644 --- a/src/core/InstrumentSoundShaping.cpp +++ b/src/core/InstrumentSoundShaping.cpp @@ -94,6 +94,7 @@ InstrumentSoundShaping::InstrumentSoundShaping( m_filterModel.addItem( tr( "RC BandPass 24dB" ), new PixmapLoader( "filter_bp" ) ); m_filterModel.addItem( tr( "RC HighPass 24dB" ), new PixmapLoader( "filter_hp" ) ); m_filterModel.addItem( tr( "Vocal Formant Filter" ), new PixmapLoader( "filter_hp" ) ); + m_filterModel.addItem( tr( "SV LowPass" ), new PixmapLoader( "filter_lp" ) ); } From eb86428f10a218ad436b433967ef3cb1dc7e6e35 Mon Sep 17 00:00:00 2001 From: Vesa Date: Wed, 22 Oct 2014 15:12:58 +0300 Subject: [PATCH 2/3] Correct samplerate-agnostic behaviour --- include/basic_filters.h | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/include/basic_filters.h b/include/basic_filters.h index 9408fcfc1..f0099c92d 100644 --- a/include/basic_filters.h +++ b/include/basic_filters.h @@ -117,8 +117,10 @@ public: m_svq( 0.0f ), m_doubleFilter( false ), m_sampleRate( (float) _sample_rate ), + m_sampleRatio( 1.0f / m_sampleRate ), m_subFilter( NULL ) { + m_svsr = 1.0f - expf( -4646.39874051f / m_sampleRate ); clearHistory(); } @@ -475,8 +477,7 @@ public: } - inline void calcFilterCoeffs( float _freq, float _q - /*, const bool _q_is_bandwidth = false*/ ) + inline void calcFilterCoeffs( float _freq, float _q ) { // temp coef vars _q = qMax( _q, minQ() ); @@ -545,7 +546,7 @@ public: _freq = qBound( minFreq(), _freq, 20000.0f ); // [ 0 - 0.5 ] - const float f = _freq / m_sampleRate; + const float f = _freq * m_sampleRatio; // (Empirical tunning) m_p = ( 3.6f - 3.2f * f ) * f; m_k = 2.0f * m_p - 1; @@ -562,16 +563,16 @@ public: if( m_type == Lowpass_SV ) { - m_svf1 = qBound( 0.0001f, _freq * 0.00004125f, 0.825f ); - m_svf2 = qBound( 0.0001f, _freq * 0.00008250f, 0.825f ); + const float f = qMax( minFreq(), _freq ) * m_sampleRatio; + m_svf1 = qMin( f * 2.0f, 0.825f ); + m_svf2 = qMin( f * 4.0f, 0.825f ); m_svq = qMax( 0.0001f, 2.0f - ( _q * 0.1995f ) ); - m_svsr = 2025.0f / m_sampleRate; return; } // other filters _freq = qBound( minFreq(), _freq, 20000.0f ); - const float omega = F_2PI * _freq / m_sampleRate; + const float omega = F_2PI * _freq * m_sampleRatio; const float tsin = sinf( omega ); const float tcos = cosf( omega ); //float alpha; @@ -672,6 +673,7 @@ private: bool m_doubleFilter; float m_sampleRate; + float m_sampleRatio; basicFilters * m_subFilter; } ; From e9ab240558d0293935fe5f421f35d16a56f5d70c Mon Sep 17 00:00:00 2001 From: Vesa Date: Wed, 22 Oct 2014 16:17:28 +0300 Subject: [PATCH 3/3] Add more filters! --- include/basic_filters.h | 35 +++++++++++++++++++---- plugins/DualFilter/DualFilterControls.cpp | 10 +++++-- src/core/InstrumentSoundShaping.cpp | 5 +++- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/include/basic_filters.h b/include/basic_filters.h index f0099c92d..b249eb4c5 100644 --- a/include/basic_filters.h +++ b/include/basic_filters.h @@ -68,7 +68,10 @@ public: Bandpass_RC24, Highpass_RC24, Formantfilter, + DoubleMoog, Lowpass_SV, + Bandpass_SV, + Highpass_SV, NumFilters } ; @@ -84,7 +87,7 @@ public: inline void setFilterType( const int _idx ) { - m_doubleFilter = _idx == DoubleLowPass; + m_doubleFilter = _idx == DoubleLowPass || _idx == DoubleMoog; if( !m_doubleFilter ) { m_type = static_cast( _idx ); @@ -93,7 +96,9 @@ public: // Double lowpass mode, backwards-compat for the goofy // Add-NumFilters to signify doubleFilter stuff - m_type = static_cast( LowPass ); + m_type = _idx == DoubleLowPass + ? LowPass + : Moog; if( m_subFilter == NULL ) { m_subFilter = new basicFilters( @@ -150,7 +155,7 @@ public: for(int i=0; i<6; i++) m_vfbp[i][_chnl] = m_vfhp[i][_chnl] = m_vflast[i][_chnl] = 0.0f; - // reset in/out history for Lowpass_SV + // reset in/out history for SV-filters m_delay1[_chnl] = 0.0f; m_delay2[_chnl] = 0.0f; m_delay3[_chnl] = 0.0f; @@ -200,6 +205,7 @@ public: // /* Hal Chamberlin's state variable filter */ case Lowpass_SV: + case Bandpass_SV: { m_sva[_chnl] += ( qAbs( _in0 ) - m_sva[_chnl] ) * m_svsr; @@ -212,7 +218,21 @@ public: m_delay3[_chnl] = m_svf2 * highpass + m_delay3[_chnl]; /* mix filter output into output buffer */ - out = atanf( 3.0f * m_delay4[_chnl] * m_sva[_chnl] ); + out = m_type == Lowpass_SV + ? atanf( 3.0f * m_delay4[_chnl] * m_sva[_chnl] ) + : atanf( 3.0f * m_delay3[_chnl] * m_sva[_chnl] ); + break; + } + + case Highpass_SV: + { + m_sva[_chnl] += ( qAbs( _in0 ) - m_sva[_chnl] ) * m_svsr; + + m_delay2[_chnl] = m_delay2[_chnl] + m_svf1 * m_delay1[_chnl]; + float hp = _in0 - m_delay2[_chnl] - m_svq * m_delay1[_chnl]; + m_delay1[_chnl] = m_svf1 * hp + m_delay1[_chnl]; + + out = atanf( 3.0f * hp * m_sva[_chnl] ); break; } @@ -541,7 +561,8 @@ public: return; } - if( m_type == Moog ) + if( m_type == Moog || + m_type == DoubleMoog ) { _freq = qBound( minFreq(), _freq, 20000.0f ); @@ -561,7 +582,9 @@ public: return; } - if( m_type == Lowpass_SV ) + if( m_type == Lowpass_SV || + m_type == Bandpass_SV || + m_type == Highpass_SV ) { const float f = qMax( minFreq(), _freq ) * m_sampleRatio; m_svf1 = qMin( f * 2.0f, 0.825f ); diff --git a/plugins/DualFilter/DualFilterControls.cpp b/plugins/DualFilter/DualFilterControls.cpp index 6bcaa9e9d..6a2061bd8 100644 --- a/plugins/DualFilter/DualFilterControls.cpp +++ b/plugins/DualFilter/DualFilterControls.cpp @@ -74,7 +74,10 @@ DualFilterControls::DualFilterControls( DualFilterEffect* effect ) : m_filter1Model.addItem( tr( "RC BandPass 24dB" ), new PixmapLoader( "filter_bp" ) ); m_filter1Model.addItem( tr( "RC HighPass 24dB" ), new PixmapLoader( "filter_hp" ) ); m_filter1Model.addItem( tr( "Vocal Formant Filter" ), new PixmapLoader( "filter_hp" ) ); - m_filter1Model.addItem( tr( "SV Lowpass" ), new PixmapLoader( "filter_lp" ) ); + m_filter1Model.addItem( tr( "2x Moog" ), new PixmapLoader( "filter_2lp" ) ); + m_filter1Model.addItem( tr( "SV LowPass" ), new PixmapLoader( "filter_lp" ) ); + m_filter1Model.addItem( tr( "SV BandPass" ), new PixmapLoader( "filter_bp" ) ); + m_filter1Model.addItem( tr( "SV HighPass" ), new PixmapLoader( "filter_hp" ) ); m_filter2Model.addItem( tr( "LowPass" ), new PixmapLoader( "filter_lp" ) ); m_filter2Model.addItem( tr( "HiPass" ), new PixmapLoader( "filter_hp" ) ); @@ -91,7 +94,10 @@ DualFilterControls::DualFilterControls( DualFilterEffect* effect ) : m_filter2Model.addItem( tr( "RC BandPass 24dB" ), new PixmapLoader( "filter_bp" ) ); m_filter2Model.addItem( tr( "RC HighPass 24dB" ), new PixmapLoader( "filter_hp" ) ); m_filter2Model.addItem( tr( "Vocal Formant Filter" ), new PixmapLoader( "filter_hp" ) ); - m_filter2Model.addItem( tr( "SV Lowpass" ), new PixmapLoader( "filter_lp" ) ); + m_filter2Model.addItem( tr( "2x Moog" ), new PixmapLoader( "filter_2lp" ) ); + m_filter2Model.addItem( tr( "SV LowPass" ), new PixmapLoader( "filter_lp" ) ); + m_filter2Model.addItem( tr( "SV BandPass" ), new PixmapLoader( "filter_bp" ) ); + m_filter2Model.addItem( tr( "SV HighPass" ), new PixmapLoader( "filter_hp" ) ); connect( engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateFilters() ) ); } diff --git a/src/core/InstrumentSoundShaping.cpp b/src/core/InstrumentSoundShaping.cpp index 9e95b9828..20f16b05c 100644 --- a/src/core/InstrumentSoundShaping.cpp +++ b/src/core/InstrumentSoundShaping.cpp @@ -94,7 +94,10 @@ InstrumentSoundShaping::InstrumentSoundShaping( m_filterModel.addItem( tr( "RC BandPass 24dB" ), new PixmapLoader( "filter_bp" ) ); m_filterModel.addItem( tr( "RC HighPass 24dB" ), new PixmapLoader( "filter_hp" ) ); m_filterModel.addItem( tr( "Vocal Formant Filter" ), new PixmapLoader( "filter_hp" ) ); - m_filterModel.addItem( tr( "SV LowPass" ), new PixmapLoader( "filter_lp" ) ); + m_filterModel.addItem( tr( "2x Moog" ), new PixmapLoader( "filter_2lp" ) ); + m_filterModel.addItem( tr( "SV LowPass" ), new PixmapLoader( "filter_lp" ) ); + m_filterModel.addItem( tr( "SV BandPass" ), new PixmapLoader( "filter_bp" ) ); + m_filterModel.addItem( tr( "SV HighPass" ), new PixmapLoader( "filter_hp" ) ); }