mirror of
https://github.com/LMMS/lmms.git
synced 2026-01-25 06:48:55 -05:00
git-svn-id: https://lmms.svn.sf.net/svnroot/lmms/trunk/lmms@507 0778d3d1-df1d-0410-868b-ea421aaaa00d
932 lines
21 KiB
C++
932 lines
21 KiB
C++
/*
|
|
* polyb302.cpp - implementation of instrument polyb302, an attempt to emulate
|
|
* the Roland TB303 bass synth
|
|
*
|
|
* Copyright (c) 2006-2007 Paul Giblock <pgib/at/users.sourceforge.net>
|
|
*
|
|
* 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 <jasp00/at/users.sourceforge.net>",
|
|
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<instrumentTrack *>( _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" ) );
|
|
|
|
|
|
#ifndef QT3
|
|
setAutoFillBackground( TRUE );
|
|
QPalette pal;
|
|
pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap(
|
|
"artwork" ) );
|
|
setPalette( pal );
|
|
#else
|
|
setErasePixmap( PLUGIN_NAME::getIconPixmap( "artwork" ) );
|
|
#endif
|
|
|
|
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( vlist<handleState *>::iterator it = m_handleStates.begin();
|
|
it != m_handleStates.end(); ++it )
|
|
{
|
|
( *it )->db24Toggled();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void polyb302Synth::detuneChanged( float )
|
|
{
|
|
for( vlist<handleState *>::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( vlist<handleState *>::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;
|
|
|
|
m_vca_mode = 2;
|
|
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++;
|
|
|
|
// 01/21/07 Changed to VCF -> VCA instead of VCA -> VCF
|
|
#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;
|
|
}
|
|
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Make it louder. For the better?
|
|
//m_vco_k*=2.0;
|
|
|
|
// Handle Envelope
|
|
// TODO: Add decay once I figure out how to extend past the end
|
|
// of a note.
|
|
if( m_sample_cnt >= 0.5 * engine::getMixer()->sampleRate() )
|
|
{
|
|
m_vca_mode = 2;
|
|
}
|
|
if( m_vca_mode == 0 )
|
|
{
|
|
m_vca_a += ( m_vca_a0 - m_vca_a ) * m_vca_attack;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|