/* * polyb302.cpp - implementation of instrument polyb302, an attempt to emulate * the Roland TB303 bass synth * * Copyright (c) 2006-2007 Paul Giblock * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * lb302FilterIIR2 is based on the gsyn filter code by Andy Sloane. * * lb302Filter3Pole is based on the TB303 instrument written by * Josep M Comajuncosas for the CSounds library * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "polyb302.h" #include "engine.h" #include "knob.h" #include "led_checkbox.h" #include "note_play_handle.h" #undef SINGLE_SOURCE_COMPILE #include "embed.cpp" #include "polyb302.moc" // Envelope Recalculation period #define ENVINC 64 // // New config // #define LB_24_IGNORE_ENVELOPE #define LB_FILTERED //#define LB_24_RES_TRICK #define LB_DIST_RATIO 4.0 #define LB_24_VOL_ADJUST 3.0 using namespace std; extern "C" { plugin::descriptor polyb302_plugin_descriptor = { STRINGIFY_PLUGIN_NAME( PLUGIN_NAME ), "PoLyB302", QT_TRANSLATE_NOOP( "pluginBrowser", "Incomplete polyphonic immitation tb303" ), "Javier Serrano Polo ", 0x0100, plugin::Instrument, new QPixmap( PLUGIN_NAME::getIconPixmap( "logo" ) ), NULL } ; // necessary for getting instance out of shared lib plugin * lmms_plugin_main( void * _data ) { return( new polyb302Synth( static_cast( _data ) ) ); } } // // lb302Filter // lb302Filter::lb302Filter( lb302FilterState * _p_fs ) : m_fs( _p_fs ), m_vcf_c0( 0 ), m_vcf_e0( 0 ), m_vcf_e1( 0 ) { } void lb302Filter::recalc( void ) { m_vcf_e1 = exp( 4.909 + 1.5876 * m_fs->envmod + 2.1553 * m_fs->cutoff + 1.2 * m_fs->reso ); m_vcf_e0 = exp( 4.8434 - 0.8 * m_fs->envmod + 2.1553 * m_fs->cutoff + 0.7696 * m_fs->reso ); m_vcf_e0 *= M_PI / engine::getMixer()->sampleRate(); m_vcf_e1 *= M_PI / engine::getMixer()->sampleRate(); m_vcf_e1 -= m_vcf_e0; } void lb302Filter::envRecalc( void ) { // Filter Decay. vcf_decay is adjusted for Hz and ENVINC m_vcf_c0 *= m_fs->envdecay; m_vcf_rescoeff = exp( -1.20 + 3.455 * m_fs->reso ); } void lb302Filter::playNote( void ) { m_vcf_c0 = m_vcf_e1; } // // lb302FilterIIR2 // lb302FilterIIR2::lb302FilterIIR2( lb302FilterState * _p_fs ) : lb302Filter( _p_fs ), m_vcf_d1( 0 ), m_vcf_d2( 0 ), m_vcf_a( 0 ), m_vcf_b( 0 ), m_vcf_c( 1 ) { m_dist = new effectLib::distortion<>( 1.0, 1.0f ); } lb302FilterIIR2::~lb302FilterIIR2() { delete m_dist; } void lb302FilterIIR2::recalc( void ) { lb302Filter::recalc(); //m_dist->setThreshold(0.5+(m_fs->dist*2.0)); m_dist->setThreshold( m_fs->dist * 75.0 ); } void lb302FilterIIR2::envRecalc( void ) { lb302Filter::envRecalc(); // e0 is adjusted for Hz and doesn't need ENVINC float w = m_vcf_e0 + m_vcf_c0; float k = exp( -w / m_vcf_rescoeff ); // Does this mean c0 is inheritantly? m_vcf_a = 2.0 * cos( 2.0 * w ) * k; m_vcf_b = -k * k; m_vcf_c = 1.0 - m_vcf_a - m_vcf_b; } float lb302FilterIIR2::process( const float & _samp ) { float ret = m_vcf_a * m_vcf_d1 + m_vcf_b * m_vcf_d2 + m_vcf_c * _samp; // Delayed samples for filter m_vcf_d2 = m_vcf_d1; m_vcf_d1 = ret; if( m_fs->dist > 0 ) { ret = m_dist->nextSample( ret ); } // output = IIR2 + dry return( ret ); } // // lb302Filter3Pole // lb302Filter3Pole::lb302Filter3Pole( lb302FilterState * _p_fs ) : lb302Filter( _p_fs ), m_ay1( 0 ), m_ay2( 0 ), m_aout( 0 ), m_lastin( 0 ) { } void lb302Filter3Pole::recalc( void ) { // DO NOT CALL BASE CLASS m_vcf_e0 = 0.000001; m_vcf_e1 = 1.0; } // TODO: Try using k instead of vcf_reso void lb302Filter3Pole::envRecalc( void ) { lb302Filter::envRecalc(); // e0 is adjusted for Hz and doesn't need ENVINC float w = m_vcf_e0 + m_vcf_c0; float k = ( m_fs->cutoff > 0.975 ) ? 0.975 : m_fs->cutoff; //TODO: Fix high quality float kfco = 50.0f + k * ( 2300.0f - 1600.0f * m_fs->envmod + w * ( 700.0f + 1500.0f * k + ( 1500.0f + k * ( engine::getMixer()->sampleRate() / 2.0f - 6000.0f ) ) * m_fs->envmod ) ); //+iacc*(.3+.7*kfco*kenvmod)*kaccent*kaccurve*2000 #ifdef LB_24_IGNORE_ENVELOPE // m_kfcn = m_fs->cutoff; m_kfcn = 2.0 * kfco / engine::getMixer()->sampleRate(); #else m_kfcn = w; #endif m_kp = ( ( -2.7528 * m_kfcn + 3.0429 ) * m_kfcn + 1.718 ) * m_kfcn - 0.9984; m_kp1 = m_kp + 1.0; m_kp1h = 0.5 * m_kp1; #ifdef LB_24_RES_TRICK k = exp( -w / m_vcf_rescoeff ); m_kres = k * ( ( ( -2.7079 * m_kp1 + 10.963 ) * m_kp1 - 14.934 ) * m_kp1 + 8.4974 ); #else m_kres = m_fs->reso * ( ( ( -2.7079 * m_kp1 + 10.963 ) * m_kp1 - 14.934 ) * m_kp1 + 8.4974 ); #endif // ENVMOD was DIST*/ m_value = 1.0 + ( m_fs->dist * ( 1.5 + 2.0 * m_kres * ( 1.0 - m_kfcn ) ) ); } float lb302Filter3Pole::process( const float & _samp ) { float ax1 = m_lastin; float ay11 = m_ay1; float ay31 = m_ay2; m_lastin = _samp - tanh( m_kres * m_aout ); m_ay1 = m_kp1h * ( m_lastin + ax1 ) - m_kp * m_ay1; m_ay2 = m_kp1h * ( m_ay1 + ay11 ) - m_kp * m_ay2; m_aout = m_kp1h * ( m_ay2 + ay31 ) - m_kp * m_aout; return( tanh( m_aout * m_value ) * LB_24_VOL_ADJUST / ( 1.0 + m_fs->dist ) ); } // // PoLyBSynth // polyb302Synth::polyb302Synth( instrumentTrack * _track ) : instrument( _track, &polyb302_plugin_descriptor ) { m_vcf_cut_knob = new knob( knobBright_26, this, tr( "VCF Cutoff Frequency" ), _track ); m_vcf_cut_knob->setRange( 0.0f, 1.5f, 0.005f ); // Originally [0,1.0] m_vcf_cut_knob->setInitValue( 0.75f ); m_vcf_cut_knob->move( 75, 130 ); m_vcf_cut_knob->setHintText( tr( "Cutoff Freq:" ) + " ", "" ); m_vcf_cut_knob->setLabel( tr( "CUT" ) ); m_vcf_res_knob = new knob( knobBright_26, this, tr( "VCF Resonance" ), _track ); m_vcf_res_knob->setRange( 0.0f, 1.25f, 0.005f ); // Originally [0,1.0] m_vcf_res_knob->setInitValue( 0.75f ); m_vcf_res_knob->move( 120, 130 ); m_vcf_res_knob->setHintText( tr( "Resonance:" ) + " ", "" ); m_vcf_res_knob->setLabel( tr( "RES" ) ); m_vcf_mod_knob = new knob( knobBright_26, this, tr( "VCF Envelope Mod" ), _track ); m_vcf_mod_knob->setRange( 0.0f, 1.0f, 0.005f ); // Originally [0,1.0] m_vcf_mod_knob->setInitValue( 1.0f ); m_vcf_mod_knob->move( 165, 130 ); m_vcf_mod_knob->setHintText( tr( "Env Mod:" ) + " ", "" ); m_vcf_mod_knob->setLabel( tr( "ENV MOD" ) ); m_vcf_dec_knob = new knob( knobBright_26, this, tr( "VCF Envelope Decay" ), _track ); m_vcf_dec_knob->setRange( 0.0f, 1.0f, 0.005f ); // Originally [0,1.0] m_vcf_dec_knob->setInitValue( 0.1f ); m_vcf_dec_knob->move( 210, 130 ); m_vcf_dec_knob->setHintText( tr( "Decay:" ) + " ", "" ); m_vcf_dec_knob->setLabel( tr( "DEC" ) ); // m_slideToggle = new ledCheckBox( "Slide", this, tr( "Slide" ), _track ); // m_slideToggle->move( 10, 180 ); // m_accentToggle = new ledCheckBox( "Accent", this, // tr( "Accent" ), // _track ); // m_accentToggle->move( 10, 200 ); // m_accentToggle->setDisabled(true); // m_deadToggle = new ledCheckBox( "Dead", this, tr( "Dead" ), _track ); // m_deadToggle->move( 10, 220 ); m_db24Toggle = new ledCheckBox( "24dB/oct", this, tr( "303-es-que, 24dB/octave, 3 pole filter" ), _track ); m_db24Toggle->move( 10, 150 ); m_slide_dec_knob = new knob( knobBright_26, this, tr( "Slide Decay" ), _track ); m_slide_dec_knob->setRange( 0.0f, 1.0f, 0.005f ); // Originally [0,1.0] m_slide_dec_knob->setInitValue( 0.6f ); m_slide_dec_knob->move( 210, 75 ); m_slide_dec_knob->setHintText( tr( "Slide Decay:" ) + " ", "" ); m_slide_dec_knob->setLabel( tr( "SLIDE" ) ); m_vco_fine_detune_knob = new knob( knobBright_26, this, tr( "VCO fine detuning" ), _track ); m_vco_fine_detune_knob->setRange( -100.0f, 100.0f, 1.0f ); m_vco_fine_detune_knob->setInitValue( 0.0f ); m_vco_fine_detune_knob->move( 165, 75 ); m_vco_fine_detune_knob->setHintText( tr( "VCO Fine Detuning:" ) + " ", "cents" ); m_vco_fine_detune_knob->setLabel( tr( "DETUNE" ) ); m_dist_knob = new knob( knobBright_26, this, tr( "Distortion" ), _track ); m_dist_knob->setRange( 0.0f, 1.0f, 0.01f ); // Originally [0,1.0] m_dist_knob->setInitValue( 0.0f ); m_dist_knob->move( 210, 190 ); m_dist_knob->setHintText( tr( "DIST:" ) + " ", "" ); m_dist_knob->setLabel( tr( "DIST" ) ); m_wave_knob = new knob( knobBright_26, this, tr( "Waveform" ), _track ); m_wave_knob->setRange( 0.0f, 5.0f, 1.0f ); // Originally [0,1.0] m_wave_knob->setInitValue( 0.0f ); m_wave_knob->move( 120, 75 ); m_wave_knob->setHintText( tr( "WAVE:" ) + " ", "" ); m_wave_knob->setLabel( tr( "WAVE" ) ); setAutoFillBackground( TRUE ); QPalette pal; pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) ); setPalette( pal ); connect( m_vcf_cut_knob, SIGNAL( valueChanged( float ) ), this, SLOT ( filterChanged( float ) ) ); connect( m_vcf_res_knob, SIGNAL( valueChanged( float ) ), this, SLOT ( filterChanged( float ) ) ); connect( m_vcf_mod_knob, SIGNAL( valueChanged( float ) ), this, SLOT ( filterChanged( float ) ) ); connect( m_vcf_dec_knob, SIGNAL( valueChanged( float ) ), this, SLOT ( filterChanged( float ) ) ); connect( m_vco_fine_detune_knob, SIGNAL( valueChanged( float ) ), this, SLOT ( detuneChanged( float) ) ); connect( m_db24Toggle, SIGNAL( toggled( bool ) ), this, SLOT ( db24Toggled( bool) ) ); connect( m_dist_knob, SIGNAL( valueChanged(float) ), this, SLOT ( filterChanged( float ) ) ); connect( m_wave_knob, SIGNAL( valueChanged( float ) ), this, SLOT ( waveChanged( float ) ) ); } polyb302Synth::~polyb302Synth() { } void polyb302Synth::saveSettings( QDomDocument & _doc, QDomElement & _this ) { m_vcf_cut_knob->saveSettings( _doc, _this, "vcf_cut" ); m_vcf_res_knob->saveSettings( _doc, _this, "vcf_res" ); m_vcf_mod_knob->saveSettings( _doc, _this, "vcf_mod" ); m_vcf_dec_knob->saveSettings( _doc, _this, "vcf_dec" ); m_vco_fine_detune_knob->saveSettings( _doc, _this, "vco_detune" ); m_wave_knob->saveSettings( _doc, _this, "shape" ); m_dist_knob->saveSettings( _doc, _this, "dist" ); m_slide_dec_knob->saveSettings( _doc, _this, "slide_dec" ); // m_slideToggle->saveSettings( _doc, _this, "slide" ); // m_deadToggle->saveSettings( _doc, _this, "dead" ); m_db24Toggle->saveSettings( _doc, _this, "db24"); } void polyb302Synth::loadSettings( const QDomElement & _this ) { m_vcf_cut_knob->loadSettings( _this, "vcf_cut" ); m_vcf_res_knob->loadSettings( _this, "vcf_res" ); m_vcf_mod_knob->loadSettings( _this, "vcf_mod" ); m_vcf_dec_knob->loadSettings( _this, "vcf_dec" ); m_vco_fine_detune_knob->loadSettings( _this, "vco_detune" ); m_dist_knob->loadSettings( _this, "dist" ); m_wave_knob->loadSettings( _this, "shape" ); m_slide_dec_knob->loadSettings( _this, "slide_dec" ); // m_slideToggle->loadSettings( _this, "slide" ); // m_deadToggle->loadSettings( _this, "dead" ); m_db24Toggle->loadSettings( _this, "db24" ); } QString polyb302Synth::nodeName( void ) const { return( polyb302_plugin_descriptor.name ); } void polyb302Synth::playNote( notePlayHandle * _n, bool ) { //int nidx = _n->index(); //if( _n->nphsOfInstrumentTrack(_n->getInstrumentTrack()).first() != _n ) //if( _n->released() && _n->nphsOfInstrumentTrack( _n->getInstrumentTrack() ).count() > 1 ) // return; /* if (_n->released() ) { if( notePlayHandle::nphsOfInstrumentTrack( getInstrumentTrack() ).size() > 0 && notePlayHandle::nphsOfInstrumentTrack( getInstrumentTrack(), TRUE ).last() == _n ) { return; } } */ handleState * hstate; if( !_n->m_pluginData ) { hstate = new handleState( this ); _n->m_pluginData = hstate; m_handleStates.push_back( hstate ); } else { hstate = (handleState *)_n->m_pluginData; } if( _n->totalFramesPlayed() <= hstate->m_lastFramesPlayed ) { // TODO: Try moving to the if() below // if( m_deadToggle->value() == 0 ) { hstate->m_sample_cnt = 0; hstate->m_vca_mode = 0; hstate->m_vca_a = 0; } // Adjust inc on SampRate change or detuning change hstate->m_vco_inc = hstate->m_vco_detune / engine::getMixer()->sampleRate(); // Initiate Slide // TODO: Break out into function, // should be called again on detuneChanged if( hstate->m_vco_slideinc ) { hstate->m_vco_slide = hstate->m_vco_inc - hstate->m_vco_slideinc; hstate->m_vco_slidebase = hstate->m_vco_inc; hstate->m_vco_slideinc = 0; } else { hstate->m_vco_slide = 0; } // End break-out // Slide note, save inc for next note // if( m_slideToggle->value() ) // { // hstate->m_vco_slideinc = hstate->m_vco_inc; // May need to equal m_vco_slidebase+m_vco_slide if last // note slid // } hstate->recalcFilter(); // if( m_deadToggle->value() == 0 ) { // Swap next two blocks?? hstate->m_vcf->playNote(); // Ensure envelope is recalculated hstate->m_vcf_envpos = ENVINC; // Double Check hstate->m_vca_mode = 0; hstate->m_vca_a = 0.0; } } const fpp_t frames = _n->framesLeftForCurrentPeriod(); sampleFrame * buf = new sampleFrame[frames]; hstate->process( buf, frames, _n->frequency() ); getInstrumentTrack()->processAudioBuffer( buf, frames, _n ); delete[] buf; hstate->m_lastFramesPlayed = _n->totalFramesPlayed(); } void polyb302Synth::deleteNotePluginData( notePlayHandle * _n ) { handleState * hstate = (handleState *)_n->m_pluginData; m_handleStates.removeAll( hstate ); delete hstate; } void polyb302Synth::db24Toggled( bool ) { for( QList::iterator it = m_handleStates.begin(); it != m_handleStates.end(); ++it ) { ( *it )->db24Toggled(); } } void polyb302Synth::detuneChanged( float ) { for( QList::iterator it = m_handleStates.begin(); it != m_handleStates.end(); ++it ) { ( *it )->detuneChanged(); } } // TODO: Split into one function per knob. envdecay doesn't require // recalcFilter. void polyb302Synth::filterChanged( float ) { for( QList::iterator it = m_handleStates.begin(); it != m_handleStates.end(); ++it ) { ( *it )->filterChanged(); } } // TODO: Set m_vco_shape in here. void polyb302Synth::waveChanged( float ) { switch( (int)rint( m_wave_knob->value() ) ) { case 0: m_wave_knob->setHintText( tr( "Sawtooth " ), "" ); break; case 1: m_wave_knob->setHintText( tr( "Inverted Sawtooth " ), "" ); break; case 2: m_wave_knob->setHintText( tr( "Triangle " ), "" ); break; case 3: m_wave_knob->setHintText( tr( "Square " ), "" ); break; case 4: m_wave_knob->setHintText( tr( "Rounded Square " ), "" ); break; case 5: m_wave_knob->setHintText( tr( "Moog " ), "" ); break; } } polyb302Synth::handleState::handleState( const polyb302Synth * _synth ) { m_vco_inc = 0.0; m_vco_c = 0; m_vco_k = 0; m_vco_slide = 0; m_vco_slideinc = 0; m_fs.cutoff = 0; m_fs.envmod = 0; m_fs.reso = 0; m_fs.envdecay = 0; m_fs.dist = 0; m_vcf_envpos = ENVINC; m_vco_detune = 0; // Start VCA on an attack. m_vca_mode = 0; m_vca_a = 0; //m_vca_attack = 1.0 - 0.94406088; m_vca_attack = 1.0 - 0.96406088; m_vca_decay = 0.99897516; m_vco_shape = SAWTOOTH; // Experimenting between original (0.5) and 1.0 m_vca_a0 = 0.5; if( _synth->m_db24Toggle->isChecked() ) { m_vcf = new lb302Filter3Pole( &m_fs ); } else { m_vcf = new lb302FilterIIR2( &m_fs ); } recalcFilter(); m_lastFramesPlayed = 0; m_synth = _synth; filterChanged(); detuneChanged(); } polyb302Synth::handleState::~handleState() { delete m_vcf; } void polyb302Synth::handleState::db24Toggled( void ) { delete m_vcf; if( m_synth->m_db24Toggle->isChecked() ) { m_vcf = new lb302Filter3Pole( &m_fs ); } else { m_vcf = new lb302FilterIIR2( &m_fs ); } recalcFilter(); } void polyb302Synth::handleState::detuneChanged( void ) { m_vco_detune = powf( 2.0f, (float)m_synth->m_vco_fine_detune_knob->value() / 1200.0f ); m_vco_inc = m_vco_detune / engine::getMixer()->sampleRate(); // If a slide note is pending, if( m_vco_slideinc ) { m_vco_slideinc = m_vco_inc; } // If currently sliding, // May need to rescale m_vco_slide as well if( m_vco_slide ) { m_vco_slidebase = m_vco_detune / engine::getMixer()->sampleRate(); } } // TODO: Split into one function per knob. envdecay doesn't require // recalcFilter. void polyb302Synth::handleState::filterChanged( void ) { m_fs.cutoff = m_synth->m_vcf_cut_knob->value(); m_fs.reso = m_synth->m_vcf_res_knob->value(); m_fs.envmod = m_synth->m_vcf_mod_knob->value(); m_fs.dist = LB_DIST_RATIO * m_synth->m_dist_knob->value(); float d = 0.2 + 2.3 * m_synth->m_vcf_dec_knob->value(); d *= engine::getMixer()->sampleRate(); // decay is 0.1 to the 1/d * ENVINC m_fs.envdecay = pow( 0.1, ENVINC / d ); // vcf_envdecay is now adjusted for both // sampling rate and ENVINC recalcFilter(); } // OBSOLETE. Break apart once we get Q_OBJECT to work. >:[ void polyb302Synth::handleState::recalcFilter( void ) { m_vcf->recalc(); // THIS IS OLD 3pole/24dB code, I may reintegrate it. Don't need it // right now. Should be toggled by LB_24_RES_TRICK at the moment. /*kfcn = 2.0 * (((vcf_cutoff*3000))) / m_LB_HZ; kp = ((-2.7528*kfcn + 3.0429)*kfcn + 1.718)*kfcn - 0.9984; kp1 = kp+1.0; kp1h = 0.5*kp1; kres = (((vcf_reso))) * (((-2.7079*kp1 + 10.963)*kp1 - 14.934)*kp1 + 8.4974); value = 1.0+( (((0))) *(1.5 + 2.0*kres*(1.0-kfcn))); // ENVMOD was DIST*/ m_vcf_envpos = ENVINC; // Trigger filter update in process() } void polyb302Synth::handleState::process( sampleFrame * _outbuf, const Uint32 _size, float _freq ) { for( Uint32 i = 0; i < _size; i++ ) { // update m_vcf if( m_vcf_envpos >= ENVINC ) { m_vcf->envRecalc(); m_vcf_envpos = 0; if( m_vco_slide ) { m_vco_inc = m_vco_slidebase - m_vco_slide; // Calculate coeff from dec_knob on knob change. // TODO: Adjust for ENVINC m_vco_slide *= 0.9 + ( m_synth->m_slide_dec_knob->value() * 0.0999 ); } } m_sample_cnt++; m_vcf_envpos++; // update vco m_vco_c += m_vco_inc * _freq; if( m_vco_c > 0.5 ) { m_vco_c -= 1.0; } switch( (int)rint( m_synth->m_wave_knob->value() ) ) { case 0: m_vco_shape = SAWTOOTH; break; case 1: m_vco_shape = INVERTED_SAWTOOTH; break; case 2: m_vco_shape = TRIANGLE; break; case 3: m_vco_shape = SQUARE; break; case 4: m_vco_shape = ROUND_SQUARE; break; case 5: m_vco_shape = MOOG; break; default: m_vco_shape = SAWTOOTH; break; } // add m_vco_shape_param the changes the shape of each curve. // merge sawtooths with triangle and square with round square? switch( m_vco_shape ) { case SAWTOOTH: // p0: curviness of line // Is this sawtooth backwards? m_vco_k = m_vco_c; break; case INVERTED_SAWTOOTH: // p0: curviness of line // Is this sawtooth backwards? m_vco_k = -m_vco_c; break; // TODO: I think TRIANGLE is broken. // p0: duty rev.saw<->triangle<->saw p1: curviness case TRIANGLE: m_vco_k = m_vco_c * 2.0 + 0.5; if( m_vco_k > 0.5 ) { m_vco_k = 1.0 - m_vco_k; } break; case SQUARE: // p0: slope of top m_vco_k = ( m_vco_c < 0 ) ? 0.5 : -0.5; break; case ROUND_SQUARE: // p0: width of round m_vco_k = ( m_vco_c < 0 ) ? sqrtf( 1 - m_vco_c * m_vco_c * 4 ) - 0.5 : -0.5; break; // Maybe the fall should be exponential/sinsoidal // instead of quadric. case MOOG: // [-0.5, 0]: Rise, [0,0.25]: Slope down, // [0.25,0.5]: Low m_vco_k = m_vco_c * 2.0 + 0.5; if( m_vco_k > 1.0 ) { m_vco_k = -0.5; } else if( m_vco_k > 0.5 ) { float w = 2 * ( m_vco_k - 0.5 ) - 1; m_vco_k = 0.5 - sqrtf( 1 - w * w ); // MOOG wave gets filtered away m_vco_k *= 2.0; } break; } // Write out samples. #ifdef LB_FILTERED float samp = m_vcf->process( m_vco_k ) * 2.0 * m_vca_a; #else float samp = m_vco_k * m_vca_a; #endif for( int c = 0; c < DEFAULT_CHANNELS; c++ ) { _outbuf[i][c] = samp; } // Handle Envelope // TODO: Add decay once I figure out how to extend past the end // of a note. if( m_vca_mode == 0 ) { m_vca_a += ( m_vca_a0 - m_vca_a ) * m_vca_attack; if( m_sample_cnt >= 0.5 * engine::getMixer()->sampleRate() ) { m_vca_mode = 2; } } else if( m_vca_mode == 1 ) { m_vca_a *= m_vca_decay; // the following line actually speeds up processing if( m_vca_a < 1 / 65536.0 ) { m_vca_a = 0; m_vca_mode = 2; } } } }