mirror of
https://github.com/LMMS/lmms.git
synced 2026-05-12 00:37:05 -04:00
Adjust the `Knob` class so that it defaults to taking the font size of
the knob's font into account when rendering its label. This allows to
use labels of different sizes for different knobs. Previously all knob
labels throughout the whole application were rendered with the same fixed
font size. Hence it was not possible to adjust the label size for a
single knob because this would have affected all other knobs as well.
The new implementation also allows the knobs to pick up CSS rules.
To be able to control the knob behavior two new constructors have been
added to the `Knob` class. Both constructors are concerned with creating
knobs with labels and therefore they directly take the label text as a
parameter. This removes numerous explicit calls to `setLabel` in the
code.
There is only one constructor that allows to switch between the new
behavior of taking the widget's font size into account and the old legacy
behavior of always rendering with the same fixed font size of
`SMALL_FONT_SIZE`. The parameter was modelled as an enum to make it
easier to find the remaining knob instances that use the legacy behavior.
This makes it easier to find them in case they should be removed as well.
In that case the string `LegacyFixedFontSize` can be searched.
The other new constructor allows to directly set the knob's (and
therefore the label's) font size to a pixel value.
Corresponding constructors have been added to `TempoSyncKnob`. The
constructors of `KnobControl` and `CustomTextKnob` have been adjusted to
take advantage of the new constructors. An usused constructor was removed
in `CustomTextKnob`.
The method `Knob::setLabel` has been made protected because labels should
now be set through the new constructors.
A new property called `m_fixedFontSizeLabelRendering` was added to the
`Knob` class. It controls how the labels are rendered. Fixed font size
legacy rendering can be activated by calling the protected method
`setFixedFontSizeLabelRendering`. The current setting can be queried via
`fixedFontSizeLabelRendering`.
## Changes in the plugins
Some plugins have been switched to using layouts to organize their
widgets so that they can accommodate for knobs with label sizes set by
the application font.
The fixed font size (legacy) rendering mode is still used in the
following places:
* EnvelopeAndLfoView
* InstrumentSoundShapingView
* InstrumentFunctionViews
* EffectView
* InstrumentTrackView
* SampleTrackView
* Delay plugin
* Carla plugin
# individual commit messages
What follows are the individual commit messages of the commits that have been squashed into one commit. They might help in case of more detailed investigations of how things came to be.
* Knob with correct label rendering
Enable the knob to render the label correctly at arbitrary sizes if it's configured to do so. Otherwise it will render like before. The used mode is determined when a label is set for the knob because as long as the label is not set a knob does not have one anyway.
The painting code now always renders the label with the font that's set for the widget.
The are now two methods to set the label text. The new method `setLabelLegacy` renders the label as before albeit in a slightly adjusted implementation. It now sets the widget font to a fixed pixel size font and then calculates the new widget size as before, i.e. not really taking the size of the font into account. This might lead to overlaps if the font of the knob is large.
The method `setLabel` now has an additional (temporary) parameter called `legacyMode`. It is by default set to `true` so that all knobs still render like they did before. This is implemented by delegating to `setLabelLegacy` if it's set to `true`. Otherwise the method calculates the new size of the widget by taking the pixmap and the label with the current font into account.
Please note that as of now you must set the knob font before calling any of the methods that sets the label. This is because the new size is only calculated via these code paths. However, this is already much better than only being able to use one hard-coded label size for all knobs.
* Switch from `setLabel` to `setLabelLegacy`
Switch all callers of `setLabel` to `setLabelLegacy` so that it becomes
obvious in which places the old knob implementation is used.
* Remove parameter `legacyMode` from `setLabel`
Remove the parameter `legacyMode` from `setLabel`. Add the member
`m_legacyMode` as it is needed in `Knob::changeEvent` so that we do not
switch the behavior when the knob is enabled/disabled.
* Extract methods
Extract `setLegacyMode` and `updateFixedSize`. Also add the getter `legacyMode`.
* Introduce legacy knob builders
Introduce legacy knob builders and apply them to the plugins. There are three new methods which encapsulate how to create a corresponding legacy knob:
* `Knob::buildLegacyKnob`
* `CustomTextKnob::buildLegacyKnob`
* `TempoSyncKnob::buildLegacyKnob`
These methods set the knob they build to legacy mode and also set a label in legacy mode. The idea is to concentrate the relevant legacy code in these methods. They will later also be useful to quickly find all the places in the application where legacy knobs are used.
The three methods are applied to the plugins directory. Most plugins use the build methods to build their knobs which also enables the removal of the explicit calls to `setLabelLegacy` from their code.
For some plugins their implementations were adjusted so that they can deal with showing the labels in the applicaiton font, i.e. in the font size of the widget their are contained in. Most of the times this involved removing the fixed size and putting the elements in a layout (while also removing move calls). The following LMMS plugins use the application font now and are thus better readable:
* Amplifier
* BassBooster
* Dispersion
* Flanger
* Peak Controller
* ReverbSC
* StereoEnhancer Effect
The Vectorscope now shows the "Persist." label in the same size as the label of the check boxes.
Setting an empty label was removed for Lb302.
* Legacy knob builders in GUI
Apply the legacy knob builders in the GUI components. Most components use the legacy knobs until they can be redesigned:
* Effect view ("W/D", "DECAY", "GATE")
* LFO Controller
* Instrument window
Everything related to the instrument window is for now kept to use the legacy knobs and should be adjusted at a later point to be more flexible:
* Envelope and LFO
* Functions
* Sound Shaping
The Instrument and sample track both use the legacy knobs for the "VOL" and "PAN" knobs. This might be adjusted later.
The following components now render the labels of their knobs with the application font size:
* MIDI CC Rack
* The class `LadspaControlView`, which is not in used anymore
Some vertical spacing was added to the MIDI CC Rack for slightly improved separation of the elements. The knobs are center aligned in the layout so that the transition between element under and over "CC 100" is cleaner. Setting the models in an explicit loop was removed and is now done when the knobs are created.
## Technical details
Extend `Knob::buildLegacyKnob` with the option to also set the name of the knob. This is needed for some changes in this PR.
The method `KnobControl::setText` now needs to distinguish between legacy mode and non-legacy mode.
* Remove `Knob::setLabelLegacy`
Remove `Knob::setLabelLegacy`. Instead make sure that the `Knob` updates its size in the following situations:
* The label is set.
* The font changes.
* Legacy mode is set or unset (already implemented).
The handling of font changes has been added to `Knob::changeEvent`. The update in case of a changed label is added to `Knob::setLabel`.
Every client that called `setLabelLegacy` now also sets the legacy font in size `SMALL_FONT_SIZE` as this was previously done in `setLabelLegacy`. The label is set via `setLabel` now. Both actions should result in an up-to-date size.
The method `KnobControl::setText` now only sets the label via `setLabel`, assuming that the wrapped knob was already configured correctly to either be a legacy knob or not.
* Use descent to calculate base line
Use the descent of the font to calculate the distance of the base line from the bottom of the knob widget if we are not in legacy mode. In legacy mode we still assume the descent to have a value of 2, i.e. the base line will always have a distance of 2 from the bottom of the knob widget regardless of the used font.
Also refactor the code a bit to make it more manageable.
* Extract `Knob::drawLabel`
Extract the method `Knob::drawLabel` which draws the label. It is called from `paintEvent`.
* Use non-legacy knobs for instrument and sample track
Use non-legacy knobs for the "VOL" and "PAN" knobs of the instrument and sample track. This gives a bit more separation between the knob and the label but to make this work the font size had to be decreased by one pixel.
* Introduce `buildKnobWithSmallPixelFont`
Introduce the builder method `buildKnobWithSmallPixelFont` in `Knob` and `TempoSyncKnob`. It creates a non-legacy knob with a small pixel sized font, i.e. it still uses the small font but with a corrected size computation and corrected space between the knob and the label. It is mostly used in places with manual layouts where there's enough space to have the bit of extra space between the knob and the label.
The following plugins use these knobs:
* Bitcrush
* Crossover EQ
* Dual Filter
* Dynamics Processor
* Multitap Echo
* Spectrum analyzer
* Mallets
* Waveshaper
* ZynAddSubFx
The "IN" and "OUT" label of the Bitcrush plugin use the default fixed font size now because the plugin uses a pixel based layout. Using the point based application font looked off.
They are also used in the following component:
* Effect view, i.e. the "W/D", "DECAY", "GATE" knobs of an effect
* LFO Controller
* Non-legacy knobs for VSTs
Use non-legacy knobs with small pixel fonts for the parameter lists of VST instruments and effects.
This is accomplished by renaming `CustomTextKnob::buildLegacyKnob` to `buildKnobWithSmallPixelFont` and removing the call to `setLegacyMode`.
* Fix styled knobs
Fix the handling of styled knobs which are created in non-legacy mode. Styled knobs do not use pixmaps and have no labels. Their size is set from the outside and they are painted within these limits. Hence we should not compute a new size from a pixmap and/or label in `Knob::updateFixedSize`.
This fixes the following plugins:
* FreeBoy
* Kicker
* Monstro
* Nescaline
* Opulenz
* Organic
* Sf2 Player
* sfxr
* SID
* SlicerT
* Triple
* Watsyn
* Xpressive
The functionality broke with commit defa8c0180e.
An alternative would have been to check for a styled knob in the contructor or `initUI` method and to set the legacy flag for these.
The best solution would likely be to create an own class for styled knobs and to pull that functionality out of `Knob` because they somewhat clash in their handling.
* Code review changes
Parameter whitespaces in the builder methods of `Knob`.
Use `adjustedToPixelSize` in `InstrumentTrackView` and `SampleTrackView`.
* Code review changes
Make the code that computes the new fixed size in legacy more readable
even if it is just legacy code that's was not touched. Add some code
documentation.
Other cosmetic changes:
* Whitespace adjustments
* Remove unused parameter in `paintEvent`
* Rename `knob_num` to `knobNum`
* Add documentation for legacy mode
Add some documentation which explains what the effects of legacy mode
are.
* Code review
Remove unnecessary dereference.
Also remove unncessary code repetition by introducing
`currentParamModel`.
* Decrease the label size of some knobs
Decrease the size of the following knob labels to 8 pixels:
* "VOL" and "PAN" in the instrument and sample track views
* "W/D", "DECAY" and "GATE" in the effect view
Technically this is accomplished by introducing
`Knob::buildKnobWithFixedPixelFont` and
`TempoSyncKnob::buildKnobWithFixedPixelFont`.
Both versions of `buildKnobWithSmallPixelFont` now also delegate to
the new methods.
* Adjustments to CrossoverEQControlDialog
Commit the adjustments that were done to `CrossoverEQControlDialog` which I had forgotten to add after fixing the merge.
* Fix formatting of CrossoverEQControlDialog
Fix the formatting of `CrossoverEQControlDialog` which got messed up after copying the code from the current version on GitHub.
* Code review changes
Use `std::max` instead of `qMax`. Remove some unnecessary whitespace.
* Protected legacy mode methods
Make `legacyMode` and `setLegacyMode` protected to ensure that legacy knobs can only be built using the factory method `buildLegacyKnob`. In the long term legacy mode should be removed.
* Code review: remove indexed access
The original request in the code review was to use `size_t` instead of
`uint32_t` in the for-loop. However it is possible to completely remove
the indexed access and to turn it into a simple iterated for-loop.
Also remove code repetition in the calculation of the maximum knob width
of the group. Use std::max instead of manual management.
* Fix u_int16_t to uint16_t
This should hopefully fix the WIndows builds.
* Fix AudioFileProcessor knobs
Fix a problem with the `AudioFileProcessorWaveView::knob` which is caused
by the fact the this knob uses the pixmap based knob type `Bright26`
without a label. Most other knobs that inherit from `Knob` set their knob
type to `Styled` which means that no pixmap is used to render the knob.
In the specific case the knob instance is created and the contructor
runs. In the constructor the AFP knob is set to a fixed size of (37,47).
However, at a later point the method `Knob::changeEvent` is triggered by
Qt due to a font change. This in turn calls `Knob::updateFixedSize` which
then recomputes the fixed size and effectively changes the width of the
knob to the width of the pixmap which is 27. Because the knob previously
was rendered centered with a width of 37 this means that the knob is now
effectively shifted by five pixels to the left.
This commit counters this effect by moving the affected AFP knobs five
pixels to the right. A visual difference between the fixed version and
the current master showed no differences. So this should fix the problem.
Because setting the knob to a fixed size of (37,47) does not have any
lasting effect anyway the code is removed from the constructor of the AFP
knob.
* Use legacy knobs in EffectView
* Legacy knobs for instrument & sample
Use legacy knobs for the instrument and sample track view ("VOL", "PAN").
* Add documentation to Knob builder methods
Add some documentation to the `Knob` builder methods. Mark `buildLegacyKnob` as deprecated and note that it should not be used in new code.
* Ensure legancy rendering for legacy knobs
Ensure that legacy knobs are always rendered at a size of 12 pixels,
i.e. `SMALL_FONT_SIZE`.
The previous implementation used the font metrics of the knob's current
font to compute the new fixed size and to render the label. It assumed
that the knob was created using `Knob::buildLegacyKnob` and that
therefore the font is set to 12 pixels. However, this meant that legacy
knobs can still be affected by style sheet settings. The following CSS
rule for example resulted in legacy knobs with a larger font size:
```
* { font-size: 18px; }
```
The fix is to use a font with a size of `SMALL_FONT_SIZE` when
calculating the fixed size of the `Knob` widget and when rendering it if
we are in legacy mode. This ensures that a legacy knob is unaffected by
CSS rules.
The non-legacy knob still uses the widget's font size and therefore it is
affected by CSS rules. However, this is a feature and not a bug because
when for example using a rule like the one above the knob does exactly
what it's asked to do.
* Remove unused constructor
Remove an unused constructor from CustomTextKnob
* Remove Knob::buildKnobWithSmallPixelFont
Remove the builder method `Knob::buildKnobWithSmallPixelFont` and replace
it with an equivalent new contstructor which takes the same arguments as
the build method.
* Remove Knob::buildKnobWithFixedPixelFont
Remove `Knob::buildKnobWithFixedPixelFont` as it is not used anymore.
Previously it was delegated to by the now removed method
`Knob::buildKnobWithSmallPixelFont`.
* Constructor for knobs with pixel size labels
Remove `TempoSyncKnob::TempoSyncKnob` and add an equivalent constructor.
Make `buildKnobWithSmallPixelFont` use the new constructor.
* Remove TempoSyncKnob::buildKnobWithSmallPixelFont
Remove `TempoSyncKnob::buildKnobWithSmallPixelFont` and make clients use
the new constructor.
* Remove CustomTextKnob::buildKnobWithSmallPixelFont
Remove `CustomTextKnob::buildKnobWithSmallPixelFont` and extend the
constructor so that it also takes a label. Previously all constructions
went through the build method and now all constructions use the extended
constructor.
* Knob constructors whichKnob constructors which take labels
Add constructors for `Knob` and `TempoSyncKnob` which also take the label
text. Make setLabel protected as most knobs should know their labels at
construction time.
This prevents "chatty" code like the following example:
```
Knob* knob = new Knob(KnobType::Bright26, this);
knob->setLabel("My label");
```
This now becomes a simple a one-liner:
```
Knob* knob = new Knob(KnobType::Bright26, "My label", this);
```
The constructor of `KnobControl` also had to be extended with the label
text because it cannot access the setLabel of the Knob that it manages.
However, it can pass the text during construction. Its implementation of
the virtual method `Control::setText` becomes empty due to this. The
`KnobControl` is currently only used in `Lv2ViewProc::Lv2ViewProc`. Here
the `KnobControl` is created by passing the port name into the
constructor. However, the virtual method `setText` is still called in
line 91 for all other implementations.
Add documentation for the constructors.
* Remove Knob::buildLegacyKnob
Remove `Knob::buildLegacyKnob` by extending the very similar constructor
with an enum that indicates whether the constructed `Knob` should be in
legacy mode or not. The default is to build a non-legacy `Knob`.
Wherever `Knob::buildLegacyKnob` was called the constructor is now called
with `Knob::Mode::Legacy` as the parameter. In some places where the
onject name is set `Knob::Mode::NonLegacy` has to be added explicitly.
* Remove TempoSyncKnob::buildLegacyKnob
Remove `TempoSyncKnob::buildLegacyKnob` by extending the very similar
constructor with an enum that indicates whether the constructed
`TempoSyncKnob` should be in legacy mode or not. The default is to
build a non-legacy `TempoSyncKnob`.
Wherever `TempoSyncKnob::buildLegacyKnob` was called the constructor is
now called with `Knob::Mode::Legacy` as the parameter.
* Vertical spacing for Peak Controller
Add a vertical spacing of 10 pixel between the knobs of the Peak Controller.
* Peak Controller: use default margins
Remove the specific call to `setContentsMargins` from the Peak Controller so that the main layout uses Qt's default margins.
Also remove the spacing again.
* Rename the enum `Knob::Mode`
Rename the enum `Knob::Mode` and its values so that they better describe
what they influence.
`Knob::Mode` is renamed to `Knob::LabelRendering` to indicate that its
value affects the label rendering.
The value `NonLegacy` is now called `WidgetFont` to indicate that the
knob uses the font settings of the widget when rendering the label.
The value `Legacy` is now called `LegacyFixedFontSize` to indicate that
it's a legacy behavior which uses a fixed font size that does not adhere
to the font size that's set for the widget's font.
Adjust all callers accordingly.
* Add TempoSyncKnob documentation
Document the constructor of `TempoSyncKnob` that can be used to set the
label rendering to lecacy mode.
* Name adjustments and parameter removal
Rename `m_legacyMode` to `m_fixedFontSizeLabelRendering`.
Rename the method `legacyMode` to `fixedFontSizeLabelRendering`.
Rename `setLegacyMode` to `setFixedFontSizeLabelRendering`. Also remove
the boolean parameter from the method as it was only called with `true`
anyway.
1046 lines
28 KiB
C++
1046 lines
28 KiB
C++
/*
|
|
* Lb302.cpp - implementation of class Lb302 which is a bass synth attempting
|
|
* to emulate the Roland TB-303 bass synth
|
|
*
|
|
* Copyright (c) 2006-2008 Paul Giblock <pgib/at/users.sourceforge.net>
|
|
*
|
|
* This file is part of LMMS - https://lmms.io
|
|
*
|
|
* Lb302FilterIIR2 is based on the gsyn filter code by Andy Sloane.
|
|
*
|
|
* Lb302Filter3Pole is based on the TB-303 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.
|
|
*
|
|
*/
|
|
|
|
// Need to include this first to ensure we get M_PI in MinGW with C++11
|
|
#define _USE_MATH_DEFINES
|
|
#include <cmath>
|
|
|
|
#include "Lb302.h"
|
|
#include "AutomatableButton.h"
|
|
#include "Engine.h"
|
|
#include "InstrumentPlayHandle.h"
|
|
#include "InstrumentTrack.h"
|
|
#include "Knob.h"
|
|
#include "LedCheckBox.h"
|
|
#include "NotePlayHandle.h"
|
|
#include "Oscillator.h"
|
|
#include "PixmapButton.h"
|
|
#include "BandLimitedWave.h"
|
|
|
|
#include "embed.h"
|
|
#include "plugin_export.h"
|
|
|
|
// 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
|
|
|
|
//
|
|
// Old config
|
|
//
|
|
|
|
namespace lmms
|
|
{
|
|
|
|
|
|
//#define engine::audioEngine()->outputSampleRate() 44100.0f
|
|
const float sampleRateCutoff = 44100.0f;
|
|
|
|
extern "C"
|
|
{
|
|
|
|
Plugin::Descriptor PLUGIN_EXPORT lb302_plugin_descriptor =
|
|
{
|
|
LMMS_STRINGIFY( PLUGIN_NAME ),
|
|
"LB302",
|
|
QT_TRANSLATE_NOOP( "PluginBrowser",
|
|
"Incomplete monophonic imitation TB-303" ),
|
|
"Paul Giblock <pgib/at/users.sf.net>",
|
|
0x0100,
|
|
Plugin::Type::Instrument,
|
|
new PluginPixmapLoader( "logo" ),
|
|
nullptr,
|
|
nullptr,
|
|
};
|
|
|
|
}
|
|
|
|
//
|
|
// Lb302Filter
|
|
//
|
|
|
|
Lb302Filter::Lb302Filter(Lb302FilterKnobState* p_fs) :
|
|
fs(p_fs),
|
|
vcf_c0(0),
|
|
vcf_e0(0),
|
|
vcf_e1(0)
|
|
{
|
|
};
|
|
|
|
|
|
void Lb302Filter::recalc()
|
|
{
|
|
vcf_e1 = std::exp(6.109f + 1.5876f * fs->envmod + 2.1553f * fs->cutoff - 1.2f * (1.0f - fs->reso));
|
|
vcf_e0 = std::exp(5.613f - 0.8f * fs->envmod + 2.1553f * fs->cutoff - 0.7696f * (1.0f - fs->reso));
|
|
vcf_e0*=M_PI/Engine::audioEngine()->outputSampleRate();
|
|
vcf_e1*=M_PI/Engine::audioEngine()->outputSampleRate();
|
|
vcf_e1 -= vcf_e0;
|
|
|
|
vcf_rescoeff = std::exp(-1.20f + 3.455f * fs->reso);
|
|
};
|
|
|
|
|
|
void Lb302Filter::envRecalc()
|
|
{
|
|
vcf_c0 *= fs->envdecay; // Filter Decay. vcf_decay is adjusted for Hz and ENVINC
|
|
// vcf_rescoeff = std::exp(-1.20f + 3.455f * 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 DspEffectLibrary::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()
|
|
{
|
|
Lb302Filter::envRecalc();
|
|
|
|
float w = vcf_e0 + vcf_c0; // e0 is adjusted for Hz and doesn't need ENVINC
|
|
float k = std::exp(-w / vcf_rescoeff); // Does this mean c0 is inheritantly?
|
|
|
|
vcf_a = 2.0 * std::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;
|
|
}
|
|
|
|
|
|
//
|
|
// 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.000001f;
|
|
vcf_e1 = 1.0;
|
|
}
|
|
|
|
|
|
// TODO: Try using k instead of vcf_reso
|
|
void Lb302Filter3Pole::envRecalc()
|
|
{
|
|
Lb302Filter::envRecalc();
|
|
|
|
// e0 is adjusted for Hz and doesn't need ENVINC
|
|
float w = vcf_e0 + vcf_c0;
|
|
float k = (fs->cutoff > 0.975)?0.975:fs->cutoff;
|
|
// sampleRateCutoff should not be changed to anything dynamic that is outside the
|
|
// scope of LB302 (like e.g. the audio engine's sample rate) as this changes the filter's cutoff
|
|
// behavior without any modification to its controls.
|
|
float kfco = 50.f + (k)*((2300.f-1600.f*(fs->envmod))+(w) *
|
|
(700.f+1500.f*(k)+(1500.f+(k)*(sampleRateCutoff/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::audioEngine()->outputSampleRate();
|
|
#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 = std::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);
|
|
}
|
|
|
|
|
|
//
|
|
// LBSynth
|
|
//
|
|
|
|
static float computeDecayFactor(float decayTimeInSeconds, float targetedAttenuation)
|
|
{
|
|
// This is the number of samples that correspond to the decay time in seconds
|
|
auto samplesNeededForDecay = decayTimeInSeconds * Engine::audioEngine()->outputSampleRate();
|
|
|
|
// This computes the factor that's needed to make a signal with a value of 1 decay to the
|
|
// targeted attenuation over the time in number of samples.
|
|
return std::pow(targetedAttenuation, 1. / samplesNeededForDecay);
|
|
}
|
|
|
|
Lb302Synth::Lb302Synth( InstrumentTrack * _instrumentTrack ) :
|
|
Instrument(_instrumentTrack, &lb302_plugin_descriptor, nullptr, Flag::IsSingleStreamed),
|
|
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_shape( 8.0f, 0.0f, 11.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" ) ),
|
|
vca_attack(1.f - 0.96406088f),
|
|
vca_a0(0.5),
|
|
vca_a(0.),
|
|
vca_mode(VcaMode::NeverPlayed)
|
|
{
|
|
|
|
connect( Engine::audioEngine(), 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;
|
|
|
|
vco_shape = VcoShape::BLSawtooth;
|
|
|
|
vcfs[0] = new Lb302FilterIIR2(&fs);
|
|
vcfs[1] = new Lb302Filter3Pole(&fs);
|
|
db24Toggled();
|
|
|
|
sample_cnt = 0;
|
|
release_frame = 0;
|
|
catch_frame = 0;
|
|
catch_decay = 0;
|
|
|
|
last_offset = 0;
|
|
|
|
new_freq = false;
|
|
|
|
filterChanged();
|
|
|
|
auto iph = new InstrumentPlayHandle(this, _instrumentTrack);
|
|
Engine::audioEngine()->addPlayHandle( iph );
|
|
}
|
|
|
|
|
|
Lb302Synth::~Lb302Synth()
|
|
{
|
|
for (const auto& vcf : vcfs)
|
|
{
|
|
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" );
|
|
|
|
wave_shape.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" );
|
|
|
|
dist_knob.loadSettings( _this, "dist");
|
|
slide_dec_knob.loadSettings( _this, "slide_dec");
|
|
wave_shape.loadSettings( _this, "shape");
|
|
slideToggle.loadSettings( _this, "slide");
|
|
deadToggle.loadSettings( _this, "dead");
|
|
db24Toggle.loadSettings( _this, "db24");
|
|
db24Toggled();
|
|
|
|
filterChanged();
|
|
}
|
|
|
|
// TODO: Split into one function per knob. envdecay doesn't require
|
|
// recalcFilter.
|
|
void Lb302Synth::filterChanged()
|
|
{
|
|
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::audioEngine()->outputSampleRate(); // d *= smpl rate
|
|
fs.envdecay = std::pow(0.1f, 1.0f / 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()
|
|
{
|
|
vcf = vcfs[db24Toggle.value()];
|
|
// These recalcFilter calls might suck
|
|
recalcFilter();
|
|
}
|
|
|
|
|
|
|
|
QString Lb302Synth::nodeName() const
|
|
{
|
|
return( lb302_plugin_descriptor.name );
|
|
}
|
|
|
|
|
|
// OBSOLETE. Break apart once we get Q_OBJECT to work. >:[
|
|
void Lb302Synth::recalcFilter()
|
|
{
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))
|
|
vcf.loadRelaxed()->recalc();
|
|
#else
|
|
vcf.load()->recalc();
|
|
#endif
|
|
// 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::audioEngine()->outputSampleRate();
|
|
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 float GET_INC(float freq) {
|
|
return freq/Engine::audioEngine()->outputSampleRate(); // TODO: Use actual sampling rate.
|
|
}
|
|
|
|
int Lb302Synth::process(SampleFrame* outbuf, const std::size_t size)
|
|
{
|
|
const float sampleRatio = 44100.f / Engine::audioEngine()->outputSampleRate();
|
|
|
|
// Hold on to the current VCF, and use it throughout this period
|
|
Lb302Filter *filter = vcf.loadAcquire();
|
|
|
|
if( release_frame == 0 || ! m_playingNote )
|
|
{
|
|
vca_mode = VcaMode::Decay;
|
|
}
|
|
|
|
if( new_freq )
|
|
{
|
|
//printf(" playing new note..\n");
|
|
Lb302Note note;
|
|
note.vco_inc = GET_INC( true_freq );
|
|
note.dead = deadToggle.value();
|
|
initNote(¬e);
|
|
|
|
new_freq = false;
|
|
}
|
|
|
|
|
|
|
|
// TODO: NORMAL RELEASE
|
|
// vca_mode = 1;
|
|
|
|
// Note: this has to be computed during processing and cannot be initialized
|
|
// in the constructor because it's dependent on the sample rate and that might
|
|
// change during rendering!
|
|
//
|
|
// At 44.1 kHz this will compute something very close to the previously
|
|
// hard coded value of 0.99897516.
|
|
auto decay = computeDecayFactor(0.245260770975f, 1.f / 65536.f);
|
|
|
|
for (auto i = std::size_t{0}; i < size; i++)
|
|
{
|
|
// start decay if we're past release
|
|
if (i >= release_frame) { vca_mode = VcaMode::Decay; }
|
|
|
|
// update vcf
|
|
if(vcf_envpos >= ENVINC) {
|
|
filter->envRecalc();
|
|
|
|
vcf_envpos = 0;
|
|
|
|
if (vco_slide) {
|
|
vco_inc = vco_slidebase - vco_slide;
|
|
// Calculate coeff from dec_knob on knob change.
|
|
vco_slide -= vco_slide * ( 0.1f - slide_dec_knob.value() * 0.0999f ) * sampleRatio; // TODO: Adjust for ENVINC
|
|
|
|
}
|
|
}
|
|
|
|
|
|
sample_cnt++;
|
|
vcf_envpos++;
|
|
|
|
//int decay_frames = 128;
|
|
|
|
// update vco
|
|
vco_c += vco_inc;
|
|
|
|
if(vco_c > 0.5)
|
|
vco_c -= 1.0;
|
|
|
|
switch(int(rint(wave_shape.value()))) {
|
|
case 0: vco_shape = VcoShape::Sawtooth; break;
|
|
case 1: vco_shape = VcoShape::Triangle; break;
|
|
case 2: vco_shape = VcoShape::Square; break;
|
|
case 3: vco_shape = VcoShape::RoundSquare; break;
|
|
case 4: vco_shape = VcoShape::Moog; break;
|
|
case 5: vco_shape = VcoShape::Sine; break;
|
|
case 6: vco_shape = VcoShape::Exponential; break;
|
|
case 7: vco_shape = VcoShape::WhiteNoise; break;
|
|
case 8: vco_shape = VcoShape::BLSawtooth; break;
|
|
case 9: vco_shape = VcoShape::BLSquare; break;
|
|
case 10: vco_shape = VcoShape::BLTriangle; break;
|
|
case 11: vco_shape = VcoShape::BLMoog; break;
|
|
default: vco_shape = VcoShape::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 VcoShape::Sawtooth: // p0: curviness of line
|
|
vco_k = vco_c; // Is this sawtooth backwards?
|
|
break;
|
|
|
|
case VcoShape::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 VcoShape::Square: // p0: slope of top
|
|
vco_k = (vco_c<0)?0.5:-0.5;
|
|
break;
|
|
|
|
case VcoShape::RoundSquare: // p0: width of round
|
|
vco_k = (vco_c < 0.f) ? (std::sqrt(1.f - (vco_c * vco_c * 4.f)) - 0.5f) : -0.5f;
|
|
break;
|
|
|
|
case VcoShape::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) {
|
|
float w = 2.0 * (vco_k - 0.5) - 1.0;
|
|
vco_k = 0.5 - std::sqrt(1.0 - (w * w));
|
|
}
|
|
vco_k *= 2.0; // MOOG wave gets filtered away
|
|
break;
|
|
|
|
case VcoShape::Sine:
|
|
// [-0.5, 0.5] : [-pi, pi]
|
|
vco_k = 0.5f * Oscillator::sinSample( vco_c );
|
|
break;
|
|
|
|
case VcoShape::Exponential:
|
|
vco_k = 0.5 * Oscillator::expSample( vco_c );
|
|
break;
|
|
|
|
case VcoShape::WhiteNoise:
|
|
vco_k = 0.5 * Oscillator::noiseSample( vco_c );
|
|
break;
|
|
|
|
// The next cases all use the BandLimitedWave class which uses the oscillator increment `vco_inc` to compute samples.
|
|
// If that oscillator increment is 0 we return a 0 sample because calling BandLimitedWave::pdToLen(0) leads to a
|
|
// division by 0 which in turn leads to floating point exceptions.
|
|
case VcoShape::BLSawtooth:
|
|
vco_k = vco_inc == 0. ? 0. : BandLimitedWave::oscillate(vco_c + 0.5f, BandLimitedWave::pdToLen(vco_inc), BandLimitedWave::Waveform::BLSaw) * 0.5f;
|
|
break;
|
|
|
|
case VcoShape::BLSquare:
|
|
vco_k = vco_inc == 0. ? 0. : BandLimitedWave::oscillate(vco_c + 0.5f, BandLimitedWave::pdToLen(vco_inc), BandLimitedWave::Waveform::BLSquare) * 0.5f;
|
|
break;
|
|
|
|
case VcoShape::BLTriangle:
|
|
vco_k = vco_inc == 0. ? 0. : BandLimitedWave::oscillate(vco_c + 0.5f, BandLimitedWave::pdToLen(vco_inc), BandLimitedWave::Waveform::BLTriangle) * 0.5f;
|
|
break;
|
|
|
|
case VcoShape::BLMoog:
|
|
vco_k = vco_inc == 0. ? 0. : BandLimitedWave::oscillate(vco_c + 0.5f, BandLimitedWave::pdToLen(vco_inc), BandLimitedWave::Waveform::BLMoog);
|
|
break;
|
|
}
|
|
|
|
//vca_a = 0.5;
|
|
// Write out samples.
|
|
#ifdef LB_FILTERED
|
|
//samp = vcf->process(vco_k)*2.0*vca_a;
|
|
//samp = vcf->process(vco_k)*2.0;
|
|
float samp = filter->process(vco_k) * vca_a;
|
|
//printf("%f %d\n", vco_c, sample_cnt);
|
|
|
|
|
|
//samp = vco_k * vca_a;
|
|
|
|
if( sample_cnt <= 4 )
|
|
{
|
|
// vca_a = 0;
|
|
}
|
|
|
|
#else
|
|
//samp = vco_k*vca_a;
|
|
#endif
|
|
/*
|
|
float releaseFrames = desiredReleaseFrames();
|
|
samp *= (releaseFrames - catch_decay)/releaseFrames;
|
|
*/
|
|
//LB302 samp *= (float)(decay_frames - catch_decay)/(float)decay_frames;
|
|
|
|
for( int c = 0; c < DEFAULT_CHANNELS; c++ )
|
|
{
|
|
outbuf[i][c] = samp;
|
|
}
|
|
|
|
// Handle Envelope
|
|
if(vca_mode==VcaMode::Attack) {
|
|
vca_a+=(vca_a0-vca_a)*vca_attack;
|
|
if(sample_cnt>=0.5*Engine::audioEngine()->outputSampleRate())
|
|
vca_mode = VcaMode::Idle;
|
|
}
|
|
else if(vca_mode == VcaMode::Decay) {
|
|
vca_a *= decay;
|
|
|
|
// the following line actually speeds up processing
|
|
if(vca_a < (1/65536.0)) {
|
|
vca_a = 0;
|
|
vca_mode = VcaMode::NeverPlayed;
|
|
}
|
|
}
|
|
|
|
}
|
|
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;
|
|
|
|
// Always reset vca on non-dead notes, and
|
|
// Only reset vca on decaying(decayed) and never-played
|
|
if(n->dead == 0 || (vca_mode == VcaMode::Decay || vca_mode == VcaMode::NeverPlayed)) {
|
|
//printf(" good\n");
|
|
sample_cnt = 0;
|
|
vca_mode = VcaMode::Attack;
|
|
// LB303:
|
|
//vca_a = 0;
|
|
}
|
|
else {
|
|
vca_mode = VcaMode::Idle;
|
|
}
|
|
|
|
initSlide();
|
|
|
|
// 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??
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))
|
|
vcf.loadRelaxed()->playNote();
|
|
#else
|
|
vcf.load()->playNote();
|
|
#endif
|
|
// Ensure envelope is recalculated
|
|
vcf_envpos = ENVINC;
|
|
|
|
// Double Check
|
|
//vca_mode = 0;
|
|
//vca_a = 0.0;
|
|
}
|
|
}
|
|
|
|
|
|
void Lb302Synth::initSlide()
|
|
{
|
|
// Initiate Slide
|
|
if (vco_slideinc) {
|
|
//printf(" sliding\n");
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
void Lb302Synth::playNote( NotePlayHandle * _n, SampleFrame* _working_buffer )
|
|
{
|
|
if( _n->isMasterNote() || ( _n->hasParent() && _n->isReleased() ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// sort notes: new notes to the end
|
|
m_notesMutex.lock();
|
|
if( _n->totalFramesPlayed() == 0 )
|
|
{
|
|
m_notes.append( _n );
|
|
}
|
|
else
|
|
{
|
|
m_notes.prepend( _n );
|
|
}
|
|
m_notesMutex.unlock();
|
|
|
|
release_frame = std::max(release_frame, _n->framesLeft() + _n->offset());
|
|
}
|
|
|
|
|
|
|
|
void Lb302Synth::processNote( NotePlayHandle * _n )
|
|
{
|
|
/// Start a new note.
|
|
if (_n->m_pluginData != this)
|
|
{
|
|
m_playingNote = _n;
|
|
new_freq = true;
|
|
_n->m_pluginData = this;
|
|
}
|
|
|
|
if( ! m_playingNote && ! _n->isReleased() && release_frame > 0 )
|
|
{
|
|
m_playingNote = _n;
|
|
if ( slideToggle.value() )
|
|
{
|
|
vco_slideinc = GET_INC( _n->frequency() );
|
|
}
|
|
}
|
|
|
|
// Check for slide
|
|
if( m_playingNote == _n )
|
|
{
|
|
true_freq = _n->frequency();
|
|
|
|
if( slideToggle.value() ) {
|
|
vco_slidebase = GET_INC( true_freq ); // The REAL frequency
|
|
}
|
|
else {
|
|
vco_inc = GET_INC( true_freq );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void Lb302Synth::play( SampleFrame* _working_buffer )
|
|
{
|
|
m_notesMutex.lock();
|
|
while( ! m_notes.isEmpty() )
|
|
{
|
|
processNote( m_notes.takeFirst() );
|
|
};
|
|
m_notesMutex.unlock();
|
|
|
|
const fpp_t frames = Engine::audioEngine()->framesPerPeriod();
|
|
|
|
process( _working_buffer, frames );
|
|
// release_frame = 0; //removed for issue # 1432
|
|
}
|
|
|
|
|
|
|
|
void Lb302Synth::deleteNotePluginData( NotePlayHandle * _n )
|
|
{
|
|
//printf("GONE\n");
|
|
if( m_playingNote == _n )
|
|
{
|
|
m_playingNote = nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
gui::PluginView * Lb302Synth::instantiateView( QWidget * _parent )
|
|
{
|
|
return( new gui::Lb302SynthView( this, _parent ) );
|
|
}
|
|
|
|
namespace gui
|
|
{
|
|
|
|
|
|
Lb302SynthView::Lb302SynthView( Instrument * _instrument, QWidget * _parent ) :
|
|
InstrumentViewFixedSize( _instrument, _parent )
|
|
{
|
|
// GUI
|
|
m_vcfCutKnob = new Knob( KnobType::Bright26, this );
|
|
m_vcfCutKnob->move( 75, 130 );
|
|
m_vcfCutKnob->setHintText( tr( "Cutoff Freq:" ), "" );
|
|
|
|
m_vcfResKnob = new Knob( KnobType::Bright26, this );
|
|
m_vcfResKnob->move( 120, 130 );
|
|
m_vcfResKnob->setHintText( tr( "Resonance:" ), "" );
|
|
|
|
m_vcfModKnob = new Knob( KnobType::Bright26, this );
|
|
m_vcfModKnob->move( 165, 130 );
|
|
m_vcfModKnob->setHintText( tr( "Env Mod:" ), "" );
|
|
|
|
m_vcfDecKnob = new Knob( KnobType::Bright26, this );
|
|
m_vcfDecKnob->move( 210, 130 );
|
|
m_vcfDecKnob->setHintText( tr( "Decay:" ), "" );
|
|
|
|
m_slideToggle = new LedCheckBox( "", this );
|
|
m_slideToggle->move( 10, 180 );
|
|
|
|
/* m_accentToggle = new LedCheckBox( "", this );
|
|
m_accentToggle->move( 10, 200 );
|
|
m_accentToggle->setDisabled(true);*/ // accent removed pending real implementation - no need for non-functional buttons
|
|
|
|
m_deadToggle = new LedCheckBox( "", this );
|
|
m_deadToggle->move( 10, 200 );
|
|
|
|
m_db24Toggle = new LedCheckBox( "", this );
|
|
m_db24Toggle->move( 10, 150);
|
|
m_db24Toggle->setToolTip(
|
|
tr( "303-es-que, 24dB/octave, 3 pole filter" ) );
|
|
|
|
|
|
m_slideDecKnob = new Knob( KnobType::Bright26, this );
|
|
m_slideDecKnob->move( 210, 75 );
|
|
m_slideDecKnob->setHintText( tr( "Slide Decay:" ), "" );
|
|
|
|
m_distKnob = new Knob( KnobType::Bright26, this );
|
|
m_distKnob->move( 210, 190 );
|
|
m_distKnob->setHintText( tr( "DIST:" ), "" );
|
|
|
|
|
|
// Shapes
|
|
// move to 120,75
|
|
const int waveBtnX = 10;
|
|
const int waveBtnY = 96;
|
|
auto sawWaveBtn = new PixmapButton(this, tr("Saw wave"));
|
|
sawWaveBtn->move( waveBtnX, waveBtnY );
|
|
sawWaveBtn->setActiveGraphic( embed::getIconPixmap(
|
|
"saw_wave_active" ) );
|
|
sawWaveBtn->setInactiveGraphic( embed::getIconPixmap(
|
|
"saw_wave_inactive" ) );
|
|
sawWaveBtn->setToolTip(
|
|
tr( "Click here for a saw-wave." ) );
|
|
|
|
auto triangleWaveBtn = new PixmapButton(this, tr("Triangle wave"));
|
|
triangleWaveBtn->move( waveBtnX+(16*1), waveBtnY );
|
|
triangleWaveBtn->setActiveGraphic(
|
|
embed::getIconPixmap( "triangle_wave_active" ) );
|
|
triangleWaveBtn->setInactiveGraphic(
|
|
embed::getIconPixmap( "triangle_wave_inactive" ) );
|
|
triangleWaveBtn->setToolTip(
|
|
tr( "Click here for a triangle-wave." ) );
|
|
|
|
auto sqrWaveBtn = new PixmapButton(this, tr("Square wave"));
|
|
sqrWaveBtn->move( waveBtnX+(16*2), waveBtnY );
|
|
sqrWaveBtn->setActiveGraphic( embed::getIconPixmap(
|
|
"square_wave_active" ) );
|
|
sqrWaveBtn->setInactiveGraphic( embed::getIconPixmap(
|
|
"square_wave_inactive" ) );
|
|
sqrWaveBtn->setToolTip(
|
|
tr( "Click here for a square-wave." ) );
|
|
|
|
auto roundSqrWaveBtn = new PixmapButton(this, tr("Rounded square wave"));
|
|
roundSqrWaveBtn->move( waveBtnX+(16*3), waveBtnY );
|
|
roundSqrWaveBtn->setActiveGraphic( embed::getIconPixmap(
|
|
"round_square_wave_active" ) );
|
|
roundSqrWaveBtn->setInactiveGraphic( embed::getIconPixmap(
|
|
"round_square_wave_inactive" ) );
|
|
roundSqrWaveBtn->setToolTip(
|
|
tr( "Click here for a square-wave with a rounded end." ) );
|
|
|
|
auto moogWaveBtn = new PixmapButton(this, tr("Moog wave"));
|
|
moogWaveBtn->move( waveBtnX+(16*4), waveBtnY );
|
|
moogWaveBtn->setActiveGraphic(
|
|
embed::getIconPixmap( "moog_saw_wave_active" ) );
|
|
moogWaveBtn->setInactiveGraphic(
|
|
embed::getIconPixmap( "moog_saw_wave_inactive" ) );
|
|
moogWaveBtn->setToolTip(
|
|
tr( "Click here for a moog-like wave." ) );
|
|
|
|
auto sinWaveBtn = new PixmapButton(this, tr("Sine wave"));
|
|
sinWaveBtn->move( waveBtnX+(16*5), waveBtnY );
|
|
sinWaveBtn->setActiveGraphic( embed::getIconPixmap(
|
|
"sin_wave_active" ) );
|
|
sinWaveBtn->setInactiveGraphic( embed::getIconPixmap(
|
|
"sin_wave_inactive" ) );
|
|
sinWaveBtn->setToolTip(
|
|
tr( "Click for a sine-wave." ) );
|
|
|
|
auto exponentialWaveBtn = new PixmapButton(this, tr("White noise wave"));
|
|
exponentialWaveBtn->move( waveBtnX+(16*6), waveBtnY );
|
|
exponentialWaveBtn->setActiveGraphic(
|
|
embed::getIconPixmap( "exp_wave_active" ) );
|
|
exponentialWaveBtn->setInactiveGraphic(
|
|
embed::getIconPixmap( "exp_wave_inactive" ) );
|
|
exponentialWaveBtn->setToolTip(
|
|
tr( "Click here for an exponential wave." ) );
|
|
|
|
auto whiteNoiseWaveBtn = new PixmapButton(this, tr("White noise wave"));
|
|
whiteNoiseWaveBtn->move( waveBtnX+(16*7), waveBtnY );
|
|
whiteNoiseWaveBtn->setActiveGraphic(
|
|
embed::getIconPixmap( "white_noise_wave_active" ) );
|
|
whiteNoiseWaveBtn->setInactiveGraphic(
|
|
embed::getIconPixmap( "white_noise_wave_inactive" ) );
|
|
whiteNoiseWaveBtn->setToolTip(
|
|
tr( "Click here for white-noise." ) );
|
|
|
|
auto blSawWaveBtn = new PixmapButton(this, tr("Bandlimited saw wave"));
|
|
blSawWaveBtn->move( waveBtnX+(16*9)-8, waveBtnY );
|
|
blSawWaveBtn->setActiveGraphic(
|
|
embed::getIconPixmap( "saw_wave_active" ) );
|
|
blSawWaveBtn->setInactiveGraphic(
|
|
embed::getIconPixmap( "saw_wave_inactive" ) );
|
|
blSawWaveBtn->setToolTip(
|
|
tr( "Click here for bandlimited saw wave." ) );
|
|
|
|
auto blSquareWaveBtn = new PixmapButton(this, tr("Bandlimited square wave"));
|
|
blSquareWaveBtn->move( waveBtnX+(16*10)-8, waveBtnY );
|
|
blSquareWaveBtn->setActiveGraphic(
|
|
embed::getIconPixmap( "square_wave_active" ) );
|
|
blSquareWaveBtn->setInactiveGraphic(
|
|
embed::getIconPixmap( "square_wave_inactive" ) );
|
|
blSquareWaveBtn->setToolTip(
|
|
tr( "Click here for bandlimited square wave." ) );
|
|
|
|
auto blTriangleWaveBtn = new PixmapButton(this, tr("Bandlimited triangle wave"));
|
|
blTriangleWaveBtn->move( waveBtnX+(16*11)-8, waveBtnY );
|
|
blTriangleWaveBtn->setActiveGraphic(
|
|
embed::getIconPixmap( "triangle_wave_active" ) );
|
|
blTriangleWaveBtn->setInactiveGraphic(
|
|
embed::getIconPixmap( "triangle_wave_inactive" ) );
|
|
blTriangleWaveBtn->setToolTip(
|
|
tr( "Click here for bandlimited triangle wave." ) );
|
|
|
|
auto blMoogWaveBtn = new PixmapButton(this, tr("Bandlimited moog saw wave"));
|
|
blMoogWaveBtn->move( waveBtnX+(16*12)-8, waveBtnY );
|
|
blMoogWaveBtn->setActiveGraphic(
|
|
embed::getIconPixmap( "moog_saw_wave_active" ) );
|
|
blMoogWaveBtn->setInactiveGraphic(
|
|
embed::getIconPixmap( "moog_saw_wave_inactive" ) );
|
|
blMoogWaveBtn->setToolTip(
|
|
tr( "Click here for bandlimited moog saw wave." ) );
|
|
|
|
|
|
m_waveBtnGrp = new automatableButtonGroup( this );
|
|
m_waveBtnGrp->addButton( sawWaveBtn );
|
|
m_waveBtnGrp->addButton( triangleWaveBtn );
|
|
m_waveBtnGrp->addButton( sqrWaveBtn );
|
|
m_waveBtnGrp->addButton( roundSqrWaveBtn );
|
|
m_waveBtnGrp->addButton( moogWaveBtn );
|
|
m_waveBtnGrp->addButton( sinWaveBtn );
|
|
m_waveBtnGrp->addButton( exponentialWaveBtn );
|
|
m_waveBtnGrp->addButton( whiteNoiseWaveBtn );
|
|
m_waveBtnGrp->addButton( blSawWaveBtn );
|
|
m_waveBtnGrp->addButton( blSquareWaveBtn );
|
|
m_waveBtnGrp->addButton( blTriangleWaveBtn );
|
|
m_waveBtnGrp->addButton( blMoogWaveBtn );
|
|
|
|
setAutoFillBackground( true );
|
|
QPalette pal;
|
|
pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap(
|
|
"artwork" ) );
|
|
setPalette( pal );
|
|
}
|
|
|
|
|
|
void Lb302SynthView::modelChanged()
|
|
{
|
|
auto syn = castModel<Lb302Synth>();
|
|
|
|
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_waveBtnGrp->setModel( &syn->wave_shape );
|
|
|
|
m_slideToggle->setModel( &syn->slideToggle );
|
|
/*m_accentToggle->setModel( &syn->accentToggle );*/
|
|
m_deadToggle->setModel( &syn->deadToggle );
|
|
m_db24Toggle->setModel( &syn->db24Toggle );
|
|
}
|
|
|
|
|
|
} // namespace gui
|
|
|
|
|
|
extern "C"
|
|
{
|
|
|
|
// necessary for getting instance out of shared lib
|
|
PLUGIN_EXPORT Plugin * lmms_plugin_main( Model * m, void * )
|
|
{
|
|
|
|
return( new Lb302Synth(
|
|
static_cast<InstrumentTrack *>( m ) ) );
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} // namespace lmms
|