diff --git a/ChangeLog b/ChangeLog index a29145577..f0c558fe3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2008-07-08 Paul Giblock + + * plugins/lb303: + * plugins/lb303/lb303.cpp: + * plugins/lb303/lb303.h: + * plugins/lb303/Makefile.am: + * plugins/Makefile.am: + * configure.in: + First version of lb303, NO backward compatability YET + + * src/gui/controller_connection_dialog.cpp: + Fix MIDI AutoDetect + + * src/gui/widgets/knob.cpp: + Fix occationally missing line after instantiation + 2008-07-07 Paul Giblock * src/gui/controller_connection_dialog.cpp: @@ -11,6 +27,7 @@ - Initial support for pitch-bend, doesn't work during slide. - Next version will probably be the first lb303 - Cleanup, fixed dropped-notes + - Fix pitch-bend while sliding 2008-07-07 Tobias Doerffel diff --git a/configure.in b/configure.in index 2f28d6501..49b23b8af 100644 --- a/configure.in +++ b/configure.in @@ -695,6 +695,7 @@ AC_CONFIG_FILES([Makefile plugins/ladspa_effect/caps/Makefile plugins/ladspa_effect/tap/Makefile plugins/lb302/Makefile + plugins/lb303/Makefile plugins/live_tool/Makefile plugins/midi_import/Makefile plugins/organic/Makefile diff --git a/plugins/Makefile.am b/plugins/Makefile.am index 459887f6f..aafa7bdf2 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -23,6 +23,7 @@ SUBDIRS = \ ladspa_browser \ ladspa_effect \ lb302 \ + lb303 \ live_tool \ midi_import \ organic \ diff --git a/plugins/lb303/Makefile.am b/plugins/lb303/Makefile.am new file mode 100644 index 000000000..db1231e0c --- /dev/null +++ b/plugins/lb303/Makefile.am @@ -0,0 +1,48 @@ +AUTOMAKE_OPTIONS = foreign 1.4 + +PLUGIN_NAME = lb303 + + +INCLUDES = -I$(top_srcdir)/include -I$(top_srcdir)/src/gui -I. + + +AM_CXXFLAGS := $(AM_CXXFLAGS) $(QT_CXXFLAGS) -DPLUGIN_NAME="$(PLUGIN_NAME)" + + +%.moc: ./%.h + $(MOC) -o $@ $< + + +MOC_FILES = ./lb303.moc + +if BUILD_WIN32 +DLL=$(PLUGIN_NAME).dll + +$(DLL): lib$(PLUGIN_NAME).la + $(CXX) *.o -L$(top_srcdir) -llmms-imp -shared -Wl,-no-undefined -o $@ $(QT_LDADD) && $(STRIP) $@ + +install-exec-hook: $(DLL) + cp $< $(pkglibdir) + rm $(pkglibdir)/lib$(PLUGIN_NAME).a $(pkglibdir)/lib$(PLUGIN_NAME).la +else +DLL= +endif + +BUILT_SOURCES = $(MOC_FILES) ./embedded_resources.h $(DLL) +EMBEDDED_RESOURCES = $(wildcard *png) + +./embedded_resources.h: $(EMBEDDED_RESOURCES) + $(BIN2RES) $(EMBEDDED_RESOURCES) > $@ + +EXTRA_DIST = $(EMBEDDED_RESOURCES) + + +CLEANFILES = $(MOC_FILES) ./embedded_resources.h + + + +pkglib_LTLIBRARIES= liblb303.la + +liblb303_la_SOURCES = lb303.cpp lb303.h + +$(liblb303_la_SOURCES): ./embedded_resources.h diff --git a/plugins/lb303/README b/plugins/lb303/README new file mode 100644 index 000000000..2a8058e56 --- /dev/null +++ b/plugins/lb303/README @@ -0,0 +1,87 @@ +LB303 Bass Synth - Known bugs, issues, and missing features +----------------------------------------------------------- + +2007.02.03 - First release +-------------------------- + +Important note, in the current incarnation there are many behaviors, +constants, and other items that will be changed. This will result +in a change of the instrument's timbre and functionality. Therefore, +it would be wise to not make any masterpieces with the synth at +this point in time. + +BUG: + Fix the awful clicking cause by adjacent notes. This problem seems + to be caused by 'unexhausted buffers' That is, the problem manifests + itself to a greater degree when the user "buffer size" configuration + is increased to over 1024 frames or so. The problem is much less + noticable when the buffer size is set to 64 frames. + +BUG: + The synth does not make accomodations for sampling rates other than + 44100. This should be easy to fix, but I haven't gotten around to + it; nor do I have the means to test it. + +BUG: + I get segfaults now and then. Granted, I've rarely used LMMS + without also using LB302, so I do not know if the problem is actually + in the Bass Synth. I have gotten the backtraces a few times: + ... + mixer::renderNextBuffer() + mixer::nextAudioBuffer() + QOBject::activate_signal() + ??() + + and + + ... + instrumentTrack::play() + notePlayHandle() + instrumentTrack::processOutEvent() + fadeButton::activate() + QSingleShotTimer::start() + qStartTimer() + qKillTimer() + QGList::insertAt() + +TODO: + Add accent feature. This isn't as bad as it sounds, but will require + some tweaking to get right. + +TODO: + LB302 contains code for a short fixed-length default decay. If a + released note is using a user-defined VCA from the instrument + panel, then LB302 should not do anything special. However, if no + envelope is specified, then LB302 should request enough release-frames + in order to apply the built-in exponential decay. + +TODO: + The original TB303's square wave is not 50% duty cycle. The peaks are + supposed to slope down slightly. It would be nice to be able to + configure this. In fact, each waveform could have a parameter knob to + change some aspect of the wave. See lb302.cpp:643 for more info. + +TODO: + Must decide on proper action to take when a slide note is interrupted + by another slide note. Right now, the slide-from frequency is always + replaced with the prior note. However, we may wish to maintain the + current frequency in order to make the sound more continuous. + +TODO: + The default filter's distortion could use some work. + effectLib::distortion<> gets the job done, but the coefficients need + to more closely match that of lb302Filter3Pole's tanh distortion. + +TODO: + Slide decay needs a better knob mapping. sqrt()? lb302.cpp:588 + +TODO: + Consider making the slide trigger set the note to slide TO as opposed + to the note to slide FROM. I originally did FROM in order to match + the real 303. However, TO may be more intuitive. + +TODO: + Various code refactoring, as well as precomputing some values. + +TODO: + Remove one of the sawtooth waveforms in favor of the other? diff --git a/plugins/lb303/artwork.png b/plugins/lb303/artwork.png new file mode 100644 index 000000000..c124bcd7c Binary files /dev/null and b/plugins/lb303/artwork.png differ diff --git a/plugins/lb303/lb303.cpp b/plugins/lb303/lb303.cpp new file mode 100644 index 000000000..d0e40bba9 --- /dev/null +++ b/plugins/lb303/lb303.cpp @@ -0,0 +1,845 @@ +/* + * lb303.cpp - implementation of class lb303 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 + * + * lb303FilterIIR2 is based on the gsyn filter code by Andy Sloane. + * + * lb303Filter3Pole 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 "lb303.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 "lb303.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 lb303_plugin_descriptor = +{ + STRINGIFY_PLUGIN_NAME( PLUGIN_NAME ), + "LB303", + QT_TRANSLATE_NOOP( "pluginBrowser", + "Incomplete monophonic immitation tb303" ), + "Paul Giblock ", + 0x0100, + plugin::Instrument, + new pluginPixmapLoader( "logo" ), + NULL +}; + +} + +// +// lb303Filter +// + +lb303Filter::lb303Filter(lb303FilterKnobState* p_fs) : + fs(p_fs), + vcf_c0(0), + vcf_e0(0), + vcf_e1(0) +{ +}; + + +void lb303Filter::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 lb303Filter::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 lb303Filter::playNote() +{ + vcf_c0 = vcf_e1; +} + + +// +// lb303FilterIIR2 +// + +lb303FilterIIR2::lb303FilterIIR2(lb303FilterKnobState* p_fs) : + lb303Filter(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); + +}; + + +lb303FilterIIR2::~lb303FilterIIR2() +{ + delete m_dist; +} + + +void lb303FilterIIR2::recalc() +{ + lb303Filter::recalc(); + //m_dist->setThreshold(0.5+(fs->dist*2.0)); + m_dist->setThreshold(fs->dist*75.0); +}; + + +void lb303FilterIIR2::envRecalc() +{ + float k, w; + + lb303Filter::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 lb303FilterIIR2::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; +} + + +// +// lb303Filter3Pole +// + +lb303Filter3Pole::lb303Filter3Pole(lb303FilterKnobState *p_fs) : + lb303Filter(p_fs), + ay1(0), + ay2(0), + aout(0), + lastin(0) +{ +}; + + +void lb303Filter3Pole::recalc() +{ + // DO NOT CALL BASE CLASS + vcf_e0 = 0.000001; + vcf_e1 = 1.0; +} + + +// TODO: Try using k instead of vcf_reso +void lb303Filter3Pole::envRecalc() +{ + float w,k; + float kfco; + + lb303Filter::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 lb303Filter3Pole::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); +} + + +// +// LBSynth +// + +lb303Synth::lb303Synth( instrumentTrack * _instrumentTrack ) : + instrument( _instrumentTrack, &lb303_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" ) ), + 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( &db24Toggle, SIGNAL( dataChanged( ) ), + this, SLOT ( db24Toggled( ) ) ); + + connect( &dist_knob, SIGNAL( dataChanged( ) ), + this, SLOT ( filterChanged( ))); + + + // 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; + + // 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 lb303FilterIIR2(&fs); + + sample_cnt = 0; + release_frame = 1<<24; + catch_frame = 0; + catch_decay = 0; + + recalcFilter(); + + last_offset = 0; + + new_freq = -1; + current_freq = -1; + delete_freq = -1; + + instrumentPlayHandle * iph = new instrumentPlayHandle( this ); + engine::getMixer()->addPlayHandle( iph ); + + filterChanged(); +} + + +lb303Synth::~lb303Synth() +{ + delete vcf; +} + + +void lb303Synth::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" ); + + 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 lb303Synth::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" ); + + 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(); +} + +// TODO: Split into one function per knob. envdecay doesn't require +// recalcFilter. +void lb303Synth::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 lb303Synth::db24Toggled( void ) +{ + delete vcf; + if(db24Toggle.value()) { + vcf = new lb303Filter3Pole(&fs); + } + else { + vcf = new lb303FilterIIR2(&fs); + } + recalcFilter(); +} + + + +QString lb303Synth::nodeName( void ) const +{ + return( lb303_plugin_descriptor.name ); +} + + +void lb303Synth::recalcFilter() +{ + vcf->recalc(); + + vcf_envpos = ENVINC; // Trigger filter update in process() +} + +inline int MIN(int a, int b) { + return (aprocessingSampleRate(); // TODO: Use actual sampling rate. +} + +int lb303Synth::process(sampleFrame *outbuf, const Uint32 size) +{ + unsigned int i; + float w; + float samp; + + if( delete_freq == current_freq ) { + // Normal release + delete_freq = -1; + vca_mode = 1; + } + + if( new_freq > 0.0f ) { + lb303Note note; + note.vco_inc = GET_INC( true_freq ); + note.dead = deadToggle.value(); + initNote(¬e); + + current_freq = new_freq; + + new_freq = -1.0f; + } + + + + // TODO: NORMAL RELEASE + // vca_mode = 1; + + for(i=0;i= 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++; + + int decay_frames = 128; + + // update vco + vco_c += vco_inc; + + if(vco_c > 0.5) + vco_c -= 1.0; + + /*LB303 + if (catch_decay > 0) { + if (catch_decay < decay_frames) { + catch_decay++; + } + }*/ + + 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; + } + + // Write out samples. + samp = vcf->process(vco_k) * vca_a; + + /* + float releaseFrames = desiredReleaseFrames(); + samp *= (releaseFrames - catch_decay)/releaseFrames; + */ + //LB303 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; + } + } + + } + return 1; +} + + +/* Prepares the active LB303 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 lb303Synth::initNote( lb303Note *n) +{ + catch_decay = 0; + + vco_inc = n->vco_inc; + + // Always reset vca on non-dead notes, and + // Only reset vca on decaying(decayed) and never-played + if(n->dead == 0 || (vca_mode==1 || vca_mode==3)) { + //printf(" good\n"); + sample_cnt = 0; + vca_mode = 0; + // LB303: + //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; // Slide amount + vco_slidebase = vco_inc; // The REAL frequency + vco_slideinc = 0; // reset from-note + } + else { + vco_slide = 0; + } + // End break-out + + // Slide-from 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 lb303Synth::playNote( notePlayHandle * _n, bool, + sampleFrame * _working_buffer ) +{ + if( _n->arpBaseNote() ) + { + //return; + } + + // 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; + + bool decay_note = false; + + release_frame = _n->framesLeft() - desiredReleaseFrames(); + + + if(deadToggle.value() == 0 && decay_note) { + } + /// Start a new note. + else if( _n->totalFramesPlayed() == 0 ) { + new_freq = _n->unpitchedFrequency(); + true_freq = _n->frequency(); + _n->m_pluginData = this; + } + + // Check for slide + if( _n->unpitchedFrequency() == current_freq ) { + true_freq = _n->frequency(); + + if( slideToggle.value() ) { + vco_slidebase = GET_INC( true_freq ); // The REAL frequency + } + else { + vco_inc = GET_INC( true_freq ); + } + } +} + + + +void lb303Synth::play( bool _try_parallelizing, + sampleFrame * _working_buffer ) +{ + //printf("."); + const fpp_t frames = engine::getMixer()->framesPerPeriod(); + + process( _working_buffer, frames); + getInstrumentTrack()->processAudioBuffer( _working_buffer, frames, + NULL ); +} + + + +void lb303Synth::deleteNotePluginData( notePlayHandle * _n ) +{ + //printf("GONE\n"); + if( _n->unpitchedFrequency() == current_freq ) + { + delete_freq = current_freq; + } +} + + +pluginView * lb303Synth::instantiateView( QWidget * _parent ) +{ + return( new lb303SynthView( this, _parent ) ); +} + + +lb303SynthView::lb303SynthView( 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_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 ); +} + + +lb303SynthView::~lb303SynthView() +{ +} + + +void lb303SynthView::modelChanged( void ) +{ + lb303Synth * 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_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 lb303Synth( + static_cast( _data ) ) ); +} + + +} + + diff --git a/plugins/lb303/lb303.h b/plugins/lb303/lb303.h new file mode 100644 index 000000000..107f708dc --- /dev/null +++ b/plugins/lb303/lb303.h @@ -0,0 +1,277 @@ +/* + * lb303.h - declaration of class lb303 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 + * + * lb303FilterIIR2 is based on the gsyn filter code by Andy Sloane. + * + * lb303Filter3Pole 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. + * + */ + + +#ifndef _LB303_H_ +#define _LB303_H_ + +#include "effect_lib.h" +#include "instrument.h" +#include "instrument_view.h" +#include "led_checkbox.h" +#include "knob.h" +#include "mixer.h" + +class lb303SynthView; +class notePlayHandle; + +class lb303FilterKnobState +{ + public: + float cutoff; + float reso; + float envmod; + float envdecay; + float dist; +}; + + +class lb303Filter +{ + public: + lb303Filter(lb303FilterKnobState* p_fs); + virtual ~lb303Filter() {}; + + virtual void recalc(); + virtual void envRecalc(); + virtual float process(const float& samp)=0; + virtual void playNote(); + + protected: + lb303FilterKnobState *fs; + + // Filter Decay + float vcf_c0; // c0=e1 on retrigger; c0*=ed every sample; cutoff=e0+c0 + float vcf_e0, // e0 and e1 for interpolation + vcf_e1; + float vcf_rescoeff; // Resonance coefficient [0.30,9.54] +}; + +class lb303FilterIIR2 : public lb303Filter +{ + public: + lb303FilterIIR2(lb303FilterKnobState* p_fs); + virtual ~lb303FilterIIR2(); + + virtual void recalc(); + virtual void envRecalc(); + virtual float process(const float& samp); + + protected: + float vcf_d1, // d1 and d2 are added back into the sample with + vcf_d2; // vcf_a and b as coefficients. IIR2 resonance + // loop. + + // IIR2 Coefficients for mixing dry and delay. + float vcf_a, // Mixing coefficients for the final sound. + vcf_b, // + vcf_c; + + effectLib::monoToStereoAdaptor > * m_dist_fx; + effectLib::distortion<> * m_dist; +}; + + +class lb303Filter3Pole : public lb303Filter +{ + public: + lb303Filter3Pole(lb303FilterKnobState* p_fs); + + //virtual void recalc(); + virtual void envRecalc(); + virtual void recalc(); + virtual float process(const float& samp); + + protected: + float kfcn, + kp, + kp1, + kp1h, + kres; + float ay1, + ay2, + aout, + lastin, + value; +}; + + + +class lb303Note +{ +public: + float vco_inc; + bool dead; +}; + + +class lb303Synth : public instrument +{ + Q_OBJECT +public: + lb303Synth( instrumentTrack * _channel_track ); + virtual ~lb303Synth(); + + virtual void play( bool _try_parallelizing, + sampleFrame * _working_buffer ); + virtual void playNote( notePlayHandle * _n, bool _try_parallelizing, + sampleFrame * _working_buffer ); + virtual void deleteNotePluginData( notePlayHandle * _n ); + + + virtual void saveSettings( QDomDocument & _doc, QDomElement & _parent ); + virtual void loadSettings( const QDomElement & _this ); + + virtual QString nodeName( void ) const; + + virtual bool supportsParallelizing( void ) const + { + return( FALSE ); + } + + virtual f_cnt_t desiredReleaseFrames( void ) const + { + return 0; //4048; + } + + virtual pluginView * instantiateView( QWidget * _parent ); + +private: + + void initNote(lb303Note *note); + + +private: + knobModel vcf_cut_knob; + knobModel vcf_res_knob; + knobModel vcf_mod_knob; + knobModel vcf_dec_knob; + + knobModel vco_fine_detune_knob; + + knobModel dist_knob; + knobModel wave_knob; + knobModel slide_dec_knob; + + boolModel slideToggle; + boolModel accentToggle; + boolModel deadToggle; + boolModel db24Toggle; + + +public slots: + void filterChanged( void ); + void detuneChanged( void ); + void db24Toggled( void ); + +private: + // Oscillator + float vco_inc, // Sample increment for the frequency. Creates Sawtooth. + vco_k, // Raw oscillator sample [-0.5,0.5] + vco_c; // Raw oscillator sample [-0.5,0.5] + + float vco_slide, //* Current value of slide exponential curve. Nonzero=sliding + vco_slideinc, //* Slide base to use in next node. Nonzero=slide next note + vco_slidebase; //* The base vco_inc while sliding. + + float vco_detune; + + enum vco_shape_t { SAWTOOTH, INVERTED_SAWTOOTH, SQUARE, TRIANGLE, MOOG, ROUND_SQUARE }; + vco_shape_t vco_shape; + + // User settings + lb303FilterKnobState fs; + lb303Filter *vcf; + + int release_frame; + + + // More States + int vcf_envpos; // Update counter. Updates when >= ENVINC + + float vca_attack, // Amp attack + vca_decay, // Amp decay + vca_a0, // Initial amplifier coefficient + vca_a; // Amplifier coefficient. + + // Envelope State + int vca_mode; // 0: attack, 1: decay, 2: idle, 3: never played + + // My hacks + int sample_cnt; + + int last_offset; + + int catch_frame; + int catch_decay; + + float new_freq; + float current_freq; + float delete_freq; + float true_freq; + + void recalcFilter(); + + int process(sampleFrame *outbuf, const Uint32 size); + + friend class lb303SynthView; + +} ; + + +class lb303SynthView : public instrumentView +{ +public: + lb303SynthView( instrument * _instrument, + QWidget * _parent ); + virtual ~lb303SynthView(); + +private: + virtual void modelChanged( void ); + + knob * m_vcfCutKnob; + knob * m_vcfResKnob; + knob * m_vcfDecKnob; + knob * m_vcfModKnob; + + knob * m_vcoFineDetuneKnob; + + knob * m_distKnob; + knob * m_waveKnob; + knob * m_slideDecKnob; + + ledCheckBox * m_slideToggle; + ledCheckBox * m_accentToggle; + ledCheckBox * m_deadToggle; + ledCheckBox * m_db24Toggle; + +} ; + +#endif diff --git a/plugins/lb303/logo.png b/plugins/lb303/logo.png new file mode 100644 index 000000000..22c99d1e4 Binary files /dev/null and b/plugins/lb303/logo.png differ diff --git a/src/gui/controller_connection_dialog.cpp b/src/gui/controller_connection_dialog.cpp index e662a5e34..b574debd9 100644 --- a/src/gui/controller_connection_dialog.cpp +++ b/src/gui/controller_connection_dialog.cpp @@ -66,8 +66,9 @@ public: virtual void processInEvent( const midiEvent & _me, - const midiTime & _time, bool _lock ) + const midiTime & _time ) { + printf("ho hum\n"); if( _me.m_type == MidiControlChange && ( m_midiPort.inputChannel() == _me.m_channel + 1 || m_midiPort.inputChannel() == 0 ) ) diff --git a/src/gui/widgets/knob.cpp b/src/gui/widgets/knob.cpp index 92d1d0ef8..dc1b1e6b9 100644 --- a/src/gui/widgets/knob.cpp +++ b/src/gui/widgets/knob.cpp @@ -278,7 +278,7 @@ bool knob::updateAngle( void ) void knob::drawKnob( QPainter * _p ) { - if( updateAngle() == FALSE ) + if( updateAngle() == FALSE && !m_cache.isNull() ) { _p->drawImage( 0, 0, m_cache ); return;