/* * lb302.cpp - implementation of class lb302 which is a bass synth attempting * to emulate the Roland TB303 bass synth * * Copyright (c) 2006-2008 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 #include "lb302.h" #include "engine.h" #include "instrument_play_handle.h" #include "instrument_track.h" #include "knob.h" #include "note_play_handle.h" #include "templates.h" #include "audio_port.h" #undef SINGLE_SOURCE_COMPILE #include "embed.cpp" #include "lb302.moc" // Envelope Recalculation period #define ENVINC 64 // // New config // #define LB_24_IGNORE_ENVELOPE #define LB_FILTERED //#define LB_DECAY //#define LB_24_RES_TRICK #define LB_DIST_RATIO 4.0 #define LB_24_VOL_ADJUST 3.0 //#define LB_DECAY_NOTES #define LB_DEBUG #ifdef LB_DEBUG #include #endif // // Old config // //#define engine::getMixer()->processingSampleRate() 44100.0f extern "C" { plugin::descriptor PLUGIN_EXPORT lb302_plugin_descriptor = { STRINGIFY_PLUGIN_NAME( PLUGIN_NAME ), "LB302", QT_TRANSLATE_NOOP( "pluginBrowser", "Incomplete monophonic immitation tb303" ), "Paul Giblock ", 0x0100, plugin::Instrument, new pluginPixmapLoader( "logo" ), NULL }; } // // lb302Filter // lb302Filter::lb302Filter(lb302FilterKnobState* p_fs) : fs(p_fs), vcf_c0(0), vcf_e0(0), vcf_e1(0) { }; void lb302Filter::recalc() { vcf_e1 = exp(6.109 + 1.5876*(fs->envmod) + 2.1553*(fs->cutoff) - 1.2*(1.0-(fs->reso))); vcf_e0 = exp(5.613 - 0.8*(fs->envmod) + 2.1553*(fs->cutoff) - 0.7696*(1.0-(fs->reso))); vcf_e0*=M_PI/engine::getMixer()->processingSampleRate(); vcf_e1*=M_PI/engine::getMixer()->processingSampleRate(); vcf_e1 -= vcf_e0; vcf_rescoeff = exp(-1.20 + 3.455*(fs->reso)); }; void lb302Filter::envRecalc() { vcf_c0 *= fs->envdecay; // Filter Decay. vcf_decay is adjusted for Hz and ENVINC // vcf_rescoeff = exp(-1.20 + 3.455*(fs->reso)); moved above }; void lb302Filter::playNote() { vcf_c0 = vcf_e1; } // // lb302FilterIIR2 // lb302FilterIIR2::lb302FilterIIR2(lb302FilterKnobState* p_fs) : lb302Filter(p_fs), vcf_d1(0), vcf_d2(0), vcf_a(0), vcf_b(0), vcf_c(1) { m_dist = new effectLib::distortion<>( 1.0, 1.0f); }; lb302FilterIIR2::~lb302FilterIIR2() { delete m_dist; } void lb302FilterIIR2::recalc() { lb302Filter::recalc(); //m_dist->setThreshold(0.5+(fs->dist*2.0)); m_dist->setThreshold(fs->dist*75.0); }; void lb302FilterIIR2::envRecalc() { float k, w; lb302Filter::envRecalc(); w = vcf_e0 + vcf_c0; // e0 is adjusted for Hz and doesn't need ENVINC k = exp(-w/vcf_rescoeff); // Does this mean c0 is inheritantly? vcf_a = 2.0*cos(2.0*w) * k; vcf_b = -k*k; vcf_c = 1.0 - vcf_a - vcf_b; } float lb302FilterIIR2::process(const float& samp) { float ret = vcf_a*vcf_d1 + vcf_b*vcf_d2 + vcf_c*samp; // Delayed samples for filter vcf_d2 = vcf_d1; vcf_d1 = ret; if(fs->dist > 0) ret=m_dist->nextSample(ret); // output = IIR2 + dry return ret; } void lb302FilterIIR2::getState(lb302FilterState* fs) { fs->iir.vcf_c0 = vcf_c0; fs->iir.vcf_a = vcf_a; fs->iir.vcf_b = vcf_b; fs->iir.vcf_c = vcf_c; fs->iir.vcf_d1 = vcf_d1; fs->iir.vcf_d2 = vcf_d2; } void lb302FilterIIR2::setState(const lb302FilterState* fs) { vcf_c0 = fs->iir.vcf_c0; vcf_a = fs->iir.vcf_a; vcf_b = fs->iir.vcf_b; vcf_c = fs->iir.vcf_c; vcf_d1 = fs->iir.vcf_d1; vcf_d2 = fs->iir.vcf_d2; } // // lb302Filter3Pole // lb302Filter3Pole::lb302Filter3Pole(lb302FilterKnobState *p_fs) : lb302Filter(p_fs), ay1(0), ay2(0), aout(0), lastin(0) { }; void lb302Filter3Pole::recalc() { // DO NOT CALL BASE CLASS vcf_e0 = 0.000001; vcf_e1 = 1.0; } // TODO: Try using k instead of vcf_reso void lb302Filter3Pole::envRecalc() { float w,k; float kfco; lb302Filter::envRecalc(); // e0 is adjusted for Hz and doesn't need ENVINC w = vcf_e0 + vcf_c0; k = (fs->cutoff > 0.975)?0.975:fs->cutoff; kfco = 50.f + (k)*((2300.f-1600.f*(fs->envmod))+(w) * (700.f+1500.f*(k)+(1500.f+(k)*(engine::getMixer()->processingSampleRate()/2.f-6000.f)) * (fs->envmod)) ); //+iacc*(.3+.7*kfco*kenvmod)*kaccent*kaccurve*2000 #ifdef LB_24_IGNORE_ENVELOPE // kfcn = fs->cutoff; kfcn = 2.0 * kfco / engine::getMixer()->processingSampleRate(); #else kfcn = w; #endif kp = ((-2.7528*kfcn + 3.0429)*kfcn + 1.718)*kfcn - 0.9984; kp1 = kp+1.0; kp1h = 0.5*kp1; #ifdef LB_24_RES_TRICK k = exp(-w/vcf_rescoeff); kres = (((k))) * (((-2.7079*kp1 + 10.963)*kp1 - 14.934)*kp1 + 8.4974); #else kres = (((fs->reso))) * (((-2.7079*kp1 + 10.963)*kp1 - 14.934)*kp1 + 8.4974); #endif value = 1.0+( (fs->dist) *(1.5 + 2.0*kres*(1.0-kfcn))); // ENVMOD was DIST } float lb302Filter3Pole::process(const float& samp) { float ax1 = lastin; float ay11 = ay1; float ay31 = ay2; lastin = (samp) - tanh(kres*aout); ay1 = kp1h * (lastin+ax1) - kp*ay1; ay2 = kp1h * (ay1 + ay11) - kp*ay2; aout = kp1h * (ay2 + ay31) - kp*aout; return tanh(aout*value)*LB_24_VOL_ADJUST/(1.0+fs->dist); } void lb302Filter3Pole::getState(lb302FilterState* fs) { fs->pole.aout = aout; fs->pole.vcf_c0 = vcf_c0; fs->pole.kp = kp; fs->pole.kp1h = kp1h; fs->pole.kres = kres; fs->pole.ay1 = ay1; fs->pole.ay2 = ay2; fs->pole.lastin = lastin; fs->pole.value = value; } void lb302Filter3Pole::setState(const lb302FilterState* fs) { aout = fs->pole.aout; vcf_c0 = fs->pole.vcf_c0; kp = fs->pole.kp; kp1h = fs->pole.kp1h; kres = fs->pole.kres; ay1 = fs->pole.ay1; ay2 = fs->pole.ay2; lastin = fs->pole.lastin; value = fs->pole.value; } // // LBSynth // lb302Synth::lb302Synth( instrumentTrack * _instrumentTrack ) : instrument( _instrumentTrack, &lb302_plugin_descriptor ), vcf_cut_knob( 0.75f, 0.0f, 1.5f, 0.005f, this, tr( "VCF Cutoff Frequency" ) ), vcf_res_knob( 0.75f, 0.0f, 1.25f, 0.005f, this, tr( "VCF Resonance" ) ), vcf_mod_knob( 0.1f, 0.0f, 1.0f, 0.005f, this, tr( "VCF Envelope Mod" ) ), vcf_dec_knob( 0.1f, 0.0f, 1.0f, 0.005f, this, tr( "VCF Envelope Decay" ) ), vco_fine_detune_knob( 0.0f, -100.0f, 100.0f, 1.0f, this, tr( "VCO Fine Detuning:") ), dist_knob( 0.0f, 0.0f, 1.0f, 0.01f, this, tr( "Distortion" ) ), wave_knob( 0.0f, 0.0f, 5.0f, 1.0f, this, tr( "Waveform" ) ), slide_dec_knob( 0.6f, 0.0f, 1.0f, 0.005f, this, tr( "Slide Decay" ) ), slideToggle( FALSE, this, tr( "Slide" ) ), accentToggle( FALSE, this, tr( "Accent" ) ), deadToggle( FALSE, this, tr( "Dead" ) ), db24Toggle( FALSE, this, tr( "24dB/oct Filter" ) ) { connect( engine::getMixer(), SIGNAL( sampleRateChanged( ) ), this, SLOT ( filterChanged( ) ) ); connect( &vcf_cut_knob, SIGNAL( dataChanged( ) ), this, SLOT ( filterChanged( ) ) ); connect( &vcf_res_knob, SIGNAL( dataChanged( ) ), this, SLOT ( filterChanged( ) ) ); connect( &vcf_mod_knob, SIGNAL( dataChanged( ) ), this, SLOT ( filterChanged( ) ) ); connect( &vcf_dec_knob, SIGNAL( dataChanged( ) ), this, SLOT ( filterChanged( ) ) ); connect( &vco_fine_detune_knob, SIGNAL( dataChanged( ) ), this, SLOT ( filterChanged( ) ) ); connect( &db24Toggle, SIGNAL( dataChanged( ) ), this, SLOT ( db24Toggled( ) ) ); connect( &dist_knob, SIGNAL( dataChanged( ) ), this, SLOT ( filterChanged( ))); connect( &wave_knob, SIGNAL( dataChanged( ) ), this, SLOT ( waveChanged( ))); // SYNTH vco_inc = 0.0; vco_c = 0; vco_k = 0; vco_slide = 0; vco_slideinc = 0; vco_slidebase = 0; fs.cutoff = 0; fs.envmod = 0; fs.reso = 0; fs.envdecay = 0; fs.dist = 0; vcf_envpos = ENVINC; vco_detune = 0; // Start VCA on an attack. vca_mode = 3; vca_a = 0; //vca_attack = 1.0 - 0.94406088; vca_attack = 1.0 - 0.96406088; vca_decay = 0.99897516; vco_shape = SAWTOOTH; // Experimenting with a0 between original (0.5) and 1.0 vca_a0 = 0.5; vca_a = 9; vca_mode = 3; vcf = new lb302FilterIIR2(&fs); use_hold_note = false; sample_cnt = 0; release_frame = 1<<24; catch_frame = 0; catch_decay = 0; recalcFilter(); lastFramesPlayed = 1; // because we subtract 1 later last_offset = 0; period_states = NULL; period_states_cnt = 0; filterChanged(); detuneChanged(); } lb302Synth::~lb302Synth() { delete vcf; } void lb302Synth::saveSettings( QDomDocument & _doc, QDomElement & _this ) { vcf_cut_knob.saveSettings( _doc, _this, "vcf_cut" ); vcf_res_knob.saveSettings( _doc, _this, "vcf_res" ); vcf_mod_knob.saveSettings( _doc, _this, "vcf_mod" ); vcf_dec_knob.saveSettings( _doc, _this, "vcf_dec" ); vco_fine_detune_knob.saveSettings( _doc, _this, "vco_detune" ); wave_knob.saveSettings( _doc, _this, "shape"); dist_knob.saveSettings( _doc, _this, "dist"); slide_dec_knob.saveSettings( _doc, _this, "slide_dec"); slideToggle.saveSettings( _doc, _this, "slide"); deadToggle.saveSettings( _doc, _this, "dead"); db24Toggle.saveSettings( _doc, _this, "db24"); } void lb302Synth::loadSettings( const QDomElement & _this ) { vcf_cut_knob.loadSettings( _this, "vcf_cut" ); vcf_res_knob.loadSettings( _this, "vcf_res" ); vcf_mod_knob.loadSettings( _this, "vcf_mod" ); vcf_dec_knob.loadSettings( _this, "vcf_dec" ); vco_fine_detune_knob.loadSettings( _this, "vco_detune" ); dist_knob.loadSettings( _this, "dist"); wave_knob.loadSettings( _this, "shape"); slide_dec_knob.loadSettings( _this, "slide_dec"); slideToggle.loadSettings( _this, "slide"); deadToggle.loadSettings( _this, "dead"); db24Toggle.loadSettings( _this, "db24"); filterChanged(); detuneChanged(); } // TODO: Split into one function per knob. envdecay doesn't require // recalcFilter. void lb302Synth::filterChanged( void ) { fs.cutoff = vcf_cut_knob.value(); fs.reso = vcf_res_knob.value(); fs.envmod = vcf_mod_knob.value(); fs.dist = LB_DIST_RATIO*dist_knob.value(); float d = 0.2 + (2.3*vcf_dec_knob.value()); d *= engine::getMixer()->processingSampleRate(); // d *= smpl rate fs.envdecay = pow(0.1, 1.0/d * ENVINC); // decay is 0.1 to the 1/d * ENVINC // vcf_envdecay is now adjusted for both // sampling rate and ENVINC recalcFilter(); } void lb302Synth::db24Toggled( void ) { delete vcf; if(db24Toggle.value()) { vcf = new lb302Filter3Pole(&fs); } else { vcf = new lb302FilterIIR2(&fs); } recalcFilter(); } void lb302Synth::detuneChanged( void ) { float freq = vco_inc*engine::getMixer()->processingSampleRate()/vco_detune; float slidebase_freq=0; if(vco_slide) { slidebase_freq = vco_slidebase*engine::getMixer()->processingSampleRate()/vco_detune; } vco_detune = powf(2.0f, (float)vco_fine_detune_knob.value()/1200.0f); vco_inc = freq*vco_detune/engine::getMixer()->processingSampleRate(); // If a slide note is pending, if(vco_slideinc) vco_slideinc = vco_inc; // If currently sliding, // May need to rescale vco_slide as well if(vco_slide) vco_slidebase = slidebase_freq*vco_detune/engine::getMixer()->processingSampleRate(); } // TODO: Set vco_shape in here. // setHintText is impossible from the synth now?? void lb302Synth::waveChanged( void ) { /* switch(int(rint(wave_knob.value()))) { case 0: wave_knob.setHintText(tr("Sawtooth "),""); break; case 1: wave_knob.setHintText(tr("Inverted Sawtooth "),""); break; case 2: wave_knob.setHintText(tr("Triangle "),""); break; case 3: wave_knob.setHintText(tr("Square "),""); break; case 4: wave_knob.setHintText(tr("Rounded Square "),""); break; case 5: wave_knob.setHintText(tr("Moog "),""); break; } */ } QString lb302Synth::nodeName( void ) const { return( lb302_plugin_descriptor.name ); } // OBSOLETE. Break apart once we get Q_OBJECT to work. >:[ void lb302Synth::recalcFilter() { 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))) / engine::getMixer()->processingSampleRate(); 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*/ vcf_envpos = ENVINC; // Trigger filter update in process() } inline int MIN(int a, int b) { return (a= ENVINC) { vcf->envRecalc(); vcf_envpos = 0; if (vco_slide) { vco_inc=vco_slidebase-vco_slide; // Calculate coeff from dec_knob on knob change. vco_slide*= 0.9+(slide_dec_knob.value()*0.0999); // TODO: Adjust for Hz and ENVINC } } sample_cnt++; vcf_envpos++; // unused variables //float old_vco_k = vco_k; //bool looking; int decay_frames = 128; // update vco vco_c += vco_inc; if(vco_c > 0.5) vco_c -= 1.0; if (catch_decay > 0) { if (catch_decay < decay_frames) { catch_decay++; } else if (use_hold_note) { use_hold_note = false; initNote(&hold_note); } } switch(int(rint(wave_knob.value()))) { case 0: vco_shape = SAWTOOTH; break; case 1: vco_shape = INVERTED_SAWTOOTH; break; case 2: vco_shape = TRIANGLE; break; case 3: vco_shape = SQUARE; break; case 4: vco_shape = ROUND_SQUARE; break; case 5: vco_shape = MOOG; break; default: vco_shape = SAWTOOTH; break; } // add vco_shape_param the changes the shape of each curve. // merge sawtooths with triangle and square with round square? switch (vco_shape) { case SAWTOOTH: // p0: curviness of line vco_k = vco_c; // Is this sawtooth backwards? break; case INVERTED_SAWTOOTH: // p0: curviness of line vco_k = -vco_c; // Is this sawtooth backwards? break; case TRIANGLE: // p0: duty rev.saw<->triangle<->saw p1: curviness vco_k = (vco_c*2.0)+0.5; if (vco_k>0.5) vco_k = 1.0- vco_k; break; case SQUARE: // p0: slope of top vco_k = (vco_c<0)?0.5:-0.5; break; case ROUND_SQUARE: // p0: width of round vco_k = (vco_c<0)?(sqrtf(1-(vco_c*vco_c*4))-0.5):-0.5; break; case MOOG: // Maybe the fall should be exponential/sinsoidal instead of quadric. // [-0.5, 0]: Rise, [0,0.25]: Slope down, [0.25,0.5]: Low vco_k = (vco_c*2.0)+0.5; if (vco_k>1.0) { vco_k = -0.5 ; } else if (vco_k>0.5) { w = 2.0*(vco_k-0.5)-1.0; vco_k = 0.5 - sqrtf(1.0-(w*w)); } vco_k *= 2.0; // MOOG wave gets filtered away break; } //vca_a = 0.5; // Write out samples. #ifdef LB_FILTERED samp = vcf->process(vco_k)*2.0*vca_a; #else samp = vco_k*vca_a; #endif /* float releaseFrames = desiredReleaseFrames(); samp *= (releaseFrames - catch_decay)/releaseFrames; */ samp *= (float)(decay_frames - catch_decay)/(float)decay_frames; for(int c=0; c=release_frame) { vca_mode=1; } // Handle Envelope if(vca_mode==0) { vca_a+=(vca_a0-vca_a)*vca_attack; if(sample_cnt>=0.5*engine::getMixer()->processingSampleRate()) vca_mode = 2; } else if(vca_mode == 1) { vca_a *= vca_decay; // the following line actually speeds up processing if(vca_a < (1/65536.0)) { vca_a = 0; vca_mode = 3; } } // Store state period_states[i].vco_c = vco_c; period_states[i].vca_a = vca_a; // Doesn't change anything (currently) period_states[i].vca_mode = vca_mode; // Doesn't change anything (currently) period_states[i].sample_cnt = sample_cnt; // Doesn't change anything (currently) vcf->getState(&period_states[i].fs); } return 1; } /* Prepares the active LB302 note. I separated this into a function because it * needs to be called onplayNote() when a new note is started. It also needs * to be called from process() when a prior edge-to-edge note is done releasing. */ void lb302Synth::initNote( lb302Note *n) { catch_decay = 0; vco_inc = n->vco_inc; // TODO: Try moving to the if() below // Why vca_mode==3? if(n->dead == 0 || (vca_mode==1 || vca_mode==3)) { sample_cnt = 0; vca_mode = 0; vca_a = 0; } else { vca_mode = 2; } // Initiate Slide // TODO: Break out into function, should be called again on detuneChanged if (vco_slideinc) { vco_slide = vco_inc-vco_slideinc; vco_slidebase = vco_inc; vco_slideinc = 0; } else { vco_slide = 0; } // End break-out // Slide note, save inc for next note if (slideToggle.value()) { vco_slideinc = vco_inc; // May need to equal vco_slidebase+vco_slide if last note slid } recalcFilter(); if(n->dead ==0){ // Swap next two blocks?? vcf->playNote(); // Ensure envelope is recalculated vcf_envpos = ENVINC; // Double Check vca_mode = 0; vca_a = 0.0; } } void lb302Synth::playNote( notePlayHandle * _n, bool, sampleFrame * _working_buffer ) { fpp_t framesPerPeriod = engine::getMixer()->framesPerPeriod(); // if( _n->arpBaseNote() ) { return; } // number of frames to play - only modified below if we have to play // the rest of an old note fpp_t frames = _n->framesLeftForCurrentPeriod(); // per default we resume at the last played frames - only in // some special-cases (which we catch below) we have to resume // somewhere else f_cnt_t resume_pos = lastFramesPlayed-1; bool decay_note = false; // find out which situation we're in constNotePlayHandleVector v = notePlayHandle::nphsOfInstrumentTrack( getInstrumentTrack(), TRUE ); // more than one note running? if( v.count() > 1 ) { const notePlayHandle * on = v.first(); // oldest note const notePlayHandle * yn = v.last(); // youngest note // are we playing a released note and the new (youngest) note // has taken over successfully (i.e. played more than the // difference of the two offsets)? if ( _n->released() && yn->totalFramesPlayed() >= yn->offset() - on->offset() ) { // then we do not need to play something anymore return; } // have to fill up the frames left to the new note so limit // frames to play for not getting into trouble if( _n != yn && !yn->arpBaseNote() ) { frames = tMin( frames, yn->offset() - on->offset() ); #ifdef LB_DEBUG // should be always true - why? I don't know... ;-) assert( frames > 0 ); #endif } // new note while other notes are running? if( v.count() > 1 && yn == _n && _n->totalFramesPlayed() == 0 ) { // if there had been a previous note whose // offset > _n->offset() it played more frames than // we actually need - therefore clear everything before // the offset of the youngest note, otherwise we get // frames with both waves overlapped engine::getMixer()->clearAudioBuffer( _n->getInstrumentTrack()->getAudioPort()->firstBuffer(), framesPerPeriod - yn->offset(), yn->offset() ); resume_pos = yn->offset() - on->offset() - 1; // make sure we have positive value, otherwise we're // accessing states out of borders while( resume_pos < 0 ) { resume_pos += framesPerPeriod; } decay_note = true; } } #ifdef LB_DEBUG if( _n->released() ) { } else { } #endif // /// Malloc our period history buffer if (period_states == NULL) { period_states = new lb302State[framesPerPeriod]; for (int i=0; i < framesPerPeriod; i++) { period_states[i].vco_c = vco_c; period_states[i].vca_a = vca_a; // Doesn't change anything (currently) period_states[i].vca_mode = vca_mode; // Doesn't change anything (currently) period_states[i].sample_cnt = sample_cnt; // Doesn't change anything (currently) vcf->getState(&period_states[i].fs); } } // now resume at the proper position and process as usual lb302State *state = &period_states[resume_pos]; // Actually resume the state, now that we have the right state object. vco_c = state->vco_c; vca_a = state->vca_a; vca_mode = state->vca_mode; sample_cnt = state->sample_cnt; vcf->setState(&state->fs); // Currently have release/decay disabled // Start the release decay if this is the first release period. //if (_n->released() && catch_decay == 0) // catch_decay = 1; release_frame = _n->framesLeft() - desiredReleaseFrames(); if ( _n->totalFramesPlayed() <= 0 ) { // This code is obsolete, hence the "if false" // Existing note. Allow it to decay. if(deadToggle.value() == 0 && decay_note) { #ifdef LB_DECAY if (catch_decay < 1) { // BEGIN NOT SURE OF... //lb302State *st = &period_states[period_states_cnt-1]; //vca_a = st->vca_a; //sample_cnt = st->sample_cnt; // END NOT SURE OF // Reserve this note for retrigger in process() hold_note.vco_inc = _n->frequency()*vco_detune/engine::getMixer()->processingSampleRate(); // TODO: Use actual sampling rate. hold_note.dead = deadToggle->value(); use_hold_note = true; catch_decay = 1; } #else lb302Note note; note.vco_inc = _n->frequency()*vco_detune/engine::getMixer()->processingSampleRate(); // TODO: Use actual sampling rate. note.dead = deadToggle.value(); initNote(¬e); vca_mode=0; vca_a = state->vca_a; #endif } /// Start a new note. else { lb302Note note; note.vco_inc = _n->frequency()*vco_detune/engine::getMixer()->processingSampleRate(); // TODO: Use actual sampling rate. note.dead = deadToggle.value(); initNote(¬e); use_hold_note = false; } } process( _working_buffer, frames); getInstrumentTrack()->processAudioBuffer( _working_buffer, frames, _n ); lastFramesPlayed = frames; //_n->framesLeftForCurrentPeriod(); //_n->totalFramesPlayed(); } void lb302Synth::deleteNotePluginData( notePlayHandle * _n ) { } pluginView * lb302Synth::instantiateView( QWidget * _parent ) { return( new lb302SynthView( this, _parent ) ); } lb302SynthView::lb302SynthView( instrument * _instrument, QWidget * _parent ) : instrumentView( _instrument, _parent ) { // GUI m_vcfCutKnob = new knob( knobBright_26, this ); m_vcfCutKnob->move( 75, 130 ); m_vcfCutKnob->setHintText( tr( "Cutoff Freq:" ) + " ", "" ); m_vcfCutKnob->setLabel( tr("CUT") ); m_vcfResKnob = new knob( knobBright_26, this ); m_vcfResKnob->move( 120, 130 ); m_vcfResKnob->setHintText( tr( "Resonance:" ) + " ", "" ); m_vcfResKnob->setLabel( tr("RES") ); m_vcfModKnob = new knob( knobBright_26, this ); m_vcfModKnob->move( 165, 130 ); m_vcfModKnob->setHintText( tr( "Env Mod:" ) + " ", "" ); m_vcfModKnob->setLabel( tr("ENV MOD") ); m_vcfDecKnob = new knob( knobBright_26, this ); m_vcfDecKnob->move( 210, 130 ); m_vcfDecKnob->setHintText( tr( "Decay:" ) + " ", "" ); m_vcfDecKnob->setLabel( tr("DEC") ); m_slideToggle = new ledCheckBox( "Slide", this ); m_slideToggle->move( 10, 180 ); m_accentToggle = new ledCheckBox( "Accent", this ); m_accentToggle->move( 10, 200 ); m_accentToggle->setDisabled(true); m_deadToggle = new ledCheckBox( "Dead", this ); m_deadToggle->move( 10, 220 ); m_db24Toggle = new ledCheckBox( "24dB/oct", this ); m_db24Toggle->setWhatsThis( tr( "303-es-que, 24dB/octave, 3 pole filter" ) ); m_db24Toggle->move( 10, 150); m_slideDecKnob = new knob( knobBright_26, this ); m_slideDecKnob->move( 210, 75 ); m_slideDecKnob->setHintText( tr( "Slide Decay:" ) + " ", "" ); m_slideDecKnob->setLabel( tr( "SLIDE")); m_vcoFineDetuneKnob = new knob( knobBright_26, this ); m_vcoFineDetuneKnob->move(165, 75); m_vcoFineDetuneKnob->setHintText( tr( "VCO Fine Detuning:") + " ", "cents"); m_vcoFineDetuneKnob->setLabel( tr( "DETUNE")); m_distKnob = new knob( knobBright_26, this ); m_distKnob->move( 210, 190 ); m_distKnob->setHintText( tr( "DIST:" ) + " ", "" ); m_distKnob->setLabel( tr( "DIST")); m_waveKnob = new knob( knobBright_26, this ); m_waveKnob->move( 120, 75 ); m_waveKnob->setHintText( tr( "WAVE:" ) + " ", "" ); m_waveKnob->setLabel( tr( "WAVE")); setAutoFillBackground( TRUE ); QPalette pal; pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) ); setPalette( pal ); } lb302SynthView::~lb302SynthView() { } void lb302SynthView::modelChanged( void ) { lb302Synth * syn = castModel(); m_vcfCutKnob->setModel( &syn->vcf_cut_knob ); m_vcfResKnob->setModel( &syn->vcf_res_knob ); m_vcfDecKnob->setModel( &syn->vcf_dec_knob ); m_vcfModKnob->setModel( &syn->vcf_mod_knob ); m_slideDecKnob->setModel( &syn->slide_dec_knob ); m_vcoFineDetuneKnob->setModel( &syn->vco_fine_detune_knob ); m_distKnob->setModel( &syn->dist_knob ); m_waveKnob->setModel( &syn->wave_knob ); m_slideToggle->setModel( &syn->slideToggle ); m_accentToggle->setModel( &syn->accentToggle ); m_deadToggle->setModel( &syn->deadToggle ); m_db24Toggle->setModel( &syn->db24Toggle ); } extern "C" { // neccessary for getting instance out of shared lib plugin * PLUGIN_EXPORT lmms_plugin_main( model *, void * _data ) { return( new lb302Synth( static_cast( _data ) ) ); } }