@@ -0,0 +1,16 @@ | |||
**/build/ | |||
**/build_test/ | |||
**/dist/ | |||
**/Debug/ | |||
**/Release/ | |||
**/.vs/ | |||
.vscode/ | |||
*.opensdf | |||
*.sdf | |||
*.ipch | |||
Squinky.suo | |||
*.dll | |||
*.so | |||
*.dylib | |||
*.exe | |||
*.user |
@@ -7,7 +7,17 @@ All rights reserved. | |||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |||
* Copyright 2016 Andrew Belt | |||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |||
* Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
@@ -65,3 +75,40 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
Component Library graphics by Grayscale (http://grayscale.info/) | |||
Licensed under CC BY-NC 4.0 (https://creativecommons.org/licenses/by-nc/4.0/) | |||
# Befaco modules for VCV Rack | |||
Copyright 2016 Andrew Belt | |||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |||
Copyright 2016 Andrew Belt | |||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
# VCV Fundamental modules | |||
Copyright 2016 Andrew Belt | |||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@@ -2,13 +2,14 @@ | |||
SLUG = squinkylabs-plug1 | |||
# Must follow the format in the Versioning section of https://vcvrack.com/manual/PluginDevelopmentTutorial.html | |||
VERSION = 0.6.5 | |||
VERSION = 0.6.9 | |||
# FLAGS will be passed to both the C and C++ compiler | |||
FLAGS += -I./dsp/generators -I./dsp/utils -I./dsp/filters | |||
FLAGS += -I./dsp/third-party/falco -I./dsp/third-party/kiss_fft130 -I./dsp/third-party/kiss_fft130/tools | |||
FLAGS += -I./dsp/third-party/falco -I./dsp/third-party/kiss_fft130 | |||
FLAGS += -I./dsp/third-party/kiss_fft130/tools -I./dsp/third-party/src | |||
FLAGS += -I./sqsrc/thread -I./dsp/fft -I./composites | |||
FLAGS += -I./sqsrc/noise -I./sqsrc/util -I./sqsrc/clock | |||
FLAGS += -I./sqsrc/noise -I./sqsrc/util -I./sqsrc/clock -I./sqsrc/grammar -I./sqsrc/delay | |||
CFLAGS += | |||
CXXFLAGS += | |||
@@ -35,6 +36,7 @@ LDFLAGS += -lpthread | |||
SOURCES += $(wildcard src/*.cpp) | |||
SOURCES += $(wildcard dsp/**/*.cpp) | |||
SOURCES += $(wildcard dsp/third-party/falco/*.cpp) | |||
xxSOURCES += dsp/third-party/src/minblep.cpp | |||
SOURCES += dsp/third-party/kiss_fft130/kiss_fft.c | |||
SOURCES += dsp/third-party/kiss_fft130/tools/kiss_fftr.c | |||
SOURCES += $(wildcard sqsrc/**/*.cpp) | |||
@@ -4,10 +4,12 @@ This project is a growing collection of modules for the VCV Rack vritual modular | |||
You can find us on Facebook [here](https://www.facebook.com/SquinkyLabs). | |||
## Manuals | |||
## Manuals and Release Notes | |||
Here is the user's manual for our modules: [instruction manual](./docs/booty-shifter.md). It contains descriptions of all of them. | |||
The [release notes](./docs/release-notes.md) describe recent changes. | |||
## Contributing | |||
Please use our GitHub issues page to report problems, request features, etc. If you don’t already have a GitHub account you will need to create one, as you must be logged in to post to GitHub. | |||
@@ -0,0 +1,65 @@ | |||
#pragma once | |||
template <class TBase> | |||
class Blank : public TBase | |||
{ | |||
public: | |||
Blank(struct Module * module) : TBase(module) | |||
{ | |||
} | |||
Blank() : TBase() | |||
{ | |||
} | |||
/** | |||
* re-calc everything that changes with sample | |||
* rate. Also everything that depends on baseFrequency. | |||
* | |||
* Only needs to be called once. | |||
*/ | |||
void init(); | |||
enum ParamIds | |||
{ | |||
NUM_PARAMS | |||
}; | |||
enum InputIds | |||
{ | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds | |||
{ | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds | |||
{ | |||
NUM_LIGHTS | |||
}; | |||
/** | |||
* Main processing entry point. Called every sample | |||
*/ | |||
void step() override; | |||
private: | |||
}; | |||
template <class TBase> | |||
inline void Blank<TBase>::init() | |||
{ | |||
} | |||
template <class TBase> | |||
inline void Blank<TBase>::step() | |||
{ | |||
} | |||
@@ -0,0 +1,382 @@ | |||
#pragma once | |||
#include <algorithm> | |||
#include "AudioMath.h" | |||
#include "poly.h" | |||
#include "ObjectCache.h" | |||
#include "SinOscillator.h" | |||
using Osc = SinOscillator<float, true>; | |||
#ifndef _CLAMP | |||
#define _CLAMP | |||
namespace std { | |||
inline float clamp(float v, float lo, float hi) | |||
{ | |||
assert(lo < hi); | |||
return std::min(hi, std::max(v, lo)); | |||
} | |||
} | |||
#endif | |||
/** | |||
*/ | |||
template <class TBase> | |||
class CHB : public TBase | |||
{ | |||
public: | |||
CHB(struct Module * module) : TBase(module) | |||
{ | |||
init(); | |||
} | |||
CHB() : TBase() | |||
{ | |||
init(); | |||
} | |||
enum ParamIds | |||
{ | |||
PARAM_TUNE, | |||
PARAM_OCTAVE, | |||
PARAM_EXTGAIN, | |||
PARAM_PITCH_MOD_TRIM, | |||
PARAM_LINEAR_FM_TRIM, | |||
PARAM_EXTGAIN_TRIM, | |||
PARAM_FOLD, | |||
PARAM_SLOPE, | |||
PARAM_MAG_EVEN, | |||
PARAM_MAG_ODD, | |||
PARAM_H0, | |||
PARAM_H1, | |||
PARAM_H2, | |||
PARAM_H3, | |||
PARAM_H4, | |||
PARAM_H5, | |||
PARAM_H6, | |||
PARAM_H7, | |||
PARAM_H8, | |||
PARAM_H9, | |||
NUM_PARAMS | |||
}; | |||
const int numHarmonics = 1 + PARAM_H9 - PARAM_H0; | |||
enum InputIds | |||
{ | |||
CV_INPUT, | |||
PITCH_MOD_INPUT, | |||
LINEAR_FM_INPUT, | |||
ENV_INPUT, | |||
GAIN_INPUT, | |||
AUDIO_INPUT, | |||
SLOPE_INPUT, | |||
H0_INPUT, | |||
H1_INPUT, | |||
H2_INPUT, | |||
H3_INPUT, | |||
H4_INPUT, | |||
H5_INPUT, | |||
H6_INPUT, | |||
H7_INPUT, | |||
H8_INPUT, | |||
H9_INPUT, | |||
H10_INPUT, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds | |||
{ | |||
MIX_OUTPUT, | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds | |||
{ | |||
GAIN_GREEN_LIGHT, | |||
GAIN_RED_LIGHT, | |||
NUM_LIGHTS | |||
}; | |||
/** | |||
* Main processing entry point. Called every sample | |||
*/ | |||
void step() override; | |||
void setEconomy(bool); | |||
bool isEconomy() const; | |||
float _freq = 0; | |||
private: | |||
bool economyMode = true; // let's default to economy mode | |||
int cycleCount = 1; | |||
int clipCount = 0; | |||
int signalCount = 0; | |||
const int clipDuration = 4000; | |||
float finalGain = 0; | |||
bool isExternalAudio = false; | |||
/** | |||
* The waveshaper that is the heart of this module | |||
*/ | |||
Poly<double, 11> poly; | |||
/* | |||
* maps freq multiple to "octave". | |||
* In other words, log base 12. | |||
*/ | |||
float _octave[11]; | |||
float getOctave(int mult) const ; | |||
void init(); | |||
float _volume[11] = {0}; | |||
/** | |||
* Internal sine wave oscillator to drive the waveshaper | |||
*/ | |||
SinOscillatorParams<float> sinParams; | |||
SinOscillatorState<float> sinState; | |||
// just maps 0..1 to 0..1 | |||
std::shared_ptr<LookupTableParams<float>> audioTaper = {ObjectCache<float>::getAudioTaper()}; | |||
AudioMath::ScaleFun<float> gainCombiner = AudioMath::makeLinearScaler(0.f, 1.f); | |||
std::function<float(float)> expLookup = ObjectCache<float>::getExp2Ex(); | |||
std::shared_ptr<LookupTableParams<float>> db2gain = ObjectCache<float>::getDb2Gain(); | |||
/** | |||
* Audio taper for the slope. | |||
*/ | |||
AudioMath::ScaleFun<float> slopeScale = | |||
{AudioMath::makeLinearScaler<float>(-18, 0)}; | |||
/** | |||
* do one-time calculations when sample rate changes | |||
*/ | |||
void internalUpdate(); | |||
/** | |||
* Do all the processing to get the input waveform | |||
* that will be fed to the polynomials | |||
*/ | |||
float getInput(); | |||
void calcVolumes(float *); | |||
void checkClipping(float sample); | |||
/** | |||
* Does audio taper | |||
* @param raw = 0..1 | |||
* @return 0..1 | |||
*/ | |||
float taper(float raw) | |||
{ | |||
return LookupTable<float>::lookup(*audioTaper, raw, false); | |||
} | |||
}; | |||
template <class TBase> | |||
inline void CHB<TBase>::init() | |||
{ | |||
for (int i = 0; i < 11; ++i) { | |||
_octave[i] = log2(float(i + 1)); | |||
} | |||
} | |||
template <class TBase> | |||
inline float CHB<TBase>::getOctave(int i) const | |||
{ | |||
assert(i >= 0 && i < 11); | |||
return _octave[i]; | |||
} | |||
#if 1 | |||
template <class TBase> | |||
inline void CHB<TBase>::setEconomy(bool b) | |||
{ | |||
economyMode = b; | |||
} | |||
template <class TBase> | |||
inline bool CHB<TBase>::isEconomy() const | |||
{ | |||
return economyMode; | |||
} | |||
#endif | |||
template <class TBase> | |||
inline float CHB<TBase>::getInput() | |||
{ | |||
assert(TBase::engineGetSampleTime() > 0); | |||
// Get the frequency from the inputs. | |||
float pitch = 1.0f + roundf(TBase::params[PARAM_OCTAVE].value) + TBase::params[PARAM_TUNE].value / 12.0f; | |||
pitch += TBase::inputs[CV_INPUT].value; | |||
pitch += .25f * TBase::inputs[PITCH_MOD_INPUT].value * | |||
taper(TBase::params[PARAM_PITCH_MOD_TRIM].value); | |||
const float q = float(log2(261.626)); // move up to pitch range of EvenVCO | |||
pitch += q; | |||
_freq = expLookup(pitch); | |||
if (_freq < .01f) { | |||
_freq = .01f; | |||
} | |||
// Multiply in the Linear FM contribution | |||
_freq *= 1.0f + TBase::inputs[LINEAR_FM_INPUT].value * taper(TBase::params[PARAM_LINEAR_FM_TRIM].value); | |||
float time = std::clamp(_freq * TBase::engineGetSampleTime(), -.5f, 0.5f); | |||
Osc::setFrequency(sinParams, time); | |||
if (cycleCount == 0) { | |||
// Get the gain from the envelope generator in | |||
// eGain = {0 .. 10.0f } | |||
float eGain = TBase::inputs[ENV_INPUT].active ? TBase::inputs[ENV_INPUT].value : 10.f; | |||
isExternalAudio = TBase::inputs[AUDIO_INPUT].active; | |||
const float gainKnobValue = TBase::params[PARAM_EXTGAIN].value; | |||
const float gainCVValue = TBase::inputs[GAIN_INPUT].value; | |||
const float gainTrimValue = TBase::params[PARAM_EXTGAIN_TRIM].value; | |||
const float combinedGain = gainCombiner(gainCVValue, gainKnobValue, gainTrimValue); | |||
// tapered gain {0 .. 0.5} | |||
const float taperedGain = .5f * taper(combinedGain); | |||
// final gain 0..5 | |||
finalGain = taperedGain * eGain; | |||
} | |||
float input = finalGain * (isExternalAudio ? | |||
TBase::inputs[AUDIO_INPUT].value : | |||
Osc::run(sinState, sinParams)); | |||
checkClipping(input); | |||
// Now clip or fold to keep in -1...+1 | |||
if (TBase::params[PARAM_FOLD].value > .5) { | |||
input = AudioMath::fold(input); | |||
} else { | |||
input = std::max(input, -1.f); | |||
input = std::min(input, 1.f); | |||
} | |||
return input; | |||
} | |||
/** | |||
* Desired behavior: | |||
* If we clip, led goes red and stays red for clipDuration | |||
* if not red, sign present goes green | |||
* nothing - turn off | |||
*/ | |||
template <class TBase> | |||
inline void CHB<TBase>::checkClipping(float input) | |||
{ | |||
if (input > 1) { | |||
// if clipping, go red | |||
clipCount = clipDuration; | |||
TBase::lights[GAIN_RED_LIGHT].value = 10; | |||
TBase::lights[GAIN_GREEN_LIGHT].value = 0; | |||
} else if (clipCount) { | |||
// If red,run down the clock | |||
clipCount--; | |||
if (clipCount <= 0) { | |||
TBase::lights[GAIN_RED_LIGHT].value = 0; | |||
TBase::lights[GAIN_GREEN_LIGHT].value = 0; | |||
} | |||
} else if (input > .3f) { | |||
// if signal present | |||
signalCount = clipDuration; | |||
TBase::lights[GAIN_GREEN_LIGHT].value = 10; | |||
} else if (signalCount) { | |||
signalCount--; | |||
if (signalCount <= 0) { | |||
TBase::lights[GAIN_GREEN_LIGHT].value = 0; | |||
} | |||
} | |||
} | |||
template <class TBase> | |||
inline void CHB<TBase>::calcVolumes(float * volumes) | |||
{ | |||
// first get the harmonics knobs, and scale them | |||
for (int i = 0; i < numHarmonics; ++i) { | |||
float val = taper(TBase::params[i + PARAM_H0].value); // apply taper to the knobs | |||
// If input connected, scale and multiply with knob value | |||
if (TBase::inputs[i + H0_INPUT].active) { | |||
const float inputCV = TBase::inputs[i + H0_INPUT].value * .1f; | |||
val *= std::max(inputCV, 0.f); | |||
} | |||
volumes[i] = val; | |||
} | |||
// Second: apply the even and odd knobs | |||
{ | |||
const float even = taper(TBase::params[PARAM_MAG_EVEN].value); | |||
const float odd = taper(TBase::params[PARAM_MAG_ODD].value); | |||
for (int i = 1; i < 11; ++i) { | |||
const float mul = (i & 1) ? even : odd; // 0 = fundamental, 1=even, 2=odd.... | |||
volumes[i] *= mul; | |||
} | |||
} | |||
// Third: slope | |||
{ | |||
const float slope = slopeScale(TBase::params[PARAM_SLOPE].value, TBase::inputs[SLOPE_INPUT].value, 1); | |||
for (int i = 0; i < 11; ++i) { | |||
float slopeAttenDb = slope * getOctave(i); | |||
float slopeAtten = LookupTable<float>::lookup(*db2gain, slopeAttenDb); | |||
volumes[i] *= slopeAtten; | |||
} | |||
} | |||
} | |||
template <class TBase> | |||
inline void CHB<TBase>::step() | |||
{ | |||
if (economyMode) { | |||
if (--cycleCount < 0) { | |||
cycleCount = 3; | |||
} | |||
} else { | |||
cycleCount = 0; | |||
} | |||
// do all the processing to get the carrier signal | |||
const float input = getInput(); | |||
#if 0 | |||
{ | |||
static float high=0; | |||
static float low=0; | |||
if (input<low || input > high) { | |||
high = std::max(high, input); | |||
low = std::min(low, input); | |||
printf("%f, %f\n", high, low); | |||
fflush(stdout); | |||
} | |||
} | |||
#endif | |||
// float volume[11]; | |||
if (cycleCount == 0) { | |||
calcVolumes(_volume); | |||
for (int i = 0; i < 11; ++i) { | |||
poly.setGain(i, _volume[i]); | |||
} | |||
} | |||
float output = poly.run(input); | |||
TBase::outputs[MIX_OUTPUT].value = 5.0f * output; | |||
} | |||
@@ -75,7 +75,7 @@ public: | |||
/** | |||
* Main processing entry point. Called every sample | |||
*/ | |||
void step(); | |||
void step() override; | |||
float getSlope() const; | |||
@@ -159,9 +159,9 @@ protected: | |||
FFT::makeNoiseSpectrum(noiseSpectrum.get(), | |||
noiseMessage->noiseSpec); | |||
// Now inverse FFT to time domain noise in client's buffer | |||
// Now inverse FFT to time domain noise in client's buffer | |||
FFT::inverse(noiseMessage->dataBuffer.get(), *noiseSpectrum.get()); | |||
FFT::normalize(noiseMessage->dataBuffer.get()); | |||
FFT::normalize(noiseMessage->dataBuffer.get(), 5); // use 5v amplitude. | |||
sendMessageToClient(noiseMessage); | |||
} | |||
private: | |||
@@ -0,0 +1,279 @@ | |||
#pragma once | |||
#include "MinBLEPVCO.h" | |||
#include "ObjectCache.h" | |||
#include "dsp/functions.hpp" // rack math | |||
/** | |||
* | |||
*/ | |||
template <class TBase> | |||
class EV3 : public TBase | |||
{ | |||
public: | |||
friend class TestMB; | |||
EV3(struct Module * module) : TBase(module) | |||
{ | |||
init(); | |||
} | |||
EV3() : TBase() | |||
{ | |||
init(); | |||
} | |||
enum class Waves | |||
{ | |||
SIN, | |||
TRI, | |||
SAW, | |||
SQUARE, | |||
EVEN, | |||
NONE, | |||
END // just a marker | |||
}; | |||
enum ParamIds | |||
{ | |||
MIX1_PARAM, | |||
MIX2_PARAM, | |||
MIX3_PARAM, | |||
OCTAVE1_PARAM, | |||
SEMI1_PARAM, | |||
FINE1_PARAM, | |||
FM1_PARAM, | |||
SYNC1_PARAM, | |||
WAVE1_PARAM, | |||
PW1_PARAM, | |||
PWM1_PARAM, | |||
OCTAVE2_PARAM, | |||
SEMI2_PARAM, | |||
FINE2_PARAM, | |||
FM2_PARAM, | |||
SYNC2_PARAM, | |||
WAVE2_PARAM, | |||
PW2_PARAM, | |||
PWM2_PARAM, | |||
OCTAVE3_PARAM, | |||
SEMI3_PARAM, | |||
FINE3_PARAM, | |||
FM3_PARAM, | |||
SYNC3_PARAM, | |||
WAVE3_PARAM, | |||
PW3_PARAM, | |||
PWM3_PARAM, | |||
NUM_PARAMS | |||
}; | |||
enum InputIds | |||
{ | |||
CV1_INPUT, | |||
CV2_INPUT, | |||
CV3_INPUT, | |||
FM1_INPUT, | |||
FM2_INPUT, | |||
FM3_INPUT, | |||
PWM1_INPUT, | |||
PWM2_INPUT, | |||
PWM3_INPUT, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds | |||
{ | |||
MIX_OUTPUT, | |||
VCO1_OUTPUT, | |||
VCO2_OUTPUT, | |||
VCO3_OUTPUT, | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds | |||
{ | |||
NUM_LIGHTS | |||
}; | |||
void step() override; | |||
private: | |||
void setSync(); | |||
void processPitchInputs(); | |||
void processPitchInputs(int osc); | |||
void processWaveforms(); | |||
void stepVCOs(); | |||
void init(); | |||
void processPWInputs(); | |||
void processPWInput(int osc); | |||
float getInput(int osc, InputIds in0, InputIds in1, InputIds in2); | |||
MinBLEPVCO vcos[3]; | |||
float _freq[3]; | |||
float _out[3]; | |||
std::function<float(float)> expLookup = | |||
ObjectCache<float>::getExp2Ex(); | |||
std::shared_ptr<LookupTableParams<float>> audioTaper = | |||
ObjectCache<float>::getAudioTaper(); | |||
}; | |||
template <class TBase> | |||
inline void EV3<TBase>::init() | |||
{ | |||
for (int i = 0; i < 3; ++i) { | |||
vcos[i].setWaveform(MinBLEPVCO::Waveform::Saw); | |||
} | |||
vcos[0].setSyncCallback([this](float f) { | |||
if (TBase::params[SYNC2_PARAM].value > .5) { | |||
vcos[1].onMasterSync(f); | |||
} | |||
if (TBase::params[SYNC3_PARAM].value > .5) { | |||
vcos[2].onMasterSync(f); | |||
} | |||
}); | |||
} | |||
template <class TBase> | |||
inline void EV3<TBase>::setSync() | |||
{ | |||
vcos[0].setSyncEnabled(false); | |||
vcos[1].setSyncEnabled(TBase::params[SYNC2_PARAM].value > .5); | |||
vcos[2].setSyncEnabled(TBase::params[SYNC3_PARAM].value > .5); | |||
} | |||
template <class TBase> | |||
inline void EV3<TBase>::processWaveforms() | |||
{ | |||
vcos[0].setWaveform((MinBLEPVCO::Waveform)(int)TBase::params[WAVE1_PARAM].value); | |||
vcos[1].setWaveform((MinBLEPVCO::Waveform)(int)TBase::params[WAVE2_PARAM].value); | |||
vcos[2].setWaveform((MinBLEPVCO::Waveform)(int)TBase::params[WAVE3_PARAM].value); | |||
} | |||
template <class TBase> | |||
float EV3<TBase>::getInput(int osc, InputIds in1, InputIds in2, InputIds in3) | |||
{ | |||
const bool in2Connected = TBase::inputs[in2].active; | |||
const bool in3Connected = TBase::inputs[in3].active; | |||
InputIds id = in1; | |||
if ((osc == 1) && in2Connected) { | |||
id = in2; | |||
} | |||
if (osc == 2) { | |||
if (in3Connected) id = in3; | |||
else if (in2Connected) id = in2; | |||
} | |||
return TBase::inputs[id].value; | |||
} | |||
template <class TBase> | |||
void EV3<TBase>::processPWInput(int osc) | |||
{ | |||
const float pwmInput = getInput(osc, PWM1_INPUT, PWM2_INPUT, PWM3_INPUT) / 5.f; | |||
const int delta = osc * (OCTAVE2_PARAM - OCTAVE1_PARAM); | |||
const float pwmTrim = TBase::params[PWM1_PARAM + delta].value; | |||
const float pwInit = TBase::params[PW1_PARAM + delta].value; | |||
float pw = pwInit + pwmInput * pwmTrim; | |||
const float minPw = 0.05f; | |||
pw = rack::rescale(std::clamp(pw, -1.0f, 1.0f), -1.0f, 1.0f, minPw, 1.0f - minPw); | |||
vcos[osc].setPulseWidth(pw); | |||
} | |||
template <class TBase> | |||
inline void EV3<TBase>::processPWInputs() | |||
{ | |||
processPWInput(0); | |||
processPWInput(1); | |||
processPWInput(2); | |||
} | |||
template <class TBase> | |||
inline void EV3<TBase>::step() | |||
{ | |||
setSync(); | |||
processPitchInputs(); | |||
processWaveforms(); | |||
processPWInputs(); | |||
stepVCOs(); | |||
float mix = 0; | |||
for (int i = 0; i < 3; ++i) { | |||
const float knob = TBase::params[MIX1_PARAM + i].value; | |||
const float gain = LookupTable<float>::lookup(*audioTaper, knob, false); | |||
const float rawWaveform = vcos[i].getOutput(); | |||
const float scaledWaveform = rawWaveform * gain; | |||
mix += scaledWaveform; | |||
_out[i] = scaledWaveform; | |||
TBase::outputs[VCO1_OUTPUT + i].value = rawWaveform; | |||
} | |||
TBase::outputs[MIX_OUTPUT].value = mix; | |||
} | |||
template <class TBase> | |||
inline void EV3<TBase>::stepVCOs() | |||
{ | |||
for (int i = 0; i < 3; ++i) { | |||
vcos[i].step(); | |||
} | |||
} | |||
template <class TBase> | |||
inline void EV3<TBase>::processPitchInputs() | |||
{ | |||
float lastFM = 0; | |||
for (int osc = 0; osc < 3; ++osc) { | |||
assert(osc >= 0 && osc <= 2); | |||
const int delta = osc * (OCTAVE2_PARAM - OCTAVE1_PARAM); | |||
const float cv = getInput(osc, CV1_INPUT, CV2_INPUT, CV3_INPUT); | |||
const float finePitch = TBase::params[FINE1_PARAM + delta].value / 12.0f; | |||
const float semiPitch = TBase::params[SEMI1_PARAM + delta].value / 12.0f; | |||
// const float fm = getInput(osc, FM1_INPUT, FM2_INPUT, FM3_INPUT); | |||
float pitch = 1.0f + roundf(TBase::params[OCTAVE1_PARAM + delta].value) + | |||
semiPitch + | |||
finePitch; | |||
pitch += cv; | |||
float fmCombined = 0; // The final, scaled, value (post knob | |||
if (TBase::inputs[FM1_INPUT + osc].active) { | |||
const float fm = TBase::inputs[FM1_INPUT + osc].value; | |||
// const float fmKnob = TBase::params[FM1_PARAM + delta].value; | |||
//const float fmDepth = LookupTable<float>::lookup(*audioTaper, fmKnob, false); | |||
const float fmDepth = rack::quadraticBipolar(TBase::params[FM1_PARAM + delta].value); | |||
fmCombined = (fmDepth * fm); | |||
#if 0 | |||
static float biggest = 0; | |||
if (fmCombined > biggest) { | |||
printf("CV =%f knob = %f depth=%f combined=%f\n", fm, fmKnob, fmDepth, fmCombined); | |||
fflush(stdout); | |||
biggest = fmCombined; | |||
} | |||
#endif | |||
// pitch += (fmDepth * fm * 12); | |||
} else { | |||
fmCombined = lastFM; | |||
} | |||
pitch += fmCombined; | |||
lastFM = fmCombined; | |||
const float q = float(log2(261.626)); // move up to pitch range of EvenVCO | |||
pitch += q; | |||
const float freq = expLookup(pitch); | |||
_freq[osc] = freq; | |||
vcos[osc].setNormalizedFreq(TBase::engineGetSampleTime() * freq, | |||
TBase::engineGetSampleTime()); | |||
} | |||
} | |||
@@ -67,7 +67,7 @@ public: | |||
/** | |||
* Main processing entry point. Called every sample | |||
*/ | |||
void step(); | |||
void step() override; | |||
typedef float T; // use floats for all signals | |||
T freqRange = 5; // the freq range switch | |||
@@ -0,0 +1,107 @@ | |||
#pragma once | |||
#include "FunVCO.h" | |||
//#define _ORIGVCO | |||
template <class TBase> | |||
class FunVCOComposite : public TBase | |||
{ | |||
public: | |||
FunVCOComposite() | |||
{ | |||
init(); | |||
} | |||
FunVCOComposite(struct Module * module) : TBase(module) | |||
{ | |||
init(); | |||
} | |||
enum ParamIds | |||
{ | |||
MODE_PARAM, | |||
SYNC_PARAM, | |||
FREQ_PARAM, | |||
FINE_PARAM, | |||
FM_PARAM, | |||
PW_PARAM, | |||
PWM_PARAM, | |||
NUM_PARAMS | |||
}; | |||
enum InputIds | |||
{ | |||
PITCH_INPUT, | |||
FM_INPUT, | |||
SYNC_INPUT, | |||
PW_INPUT, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds | |||
{ | |||
SIN_OUTPUT, | |||
TRI_OUTPUT, | |||
SAW_OUTPUT, | |||
SQR_OUTPUT, | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds | |||
{ | |||
NUM_LIGHTS | |||
}; | |||
void step() override; | |||
void init() | |||
{ | |||
oscillator.init(); | |||
} | |||
void setSampleRate(float rate) | |||
{ | |||
oscillator.sampleTime = 1.f / rate; | |||
} | |||
private: | |||
#ifdef _ORIGVCO | |||
VoltageControlledOscillatorOrig<16, 16> oscillator; | |||
#else | |||
VoltageControlledOscillator<16, 16> oscillator; | |||
#endif | |||
}; | |||
template <class TBase> | |||
inline void FunVCOComposite<TBase>::step() | |||
{ | |||
oscillator.analog = TBase::params[MODE_PARAM].value > 0.0f; | |||
oscillator.soft = TBase::params[SYNC_PARAM].value <= 0.0f; | |||
float pitchFine = 3.0f * quadraticBipolar(TBase::params[FINE_PARAM].value); | |||
float pitchCv = 12.0f * TBase::inputs[PITCH_INPUT].value; | |||
if (TBase::inputs[FM_INPUT].active) { | |||
pitchCv += quadraticBipolar(TBase::params[FM_PARAM].value) * 12.0f * TBase::inputs[FM_INPUT].value; | |||
} | |||
oscillator.setPitch(TBase::params[FREQ_PARAM].value, pitchFine + pitchCv); | |||
oscillator.setPulseWidth(TBase::params[PW_PARAM].value + TBase::params[PWM_PARAM].value * TBase::inputs[PW_INPUT].value / 10.0f); | |||
oscillator.syncEnabled = TBase::inputs[SYNC_INPUT].active; | |||
#ifndef _ORIGVCO | |||
oscillator.sawEnabled = TBase::outputs[SAW_OUTPUT].active; | |||
oscillator.sinEnabled = TBase::outputs[SIN_OUTPUT].active; | |||
oscillator.sqEnabled = TBase::outputs[SQR_OUTPUT].active; | |||
oscillator.triEnabled = TBase::outputs[TRI_OUTPUT].active; | |||
#endif | |||
oscillator.process(TBase::engineGetSampleTime(), TBase::inputs[SYNC_INPUT].value); | |||
// Set output | |||
if (TBase::outputs[SIN_OUTPUT].active) | |||
TBase::outputs[SIN_OUTPUT].value = 5.0f * oscillator.sin(); | |||
if (TBase::outputs[TRI_OUTPUT].active) | |||
TBase::outputs[TRI_OUTPUT].value = 5.0f * oscillator.tri(); | |||
if (TBase::outputs[SAW_OUTPUT].active) | |||
TBase::outputs[SAW_OUTPUT].value = 5.0f * oscillator.saw(); | |||
if (TBase::outputs[SQR_OUTPUT].active) | |||
TBase::outputs[SQR_OUTPUT].value = 5.0f * oscillator.sqr(); | |||
} |
@@ -0,0 +1,88 @@ | |||
#pragma once | |||
#include "ObjectCache.h" | |||
#include "GenerativeTriggerGenerator.h" | |||
#include "TriggerOutput.h" | |||
#include <memory> | |||
/** | |||
*/ | |||
template <class TBase> | |||
class GMR : public TBase | |||
{ | |||
public: | |||
GMR(struct Module * module) : TBase(module), inputClockProcessing(true) | |||
{ | |||
} | |||
GMR() : TBase(), inputClockProcessing(true) | |||
{ | |||
} | |||
void setSampleRate(float rate) | |||
{ | |||
reciprocalSampleRate = 1 / rate; | |||
} | |||
// must be called after setSampleRate | |||
void init(); | |||
enum ParamIds | |||
{ | |||
NUM_PARAMS | |||
}; | |||
enum InputIds | |||
{ | |||
CLOCK_INPUT, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds | |||
{ | |||
TRIGGER_OUTPUT, | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds | |||
{ | |||
NUM_LIGHTS | |||
}; | |||
/** | |||
* Main processing entry point. Called every sample | |||
*/ | |||
void step() override; | |||
private: | |||
float reciprocalSampleRate = 0; | |||
std::shared_ptr<GenerativeTriggerGenerator> gtg; | |||
GateTrigger inputClockProcessing; | |||
TriggerOutput outputProcessing; | |||
}; | |||
template <class TBase> | |||
inline void GMR<TBase>::init() | |||
{ | |||
StochasticGrammarDictionary::Grammar grammar = StochasticGrammarDictionary::getGrammar(0); | |||
gtg = std::make_shared<GenerativeTriggerGenerator>( | |||
AudioMath::random(), | |||
grammar.rules, | |||
grammar.numRules, | |||
grammar.firstRule); | |||
} | |||
template <class TBase> | |||
inline void GMR<TBase>::step() | |||
{ | |||
bool outClock = false; | |||
float inClock = TBase::inputs[CLOCK_INPUT].value; | |||
inputClockProcessing.go(inClock); | |||
if (inputClockProcessing.trigger()) { | |||
outClock = gtg->clock(); | |||
} | |||
outputProcessing.go(outClock); | |||
TBase::outputs[TRIGGER_OUTPUT].value = outputProcessing.get(); | |||
} | |||
@@ -0,0 +1,141 @@ | |||
#pragma once | |||
#include "GateTrigger.h" | |||
static const uint8_t gtable[256] = | |||
{ | |||
0, 1, 3, 2, 6, 7, 5, 4, 12, 13, 15, 14, 10, 11, 9, 8, | |||
24, 25, 27, 26, 30, 31, 29, 28, 20, 21, 23, 22, 18, 19, 17, 16, | |||
48, 49, 51, 50, 54, 55, 53, 52, 60, 61, 63, 62, 58, 59, 57, 56, | |||
40, 41, 43, 42, 46, 47, 45, 44, 36, 37, 39, 38, 34, 35, 33, 32, | |||
96, 97, 99, 98, 102, 103, 101, 100, 108, 109, 111, 110, 106, 107, 105, 104, | |||
120, 121, 123, 122, 126, 127, 125, 124, 116, 117, 119, 118, 114, 115, 113, 112, | |||
80, 81, 83, 82, 86, 87, 85, 84, 92, 93, 95, 94, 90, 91, 89, 88, | |||
72, 73, 75, 74, 78, 79, 77, 76, 68, 69, 71, 70, 66, 67, 65, 64, | |||
192, 193, 195, 194, 198, 199, 197, 196, 204, 205, 207, 206, 202, 203, 201, 200, | |||
216, 217, 219, 218, 222, 223, 221, 220, 212, 213, 215, 214, 210, 211, 209, 208, | |||
240, 241, 243, 242, 246, 247, 245, 244, 252, 253, 255, 254, 250, 251, 249, 248, | |||
232, 233, 235, 234, 238, 239, 237, 236, 228, 229, 231, 230, 226, 227, 225, 224, | |||
160, 161, 163, 162, 166, 167, 165, 164, 172, 173, 175, 174, 170, 171, 169, 168, | |||
184, 185, 187, 186, 190, 191, 189, 188, 180, 181, 183, 182, 178, 179, 177, 176, | |||
144, 145, 147, 146, 150, 151, 149, 148, 156, 157, 159, 158, 154, 155, 153, 152, | |||
136, 137, 139, 138, 142, 143, 141, 140, 132, 133, 135, 134, 130, 131, 129, 128 | |||
}; | |||
static const uint8_t bgtable[256] = | |||
{ | |||
0x00, 0x01, 0x03, 0x02, 0x06, 0x0E, 0x0A, 0x0B, 0x09, 0x0D, 0x0F, 0x07, 0x05, 0x04, 0x0C, 0x08, | |||
0x18, 0x1C, 0x14, 0x15, 0x17, 0x1F, 0x3F, 0x37, 0x35, 0x34, 0x3C, 0x38, 0x28, 0x2C, 0x24, 0x25, | |||
0x27, 0x2F, 0x2D, 0x29, 0x39, 0x3D, 0x1D, 0x19, 0x1B, 0x3B, 0x2B, 0x2A, 0x3A, 0x1A, 0x1E, 0x16, | |||
0x36, 0x3E, 0x2E, 0x26, 0x22, 0x32, 0x12, 0x13, 0x33, 0x23, 0x21, 0x31, 0x11, 0x10, 0x30, 0x20, | |||
0x60, 0x70, 0x50, 0x51, 0x71, 0x61, 0x63, 0x73, 0x53, 0x52, 0x72, 0x62, 0x66, 0x6E, 0x7E, 0x76, | |||
0x56, 0x5E, 0x5A, 0x7A, 0x6A, 0x6B, 0xEB, 0xEA, 0xFA, 0xDA, 0xDE, 0xD6, 0xF6, 0xFE, 0xEE, 0xE6, | |||
0xE2, 0xF2, 0xD2, 0xD3, 0xF3, 0xE3, 0xE1, 0xF1, 0xD1, 0xD0, 0xF0, 0xE0, 0xA0, 0xB0, 0x90, 0x91, | |||
0xB1, 0xA1, 0xA3, 0xB3, 0x93, 0x92, 0xB2, 0xA2, 0xA6, 0xAE, 0xBE, 0xB6, 0x96, 0x9E, 0x9A, 0xBA, | |||
0xAA, 0xAB, 0xBB, 0x9B, 0x99, 0x9D, 0xDD, 0xD9, 0xDB, 0xFB, 0x7B, 0x5B, 0x59, 0x5D, 0x7D, 0x79, | |||
0xF9, 0xFD, 0xBD, 0xB9, 0xA9, 0xE9, 0x69, 0x6D, 0x6F, 0x67, 0x65, 0x64, 0xE4, 0xE5, 0xE7, 0xEF, | |||
0xED, 0xAD, 0xAF, 0xA7, 0xA5, 0xA4, 0xAC, 0xEC, 0x6C, 0x68, 0xE8, 0xA8, 0xB8, 0xF8, 0x78, 0x7C, | |||
0xFC, 0xBC, 0xB4, 0xB5, 0xB7, 0xF7, 0xF5, 0xF4, 0x74, 0x75, 0x77, 0x7F, 0xFF, 0xBF, 0x9F, 0xDF, | |||
0x5F, 0x57, 0x55, 0x54, 0xD4, 0xD5, 0xD7, 0x97, 0x95, 0x94, 0x9C, 0xDC, 0x5C, 0x58, 0xD8, 0x98, | |||
0x88, 0xC8, 0x48, 0x4C, 0xCC, 0x8C, 0x84, 0xC4, 0x44, 0x45, 0xC5, 0x85, 0x87, 0xC7, 0x47, 0x4F, | |||
0xCF, 0x8F, 0x8D, 0xCD, 0x4D, 0x49, 0xC9, 0x89, 0x8B, 0xCB, 0x4B, 0x4A, 0xCA, 0x8A, 0x8E, 0xCE, | |||
0x4E, 0x46, 0xC6, 0x86, 0x82, 0xC2, 0x42, 0x43, 0xC3, 0x83, 0x81, 0xC1, 0x41, 0x40, 0xC0, 0x80 | |||
}; | |||
template <class TBase> | |||
class Gray : public TBase | |||
{ | |||
public: | |||
Gray(struct Module * module) : TBase(module), gateTrigger(true) | |||
{ | |||
init(); | |||
} | |||
Gray() : TBase(), gateTrigger(true) | |||
{ | |||
init(); | |||
} | |||
enum ParamIds | |||
{ | |||
PARAM_CODE, | |||
NUM_PARAMS | |||
}; | |||
enum InputIds | |||
{ | |||
INPUT_CLOCK, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds | |||
{ | |||
OUTPUT_MIXED, | |||
OUTPUT_0, | |||
OUTPUT_1, | |||
OUTPUT_2, | |||
OUTPUT_3, | |||
OUTPUT_4, | |||
OUTPUT_5, | |||
OUTPUT_6, | |||
OUTPUT_7, | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds | |||
{ | |||
LIGHT_0, | |||
LIGHT_1, | |||
LIGHT_2, | |||
LIGHT_3, | |||
LIGHT_4, | |||
LIGHT_5, | |||
LIGHT_6, | |||
LIGHT_7, | |||
NUM_LIGHTS | |||
}; | |||
/** | |||
* Main processing entry point. Called every sample | |||
*/ | |||
void step() override; | |||
private: | |||
uint8_t counterValue = 0; | |||
GateTrigger gateTrigger; | |||
int c = 0; | |||
void init(); | |||
}; | |||
template <class TBase> | |||
void Gray<TBase>::init() | |||
{ | |||
// Init all the outputs to zero, | |||
// since they don't all get update until a clock. | |||
for (int i=0; i<NUM_OUTPUTS; ++i) { | |||
TBase::outputs[i].value = 0; | |||
} | |||
} | |||
template <class TBase> | |||
void Gray<TBase>::step() | |||
{ | |||
gateTrigger.go(TBase::inputs[INPUT_CLOCK].value); | |||
if (!gateTrigger.trigger()) { | |||
return; | |||
} | |||
++counterValue; | |||
const uint8_t* table = TBase::params[PARAM_CODE].value > .5 ? gtable : bgtable; | |||
const auto g0 = table[counterValue]; | |||
auto g = g0; | |||
for (int i=0; i<8; ++i) { | |||
bool b = g & 1; | |||
TBase::lights[i + LIGHT_0].value = b ? 10 : 0; | |||
TBase::outputs[i + OUTPUT_0].value = b ? 10 : 0; | |||
g >>= 1; | |||
} | |||
TBase::outputs[OUTPUT_MIXED].value = (float) g0/25.f; | |||
} |
@@ -0,0 +1,247 @@ | |||
#pragma once | |||
#include "ButterworthFilterDesigner.h" | |||
#include "Decimator.h" | |||
#include "GraphicEq.h" | |||
#include "LowpassFilter.h" | |||
#include "BiquadParams.h" | |||
#include "BiquadState.h" | |||
#include "BiquadFilter.h" | |||
#include "ObjectCache.h" | |||
#include <random> | |||
/** | |||
* Noise generator feeding a graphic equalizer. | |||
* Calculated at very low sample rate, then re-sampled | |||
* up to audio rate. | |||
* | |||
* Below assumes 44k SR. TODO: other rates. | |||
* | |||
* We first design the EQ around bands of 100, 200, 400, 800, | |||
* 1600. EQ gets noise. | |||
* | |||
* Then output of EQ is re-sampled up by a factor of 100 | |||
* to bring the first band down to 1hz. | |||
* or : decimation factor = 100 * (fs) / 44100. | |||
* | |||
* A butterworth lowpass then removes the re-sampling artifacts. | |||
* Otherwise these images bring in high frequencies that we | |||
* don't want. | |||
* | |||
* Cutoff for the filter can be as low as the top of the eq, | |||
* which is 3.2khz. 44k/3.2k is about 10, | |||
* so fc/fs can be 1/1000. | |||
* | |||
* or : fc = (fs / 44100) / 1000; | |||
* | |||
* (had been using fc/fs = float(1.0 / (44 * 100.0)));) | |||
* | |||
* Design for R = root freq (was 1 Hz, above) | |||
* EQ first band at E (was 100 Hz, above) | |||
* | |||
* Decimation divider = E / R | |||
* | |||
* Imaging filter fc = 3.2khz / decimation-divider | |||
* fc/fs = 3200 * (reciprocal sr) / decimation-divider. | |||
* | |||
* Experiment: let's use those values and compare to what we had been using. | |||
* result: not too far off. | |||
* | |||
* make a range/base control. map -5 to +5 into 1/10 Hz to 2 Hz rate. Can use regular | |||
* functions, since we won't calc that often. | |||
*/ | |||
template <class TBase> | |||
class LFN : public TBase | |||
{ | |||
public: | |||
LFN(struct Module * module) : TBase(module) | |||
{ | |||
} | |||
LFN() : TBase() | |||
{ | |||
} | |||
void setSampleTime(float time) | |||
{ | |||
reciprocalSampleRate = time; | |||
updateLPF(); | |||
} | |||
/** | |||
* re-calc everything that changes with sample | |||
* rate. Also everything that depends on baseFrequency. | |||
* | |||
* Only needs to be called once. | |||
*/ | |||
void init(); | |||
enum ParamIds | |||
{ | |||
EQ0_PARAM, | |||
EQ1_PARAM, | |||
EQ2_PARAM, | |||
EQ3_PARAM, | |||
EQ4_PARAM, | |||
FREQ_RANGE_PARAM, | |||
NUM_PARAMS | |||
}; | |||
enum InputIds | |||
{ | |||
EQ0_INPUT, | |||
EQ1_INPUT, | |||
EQ2_INPUT, | |||
EQ3_INPUT, | |||
EQ4_INPUT, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds | |||
{ | |||
OUTPUT, | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds | |||
{ | |||
NUM_LIGHTS | |||
}; | |||
/** | |||
* Main processing entry point. Called every sample | |||
*/ | |||
void step() override; | |||
float getBaseFrequency() const | |||
{ | |||
return baseFrequency; | |||
} | |||
/** | |||
* This lets the butterworth get re-calculated on the UI thread. | |||
* We can't do it on the audio thread, because it calls malloc. | |||
*/ | |||
void pollForChangeOnUIThread(); | |||
private: | |||
float reciprocalSampleRate = 0; | |||
::Decimator decimator; | |||
GraphicEq2<5> geq; | |||
/** | |||
* Template type for butterworth reconstruction filter | |||
* Tried double for best low frequency performance. It's | |||
* probably overkill, but calculates plenty fast. | |||
*/ | |||
using TButter = double; | |||
BiquadParams<TButter, 2> lpfParams; | |||
BiquadState<TButter, 2> lpfState; | |||
/** | |||
* Frequency, in Hz, of the lowest band in the graphic EQ | |||
*/ | |||
float baseFrequency = 1; | |||
/** | |||
* The last value baked by the LPF filter calculation | |||
* done on the UI thread. | |||
*/ | |||
float lastBaseFrequencyParamValue = -100; | |||
std::default_random_engine generator{57}; | |||
std::normal_distribution<double> distribution{-1.0, 1.0}; | |||
float noise() | |||
{ | |||
return (float) distribution(generator); | |||
} | |||
/** | |||
* Must be called after baseFrequency is updated. | |||
* re-calculates the butterworth lowpass. | |||
*/ | |||
void updateLPF(); | |||
/** | |||
* scaling function for the range / base frequency knob | |||
* map knob range from .1 Hz to 2.0 Hz | |||
*/ | |||
std::function<double(double)> rangeFunc = | |||
{AudioMath::makeFunc_Exp(-5, 5, .1, 2)}; | |||
/** | |||
* Audio taper for the EQ gains. Arbitrary max value selected | |||
* to give "good" output level. | |||
*/ | |||
AudioMath::SimpleScaleFun<float> gainScale = | |||
{AudioMath::makeSimpleScalerAudioTaper(0, 35)}; | |||
}; | |||
template <class TBase> | |||
inline void LFN<TBase>::pollForChangeOnUIThread() | |||
{ | |||
if (lastBaseFrequencyParamValue != TBase::params[FREQ_RANGE_PARAM].value) { | |||
lastBaseFrequencyParamValue = TBase::params[FREQ_RANGE_PARAM].value; | |||
baseFrequency = float(rangeFunc(lastBaseFrequencyParamValue)); | |||
updateLPF(); // now get the filters updated | |||
} | |||
} | |||
template <class TBase> | |||
inline void LFN<TBase>::init() | |||
{ | |||
updateLPF(); | |||
} | |||
template <class TBase> | |||
inline void LFN<TBase>::updateLPF() | |||
{ | |||
assert(reciprocalSampleRate > 0); | |||
// decimation must be 100hz (what our EQ is designed at) | |||
// divided by base. | |||
const float decimationDivider = float(100.0 / baseFrequency); | |||
decimator.setDecimationRate(decimationDivider); | |||
// calculate lpFc ( Fc / sr) | |||
// Imaging filter fc = 3.2khz / decimation-divider | |||
// fc/fs = 3200 * (reciprocal sr) / decimation-divider. | |||
const float lpFc = 3200 * reciprocalSampleRate / decimationDivider; | |||
ButterworthFilterDesigner<TButter>::designThreePoleLowpass( | |||
lpfParams, lpFc); | |||
} | |||
template <class TBase> | |||
inline void LFN<TBase>::step() | |||
{ | |||
// Let's only check the inputs every 4 samples. Still plenty fast, but | |||
// get the CPU usage down really far. | |||
static int count = 0; | |||
if (count++ > 4) { | |||
count = 0; | |||
const int numEqStages = geq.getNumStages(); | |||
for (int i = 0; i < numEqStages; ++i) { | |||
auto paramNum = i + EQ0_PARAM; | |||
auto cvNum = i + EQ0_INPUT; | |||
const float gainParamKnob = TBase::params[paramNum].value; | |||
const float gainParamCV = TBase::inputs[cvNum].value; | |||
const float gain = gainScale(gainParamKnob, gainParamCV); | |||
geq.setGain(i, gain); | |||
} | |||
} | |||
bool needsData; | |||
TButter x = decimator.clock(needsData); | |||
x = BiquadFilter<TButter>::run(x, lpfState, lpfParams); | |||
if (needsData) { | |||
const float z = geq.run(noise()); | |||
decimator.acceptData(z); | |||
} | |||
TBase::outputs[OUTPUT].value = (float) x; | |||
} | |||
@@ -0,0 +1,383 @@ | |||
#pragma once | |||
#include "IIRUpsampler.h" | |||
#include "IIRDecimator.h" | |||
#include "LookupTable.h" | |||
#include "AsymWaveShaper.h" | |||
#include "ObjectCache.h" | |||
/** | |||
Version 1, cpu usage: | |||
full wave: 95 | |||
crush: 281 | |||
asy:149 | |||
fold: 102 | |||
fold2: 154 | |||
X4 on input scanning: | |||
full wave: 85 | |||
crush: 278 | |||
asy:163 | |||
fold: 89 | |||
fold2: 154 | |||
inline: | |||
fw: 75 | |||
crush: 87 | |||
asy: 112 | |||
fold: 77 | |||
fold2: 136 | |||
*/ | |||
template <class TBase> | |||
class Shaper : public TBase | |||
{ | |||
public: | |||
Shaper(struct Module * module) : TBase(module) | |||
{ | |||
init(); | |||
} | |||
Shaper() : TBase() | |||
{ | |||
init(); | |||
} | |||
enum class Shapes | |||
{ | |||
AsymSpline, | |||
Clip, | |||
EmitterCoupled, | |||
FullWave, | |||
HalfWave, | |||
Fold, | |||
Fold2, | |||
Crush, | |||
Invalid | |||
}; | |||
static const char* getString(Shapes); | |||
enum ParamIds | |||
{ | |||
PARAM_SHAPE, | |||
PARAM_GAIN, | |||
PARAM_GAIN_TRIM, | |||
PARAM_OFFSET, | |||
PARAM_OFFSET_TRIM, | |||
PARAM_OVERSAMPLE, | |||
NUM_PARAMS | |||
}; | |||
enum InputIds | |||
{ | |||
INPUT_AUDIO, | |||
INPUT_GAIN, | |||
INPUT_OFFSET, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds | |||
{ | |||
OUTPUT_AUDIO, | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds | |||
{ | |||
NUM_LIGHTS | |||
}; | |||
/** | |||
* Main processing entry point. Called every sample | |||
*/ | |||
void step() override; | |||
float _gain = 0; | |||
float _offset = 0; | |||
float _gainInput = 0; | |||
private: | |||
std::shared_ptr<LookupTableParams<float>> audioTaper = {ObjectCache<float>::getAudioTaper()}; | |||
std::shared_ptr<LookupTableParams<float>> sinLookup = {ObjectCache<float>::getSinLookup()}; | |||
AudioMath::ScaleFun<float> scaleGain = AudioMath::makeLinearScaler<float>(0, 1); | |||
AudioMath::ScaleFun<float> scaleOffset = AudioMath::makeLinearScaler<float>(-5, 5); | |||
// domain starts at 2. | |||
// std::shared_ptr<LookupTableParams<float>> exp2Lookup = {ObjectCache<float>::getExp2()}; | |||
const static int maxOversample = 16; | |||
int curOversample = 16; | |||
void init(); | |||
IIRUpsampler up; | |||
IIRDecimator dec; | |||
std::shared_ptr<LookupTableParams<float>> tanhLookup; | |||
AsymWaveShaper asymShaper; | |||
int cycleCount = 0; | |||
Shapes shape = Shapes::Clip; | |||
int asymCurveindex = 0; | |||
void processCV(); | |||
void setOversample(); | |||
void processBuffer(float *) const; | |||
}; | |||
template <class TBase> | |||
const char* Shaper<TBase>::getString(Shapes shape) | |||
{ | |||
const char* ret = ""; | |||
switch (shape) { | |||
case Shapes::Clip: | |||
ret = "Clip"; | |||
break; | |||
case Shapes::EmitterCoupled: | |||
ret = "Emitter Coupled"; | |||
break; | |||
case Shapes::FullWave: | |||
ret = "Full Wave"; | |||
break; | |||
case Shapes::HalfWave: | |||
ret = "Half Wave"; | |||
break; | |||
case Shapes::Fold: | |||
ret = "Folder"; | |||
break; | |||
case Shapes::Fold2: | |||
ret = "Folder II"; | |||
break; | |||
case Shapes::AsymSpline: | |||
ret = "Smooth"; | |||
break; | |||
case Shapes::Crush: | |||
ret = "Crush"; | |||
break; | |||
default: | |||
assert(false); | |||
ret = "error"; | |||
} | |||
return ret; | |||
} | |||
template <class TBase> | |||
void Shaper<TBase>::init() | |||
{ | |||
setOversample(); | |||
tanhLookup = ObjectCache<float>::getTanh5(); | |||
} | |||
template <class TBase> | |||
void Shaper<TBase>::setOversample() | |||
{ | |||
// float fc = .25 / float(oversample); | |||
up.setup(curOversample); | |||
dec.setup(curOversample); | |||
} | |||
template <class TBase> | |||
void Shaper<TBase>::processCV() | |||
{ | |||
int oversampleCode = (int) std::round(TBase::params[PARAM_OVERSAMPLE].value); | |||
switch (oversampleCode) { | |||
case 0: | |||
curOversample = 16; | |||
setOversample(); | |||
break; | |||
case 1: | |||
curOversample = 4; | |||
setOversample(); | |||
break; | |||
case 2: | |||
curOversample = 1; | |||
break; | |||
default: | |||
assert(false); | |||
} | |||
// 0..1 | |||
_gainInput = scaleGain( | |||
TBase::inputs[INPUT_GAIN].value, | |||
TBase::params[PARAM_GAIN].value, | |||
TBase::params[PARAM_GAIN_TRIM].value); | |||
_gain = 5 * LookupTable<float>::lookup(*audioTaper, _gainInput, false); | |||
// -5 .. 5 | |||
const float offsetInput = scaleOffset( | |||
TBase::inputs[INPUT_OFFSET].value, | |||
TBase::params[PARAM_OFFSET].value, | |||
TBase::params[PARAM_OFFSET_TRIM].value); | |||
_offset = offsetInput; | |||
const int iShape = (int) std::round(TBase::params[PARAM_SHAPE].value); | |||
shape = Shapes(iShape); | |||
const float sym = .1f * (5 - _offset); | |||
asymCurveindex = (int) round(sym * 15.1); // This math belongs in the shaper | |||
} | |||
template <class TBase> | |||
void Shaper<TBase>::step() | |||
{ | |||
if (--cycleCount < 0) { | |||
cycleCount = 7; | |||
processCV(); | |||
} | |||
float buffer[maxOversample]; | |||
float input = TBase::inputs[INPUT_AUDIO].value; | |||
// const float rawInput = input; | |||
// TODO: maybe add offset after gain? | |||
if (shape != Shapes::AsymSpline) { | |||
input += _offset; | |||
} | |||
if (shape != Shapes::Crush) { | |||
input *= _gain; | |||
} | |||
if (curOversample != 1) { | |||
up.process(buffer, input); | |||
} else { | |||
buffer[0] = input; | |||
} | |||
processBuffer(buffer); | |||
float output; | |||
if (curOversample != 1) { | |||
output = dec.process(buffer); | |||
} else { | |||
output = buffer[0]; | |||
} | |||
TBase::outputs[OUTPUT_AUDIO].value = output; | |||
// printf("in step input = %f, output = %f\n", input, output); | |||
} | |||
#if 1 | |||
template <class TBase> | |||
void Shaper<TBase>::processBuffer(float* buffer) const | |||
{ | |||
switch (shape) { | |||
case Shapes::FullWave: | |||
for (int i = 0; i < curOversample; ++i) { | |||
float x = buffer[i]; | |||
x = std::abs(x); | |||
x = std::min(x, 10.f); | |||
buffer[i] = x; | |||
} | |||
break; | |||
case Shapes::AsymSpline: | |||
for (int i = 0; i < curOversample; ++i) { | |||
float x = buffer[i]; | |||
x *= .15f; | |||
x = asymShaper.lookup(x, asymCurveindex); | |||
x *= 6.1f; | |||
buffer[i] = x; | |||
} | |||
break; | |||
case Shapes::Clip: | |||
for (int i = 0; i < curOversample; ++i) { | |||
float x = buffer[i]; | |||
x *= 3; | |||
x = std::min(3.f, x); | |||
x = std::max(-3.f, x); | |||
x *= 1.2f; | |||
buffer[i] = x; | |||
} | |||
break; | |||
case Shapes::EmitterCoupled: | |||
for (int i = 0; i < curOversample; ++i) { | |||
float x = buffer[i]; | |||
x *= .25; | |||
x = LookupTable<float>::lookup(*tanhLookup.get(), x, true); | |||
x *= 5.4f; | |||
buffer[i] = x; | |||
} | |||
break; | |||
case Shapes::HalfWave: | |||
for (int i = 0; i < curOversample; ++i) { | |||
float x = buffer[i]; | |||
x = std::max(0.f, x); | |||
x *= 1.4f; | |||
x = std::min(x, 10.f); | |||
buffer[i] = x; | |||
} | |||
break; | |||
case Shapes::Fold: | |||
for (int i = 0; i < curOversample; ++i) { | |||
float x = buffer[i]; | |||
x = AudioMath::fold(x); | |||
x *= 5.6f; | |||
buffer[i] = x; | |||
} | |||
break; | |||
case Shapes::Fold2: | |||
for (int i = 0; i < curOversample; ++i) { | |||
float x = buffer[i]; | |||
x = .3f * AudioMath::fold(x); | |||
if (x > 0) { | |||
x = LookupTable<float>::lookup(*sinLookup, 1.3f * x, false); | |||
} else { | |||
x = -LookupTable<float>::lookup(*sinLookup, -x, false); | |||
} | |||
if (x > 0) x = std::sqrt(x); | |||
x *= 4.4f; | |||
buffer[i] = x; | |||
} | |||
break; | |||
case Shapes::Crush: | |||
{ | |||
float invGain = 1 + (1 - _gainInput) * 100; //0..10 | |||
invGain *= .01f; | |||
invGain = std::max(invGain, .09f); | |||
assert(invGain >= .09); | |||
for (int i = 0; i < curOversample; ++i) { | |||
float x = buffer[i]; // for crush, no gain has been applied | |||
#if 0 | |||
if (invGain < 1) { | |||
printf("invg gain = %f\n", invGain); | |||
fflush(stdout); | |||
invGain = 1; | |||
} | |||
#endif | |||
// printf("crush, x=%.2f, gi=%.2f invGain = %.2f", x, _gainInput, invGain); | |||
x *= invGain; | |||
x = std::round(x + .5f) - .5f; | |||
// printf("invGain = %f, x = %f\n", invGain, x); | |||
// printf(" mult=%.2f", x); | |||
x /= invGain; | |||
// printf("after div back %f\n", x); fflush(stdout); | |||
// printf(" dv=%.2f\n", x); fflush(stdout); | |||
buffer[i] = x; | |||
} | |||
} | |||
break; | |||
default: | |||
assert(false); | |||
} | |||
} | |||
#else | |||
template <class TBase> | |||
void Shaper<TBase>::step() | |||
{ | |||
float buffer[oversample]; | |||
float input = TBase::inputs[INPUT_AUDIO].value; | |||
up.process(buffer, input); | |||
const float output = dec.process(buffer); | |||
TBase::outputs[OUTPUT_AUDIO].value = output; | |||
} | |||
#endif |
@@ -0,0 +1,180 @@ | |||
#pragma once | |||
#include "ButterworthLookup.h" | |||
#include "BiquadState.h" | |||
#include "BiquadFilter.h" | |||
#include "ObjectCache.h" | |||
template <class TBase> | |||
class Super : public TBase | |||
{ | |||
public: | |||
Super(struct Module * module) : TBase(module) | |||
{ | |||
} | |||
Super() : TBase() | |||
{ | |||
} | |||
/** | |||
* re-calc everything that changes with sample | |||
* rate. Also everything that depends on baseFrequency. | |||
* | |||
* Only needs to be called once. | |||
*/ | |||
void init(); | |||
enum ParamIds | |||
{ | |||
OCTAVE_PARAM, | |||
SEMI_PARAM, | |||
FINE_PARAM, | |||
DETUNE_PARAM, | |||
MIX_PARAM, | |||
NUM_PARAMS | |||
}; | |||
enum InputIds | |||
{ | |||
CV_INPUT, | |||
GATE_INPUT, | |||
DEBUG_INPUT, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds | |||
{ | |||
MAIN_OUTPUT, | |||
DEBUG_OUTPUT, | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds | |||
{ | |||
NUM_LIGHTS | |||
}; | |||
/** | |||
* Main processing entry point. Called every sample | |||
*/ | |||
void step() override; | |||
private: | |||
static const int numSaws = 7; | |||
float phase[numSaws] = {0}; | |||
float phaseInc[numSaws] = {0}; | |||
float globalPhaseInc = 0; | |||
std::function<float(float)> expLookup = | |||
ObjectCache<float>::getExp2Ex(); | |||
std::shared_ptr<LookupTableParams<float>> audioTaper = | |||
ObjectCache<float>::getAudioTaper(); | |||
void updatePhaseInc(); | |||
void updateAudio(); | |||
// TODO: make static | |||
float const detuneFactors[numSaws] = { | |||
.89f, | |||
.94f, | |||
.98f, | |||
1.f, | |||
1.02f, | |||
1.06f, | |||
1.107f | |||
}; | |||
// For debugging filters | |||
BiquadState<float, 2> filterState; | |||
BiquadParams<float, 2> filterParams; | |||
void updateHPFilters(); | |||
ButterworthLookup4PHP filterLookup; | |||
}; | |||
template <class TBase> | |||
inline void Super<TBase>::init() | |||
{ | |||
} | |||
template <class TBase> | |||
inline void Super<TBase>::updatePhaseInc() | |||
{ | |||
const float cv = TBase::inputs[CV_INPUT].value; | |||
const float finePitch = TBase::params[FINE_PARAM].value / 12.0f; | |||
const float semiPitch = TBase::params[SEMI_PARAM].value / 12.0f; | |||
float pitch = 1.0f + roundf(TBase::params[OCTAVE_PARAM].value) + | |||
semiPitch + | |||
finePitch; | |||
pitch += cv; | |||
const float q = float(log2(261.626)); // move up to pitch range of even vco | |||
pitch += q; | |||
const float freq = expLookup(pitch); | |||
globalPhaseInc = TBase::engineGetSampleTime() * freq; | |||
for (int i=0; i<numSaws; ++i) { | |||
float detune = (detuneFactors[i] - 1) * .1f; | |||
detune += 1; | |||
phaseInc[i] = globalPhaseInc * detune; | |||
// printf("phaseINc[%d] = %f\n", i, phaseInc[i]); fflush(stdout); | |||
} | |||
} | |||
template <class TBase> | |||
inline void Super<TBase>::updateAudio() | |||
{ | |||
float mix = 0; | |||
for (int i=0; i<numSaws; ++i) { | |||
phase[i] += phaseInc[i]; | |||
if (phase[i] > 1) { | |||
phase[i] -= 1; | |||
} | |||
if (phase[i] > 1) { | |||
printf("hey, phase too big %f\n", phase[i]); fflush(stdout); | |||
} | |||
if (phase[i] < 0) { | |||
printf("hey, phase too bismallg %f\n", phase[i]); fflush(stdout); | |||
} | |||
mix += phase[i]; | |||
} | |||
// mix = phase[3]; // just for test | |||
mix *= 2; | |||
const float output = BiquadFilter<float>::run(mix, filterState, filterParams); | |||
TBase::outputs[MAIN_OUTPUT].value = output; | |||
} | |||
template <class TBase> | |||
inline void Super<TBase>::updateHPFilters() | |||
{ | |||
filterLookup.get(filterParams, globalPhaseInc); | |||
#if 0 | |||
const float input = TBase::inputs[DEBUG_INPUT].value; | |||
filterLookup.get(filterParams, globalPhaseInc); | |||
const float output = BiquadFilter<float>::run(input, filterState, filterParams); | |||
TBase::outputs[DEBUG_OUTPUT].value = output * 10; | |||
#endif | |||
} | |||
template <class TBase> | |||
inline void Super<TBase>::step() | |||
{ | |||
updatePhaseInc(); | |||
updateHPFilters(); | |||
updateAudio(); | |||
} | |||
@@ -13,7 +13,7 @@ public: | |||
TestComposite() : | |||
inputs(20), | |||
outputs(20), | |||
params(20), | |||
params(40), | |||
lights(20) | |||
{ | |||
@@ -32,7 +32,9 @@ public: | |||
{ | |||
value = (brightness > 0.f) ? brightness * brightness : 0.f; | |||
} | |||
void setBrightnessSmooth(float brightness); | |||
void setBrightnessSmooth(float brightness) | |||
{ | |||
} | |||
}; | |||
struct Input | |||
@@ -54,7 +56,7 @@ public: | |||
/** Voltage of the port. Write-only by Module */ | |||
float value = 0.0; | |||
/** Whether a wire is plugged in */ | |||
bool active = false; | |||
bool active = true; | |||
Light plugLights[2]; | |||
}; | |||
@@ -62,4 +64,18 @@ public: | |||
std::vector<Output> outputs; | |||
std::vector<Param> params; | |||
std::vector<Light> lights; | |||
float engineGetSampleTime() | |||
{ | |||
return 1.0f / 44100.0f; | |||
} | |||
float engineGetSampleRate() | |||
{ | |||
return 44100.f; | |||
} | |||
virtual void step() | |||
{ | |||
} | |||
}; |
@@ -14,10 +14,10 @@ template <class TBase> | |||
class Tremolo : public TBase | |||
{ | |||
public: | |||
Tremolo(struct Module * module) : TBase(module) | |||
Tremolo(struct Module * module) : TBase(module), gateTrigger(true) | |||
{ | |||
} | |||
Tremolo() : TBase() | |||
Tremolo() : TBase(), gateTrigger(true) | |||
{ | |||
} | |||
void setSampleRate(float rate) | |||
@@ -71,7 +71,7 @@ public: | |||
/** | |||
* Main processing entry point. Called every sample | |||
*/ | |||
void step(); | |||
void step() override; | |||
private: | |||
@@ -80,7 +80,7 @@ private: | |||
float reciprocalSampleRate = 0; | |||
AsymRampShaperParams rampShaper; | |||
std::shared_ptr<LookupTableParams<float>> exp2 = ObjectCache<float>::getExp2(); | |||
std::shared_ptr<LookupTableParams<float>> exp2 = ObjectCache<float>::getExp2(); | |||
// make some bootstrap scalers | |||
AudioMath::ScaleFun<float> scale_rate; | |||
@@ -126,7 +126,7 @@ inline void Tremolo<TBase>::step() | |||
clock.setMultiplier(clockMul); | |||
const float shape = scale_shape( | |||
TBase::inputs[LFO_SHAPE_INPUT].value, | |||
@@ -160,7 +160,7 @@ inline void Tremolo<TBase>::step() | |||
clock.setFreeRunFreq(scaledRate * reciprocalSampleRate); | |||
} | |||
// For now, call setup every sample. will eat a lot of cpu | |||
AsymRampShaper::setup(rampShaper, skew, phase); | |||
@@ -91,7 +91,7 @@ public: | |||
}; | |||
void init(); | |||
void step(); | |||
void step() override; | |||
T modulatorOutput[numModOutputs]; | |||
// The frequency inputs to the filters, exposed for testing. | |||
@@ -144,10 +144,10 @@ inline void VocalAnimator<TBase>::init() | |||
normalizedFilterFreq[i] = nominalFilterCenterHz[i] * reciprocalSampleRate; | |||
} | |||
scale0_1 = AudioMath::makeBipolarAudioScaler(0, 1); // full CV range -> 0..1 | |||
scalem2_2 = AudioMath::makeBipolarAudioScaler(-2, 2); // full CV range -> -2..2 | |||
scaleQ = AudioMath::makeBipolarAudioScaler(.71f, 21); | |||
scalen5_5 = AudioMath::makeBipolarAudioScaler(-5, 5); | |||
scale0_1 = AudioMath::makeScalerWithBipolarAudioTrim(0, 1); // full CV range -> 0..1 | |||
scalem2_2 = AudioMath::makeScalerWithBipolarAudioTrim(-2, 2); // full CV range -> -2..2 | |||
scaleQ = AudioMath::makeScalerWithBipolarAudioTrim(.71f, 21); | |||
scalen5_5 = AudioMath::makeScalerWithBipolarAudioTrim(-5, 5); | |||
// make table of 2 ** x | |||
expLookup = ObjectCache<T>::getExp2(); | |||
@@ -72,7 +72,7 @@ public: | |||
}; | |||
void init(); | |||
void step(); | |||
void step() override; | |||
float reciprocalSampleRate; | |||
@@ -156,7 +156,7 @@ inline void VocalFilter<TBase>::step() | |||
for (int i = LED_A; i <= LED_U; ++i) { | |||
if (i == iVowel) { | |||
TBase::lights[i].value = ((i + 1) - fVowel) * 1; | |||
TBase::lights[i+1].value = (fVowel - i) * 1; | |||
TBase::lights[i + 1].value = (fVowel - i) * 1; | |||
} else if (i != (iVowel + 1)) { | |||
TBase::lights[i].value = 0; | |||
} | |||
@@ -199,7 +199,7 @@ inline void VocalFilter<TBase>::step() | |||
T modifiedGainDB = (1 - gainDB) * brightness + gainDB; | |||
// TODO: why is normalizedBW in this equation? | |||
const T gain =LookupTable<T>::lookup(*db2GainLookup, modifiedGainDB) * normalizedBw; | |||
const T gain = LookupTable<T>::lookup(*db2GainLookup, modifiedGainDB) * normalizedBw; | |||
T fcFinalLog = fcLog + fPara; | |||
T fcFinal = LookupTable<T>::lookup(*expLookup, fcFinalLog); | |||
@@ -11,12 +11,26 @@ public: | |||
inputs(parent->inputs), | |||
outputs(parent->outputs), | |||
params(parent->params), | |||
lights(parent->lights) | |||
lights(parent->lights), | |||
module(parent) | |||
{ | |||
} | |||
virtual void step() | |||
{ | |||
}; | |||
float engineGetSampleRate() | |||
{ | |||
return ::engineGetSampleRate(); | |||
} | |||
float engineGetSampleTime() | |||
{ | |||
return ::engineGetSampleTime(); | |||
} | |||
protected: | |||
std::vector<Input>& inputs; | |||
std::vector<Output>& outputs; | |||
std::vector<Param>& params; | |||
std::vector<Light>& lights; | |||
private: | |||
Module * const module; | |||
}; |
@@ -0,0 +1,111 @@ | |||
#pragma once | |||
#include "AudioMath.h" | |||
#include "FractionalDelay.h" | |||
#include "ObjectCache.h" | |||
template <class TBase> | |||
class Daveguide : public TBase | |||
{ | |||
public: | |||
Daveguide(struct Module * module) : TBase(module), delay(44100) | |||
{ | |||
// init(); | |||
} | |||
Daveguide() : TBase(), delay(44100) | |||
{ | |||
// init(); | |||
} | |||
enum ParamIds | |||
{ | |||
OCTAVE_PARAM, | |||
TUNE_PARAM, | |||
DECAY_PARAM, | |||
FC_PARAM, | |||
NUM_PARAMS | |||
}; | |||
enum InputIds | |||
{ | |||
AUDIO_INPUT, | |||
CV_INPUT, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds | |||
{ | |||
AUDIO_OUTPUT, | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds | |||
{ | |||
NUM_LIGHTS | |||
}; | |||
/** | |||
* Main processing entry point. Called every sample | |||
*/ | |||
void step() override; | |||
float _freq = 0; | |||
private: | |||
RecirculatingFractionalDelay delay; | |||
//static std::function<double(double)> makeFunc_Exp(double xMin, double xMax, double yMin, double yMax); | |||
// std::function<double(double)> delayScale = AudioMath::makeFunc_Exp(-5, 5, 1, 500); | |||
// AudioMath::ScaleFun<float> feedbackScale = AudioMath::makeLinearScaler(0.f, 1.f); | |||
std::function<float(float)> expLookup = ObjectCache<float>::getExp2Ex(); | |||
}; | |||
template <class TBase> | |||
void Daveguide<TBase>::step() | |||
{ | |||
#if 0 | |||
// make delay knob to from 1 ms. to 1000 | |||
double delayMS = delayScale(TBase::params[PARAM_DELAY].value); | |||
double feedback = feedbackScale(0, (TBase::params[PARAM_FEEDBACK].value), 1); | |||
double delaySeconds = delayMS * .001; | |||
double delaySamples = delaySeconds * TBase::engineGetSampleRate(); | |||
delay.setDelay((float) delaySamples); | |||
delay.setFeedback((float) feedback); | |||
const float input = TBase::inputs[INPUT_AUDIO].value; | |||
const float output = delay.run(input); | |||
TBase::outputs[OUTPUT_AUDIO].value = output; | |||
#endif | |||
float pitch = 1.0f + roundf(TBase::params[OCTAVE_PARAM].value) + TBase::params[TUNE_PARAM].value / 12.0f; | |||
pitch += TBase::inputs[CV_INPUT].value; | |||
//pitch += .25f * TBase::inputs[PITCH_MOD_INPUT].value * | |||
// taper(TBase::params[PARAM_PITCH_MOD_TRIM].value); | |||
const float q = float(log2(261.626)); // move up to pitch range of even vco | |||
pitch += q; | |||
_freq = expLookup(pitch); | |||
const float delaySeconds = 1.0f / _freq; | |||
float delaySamples = delaySeconds * TBase::engineGetSampleRate(); | |||
delay.setDelay(delaySamples); | |||
delay.setFeedback(.999f); | |||
// printf("set delay to %f samples (%f sec)\n", delaySamples, delaySeconds); | |||
// fflush(stdout); | |||
const float input = TBase::inputs[AUDIO_INPUT].value; | |||
const float output = delay.run(input); | |||
TBase::outputs[AUDIO_OUTPUT].value = output; | |||
// clock.setMultiplier(1); // no mult | |||
} |
@@ -2,6 +2,8 @@ | |||
All of our plugins are free and open source. The [instruction manual](booty-shifter.md) describes all of the released modules. | |||
The [release notes](release-notes.md) describe recent changes. | |||
All of our released modules may be found in the [VCV Rack plugin manager] (https://vcvrack.com/plugins.html). This is by far the easiest way for most users to install our modules and keep them up to date. | |||
It is also quite easy to clone this repo and build them yourself. In order to do this, however, you must first download and build [VCV Rack itself](https://github.com/VCVRack/Rack). | |||
@@ -22,10 +24,6 @@ As with all third-party modules for VCV, you must: | |||
* `CD SquinkyVCV` | |||
* `make` | |||
## Experimental modules | |||
At any given time, there may partially finished "experimental" modules in this repo. You can find up to date information on them [here](experimental.md). | |||
## Unit testing framework | |||
We have reasonably thorough tests for our code. Some of this might be of interest - it's [here](unit-test.md). |
@@ -0,0 +1,39 @@ | |||
# Some information about aliasing | |||
We’ve been thinking about aliasing a lot lately. It has been a major issue in many of our recent projects. With Fundamental VCO-1 we wanted to be sure that after we re-designed the anti-alias filters to use less CPU that the aliasing was still good. So we spent a lot of time writing unit tests to compare the two. | |||
With EV3, we wanted to add sync to the VCO without adding aliasing. It took us way longer than it should have to figure out exactly how to do that. Then with Shaper it was clear that it would generate a lot of aliasing if we didn’t oversample it. So we borrowed the oversampler from Fundamental VCO-1. | |||
Aliasing in digital audio happens when an algorithm attempts to create frequencies that are impossible to represent. You probably know that the highest frequency that can be contained in a digital signal is one-half the sampling rate. So at the standard 44,100 sample rate, the highest frequency that can be encoded is 22,050 Hz. | |||
If a DSP algorithm, like a VCV module, attempts to generate frequencies higher than than this, they will appear at a frequency below 22,050. In fact they will “fold back” - increasing frequencies alias and manifest themselves as decreasing frequencies below fs/2. | |||
Most people find that aliasing sounds terrible. Unlike harmonic distortion, which occurs at harmonic multiples of the pitch, the alias frequencies are not related to the non-aliased frequencies in a harmonious way. As an extreme example: if you play a high-pitched note and bend it up, the alias frequencies will bend in the opposite direction, so you hear you main signal bending up in pitch, with some “ghost tones” bending down in pitch. | |||
For more information on aliasing, especially how it sounds, there is a wealth of information on the internet. Spoiler alert: most people think if sounds bad and most commercial digital audio equipment goes to some lengths to suppress it. | |||
Now if you are new to this, and paying attention, you might be perplexed by the statement above that aliasing happens when a DSP algorithm attempts to generate sounds above 22,050. Why would any algorithm try to do this? Well, a simple example is a square wave. An analog square wave has an infinite number of harmonics going up to infinite frequencies (look it up if you don’t believe us). So a naive attempt to make a square wave VCO is going to generate a ton of aliasing. Similarly, a distortion or waveshaper module that does hard clipping will tend to create square waves and a lot of aliasing. | |||
 | |||
Luckily for VCV Rack users it is easy to see if a particular module is aliasing. Pick a spectrum analyzer module. Our favorite is the Bogaudio one. To examine a VCO, just patch the VCO output to the analyzer’s input. To examine a waveshaper, patch a sine wave from a VCO into the input of the shaper, then patch the output of the shaper to the spectrum analyzer. | |||
Aliasing is far more severe at high frequencies, so set the VCO for somewhere between 1 kHz and 2kHz. At lower frequencies it might not be visible (or audible). If you set it at an exact division of the sampling rate the aliasing will blend in with the harmonics and you won’t see it. 1k and 2k are very close to exact divisions of 44,100, so pick something in between. Then adjust the VCO frequency up and down until you see the worst aliasing. You may see new frequency lines appear in between real harmonics. Or you may see that as you raise the pitch some of the lines go down - those are the alias frequencies folding over. | |||
This excellent video by the author of the Vult modules shows how to do it: [Can you hear the alias?](https://www.youtube.com/watch?v=LSIO5R0fuoU&feature=youtu.be&fbclid=IwAR1_wsaqiuLiYNO7Tn0c5smP3mLnwxgYJmD8MXcn7eNuhlb6To_XwzR3Kzc) | |||
As an example we will show the effect of changing the oversample rate in our “Shaper” module using the “Clip” setting. The gain is up pretty high. | |||
 | |||
This image shows the output of Shaper on the spectrum analyzer at 1X (left), 4X, and 16X (on the right). You can easily see the alising in the 1X image. At this setting there is no visible difference between 4X and 16X. No doubt there would be if the analyzer were more sensitive, but it’s worth noting here that at 1X the alias components are getting pretty high compared to the harmonics in the 10k region. But down in the 1-2k area the alias is below the fundamental by a bit more than 50 db. Also notice that there are alias components below the fundamental frequency. | |||
This next image is the same test, but with the “Folder” setting. Again, the gain is pretty high, and there is some offset to bring in even harmonics. | |||
 | |||
Not surprisingly, this highly folded sine wave shows a **lot** of aliasing at 1X. Notice the line at 2 kHz - that’s an alias component that is louder than the fundamental. And now you can see many more alias components below the fundamental. | |||
Moving right to the 4X image, clearly most of the aliasing is gone. But there are still alias components adjacent to the harmonics, and they are only perhaps 25 db down from the good signal. Then in the 16X image things are as they should be. The lines are all at even multiples of 1.5 k, so they are harmonics, not aliasing. Note that the level of the harmonics is not falling off at all with increasing frequency. That is why this shape brings out so much aliasing. | |||
We hope you have learned how easy it is to evaluate the aliasing of a VCV module. Next time you are experimenting with a new module, why not take a look at the aliasing? Make your own opinion about aliasing. Your are fortunate to have the tools already. |
@@ -0,0 +1,21 @@ | |||
# Thread Booster<a name="booster"></a> | |||
 | |||
Thread booster raises the priority of VCV Rack's audio rendering thread. In many cases this decreases the annoying pops, ticks, and dropouts that many users are experiencing. | |||
Many users have reported that Thread Booster helps significantly. Others have reported that it does not help at all. No one has reported a detrimental effect. | |||
For a deeper dive into the Thread Booster, you should read [this document](./thread-booster.md). | |||
Thread Booster has a UI that lets you boost the priority of the audio thread. There are three arbitrary settings: normal, boosted, and real time. When the switch is in the bottom position, the plugin does nothing; the audio thread keeps its default priority. In the boost (middle) position, it sets the thread priority to the highest priority non-real-time setting. In the real-time position it attempts to set it to the highest possible priority, or near it. | |||
If setting the priority fails, the red error light lights up, and the priority stays where it was last. | |||
To use Thread Booster, just insert an instance into VCV Rack, then adjust the boost switch. In general we recommend the "real time" setting, if it is available on your computer. | |||
Once Thread booster is in your session, it will boost all the audio processing - it doesn't matter if other modules are added before or after - they all get boosted. | |||
Linux users - you must read [the detailed document](./thread-booster.md) to use this module. | |||
Note to users who downloaded the original version of Thread Booster: we've improved it a bit since then, especially on Linux and Windows. |
@@ -0,0 +1,249 @@ | |||
# Table of contents | |||
[Chebyshev Waveshaper VCO](../docs/chebyshev.md) Click on link to go to Chbeyshev manual. | |||
[Functional VCO-1](#fun) Is an improved version of the Fundamental-VCO1. | |||
[LFN](#lfn) Is a random voltage generator made by running low frequency noise through a graphic equalizer. | |||
[Chopper](#chopper) Is a tremolo powered by a clock-synchable LFO. The LFO is highly programmable to give a range of waveforms. | |||
[Thread Booster](#booster) reduces pops and clicks in VCV Rack by reprogramming VCV's audio engine. | |||
[Colors](#colors) is a colored noise generator. It can generate all the common **"colors"** of noise, including white, pink, red, blue, and violet. | |||
[Growler](#growler) is a "vocal animator." It imparts random vocal timbres on anything played through it. The pseudo-random LFOs all have discrete outputs. | |||
[Booty Shifter](#shifter) is an emulation of the legendary Moog/Bode frequency shifter. | |||
[Formants](#formants) is a programmable bank of filters that can synthesize various vowel sounds and morph between them. | |||
[Attenuverters](#atten) | |||
[CV ranges](#cv) | |||
The [release notes](release-notes.md) describe recent changes to our modules. | |||
# Functional VCO-1 <a name="fun"></a> | |||
 | |||
Functional VCO-1 works just like its namesake. The control layout is familiar, the sound is the same, but it uses about 1/4 as much CPU as the original. | |||
We believe VCV's Fundamental VCO is an unsung hero. It's one of the few VCOs that never has audible aliasing artifacts. You can sync it, and modulate all its inputs, but the sound never falls apart. | |||
We "forked" the code to Fundamental VCO-1 and modified it a little bit to make it much more CPU efficient. Now you may use a lot more of them without pops, clicks, and dropouts. | |||
If you would like the details of how we did this, you can [find them here](../docs/vco-optimization.md). | |||
# LFN Low Frequency Noise Generator <a name="lfn"></a> | |||
 | |||
LFN stands for Low Frequency Noise. Technically it is a white noise generator run through a graphic equalizer at extremely low frequencies. People may find it easier to think of it as a random voltage source with unique control over the output. | |||
The top knob, which is unlabeled, sets the "base frequency" of LFN. | |||
The five other knobs, and the CV inputs beside them, control the gain of the graphic equalizers sections. Beside each EQ gain knob is a label indicating what frequency range that knob controls. | |||
For example, it the base frequency is 1.0, the EQ sections will be at 1Hz, 2Hz, 4Hz, 8Hz, and 16Hz. If the base frequency is 0.2, The EQ sections will be at 0.2Hz, 0.4Hz, 0.8Hz, 1.6Hz, and 3.2Hz. | |||
But instead of thinking about frequencies like 1Hz, which are a little difficult to imagine, think of the knobs as mixing very slow random voltages, with less slow ones. For example if LFN is driving a pitch quantizer into a VCO, turn all the knobs down to zero except the first, lowest, one. This will make a series of pitches slowly rising and falling. Then bring in a little of the faster channels. The pitch will still be slowly rising and falling, but will also quickly move up and down by smaller steps. | |||
A good way to learn what makes LFN tick is to set it slow and watch it on the scope. At the same time run it straight into a VCO. Experiment with different mixes of the slow knobs and the fast ones. | |||
As you would expect from Squinky Labs, the CPU usage of LFN is very low. In fact it is one of our leanest modules yet. So feel free to use as many instances as you like. | |||
# Chopper tremolo / programmable LFO <a name="chopper"></a> | |||
 | |||
In its simplest use, Chopper produces a very wide range of **tremolo** effects. The built-in LFO can produce a wide range of waveforms that cover many of the waveforms produced by the tremolo circuits built into **vintage guitar amplifiers**. | |||
The LFO is sent to an output so that it may modulate other modules. | |||
There is also a **clock synchronizer** and multiplier. | |||
To use Chopper as a tremolo, send a signal to the *in* jack, and listen to the *out* jack. Leave the *clock* control at the default *int* setting. Most of the knob settings will now affect the tremolo effect. | |||
## Chopper LFO | |||
 | |||
To understand all the LFO settings, it helps to watch the outputs on a scope. | |||
The LFO starts as **skewed** sawtooth. In the middle position it is a symmetric triangle wave, at one end a positive sawtooth and at the other a negative sawtooth. The signal is sent to the **saw** output. | |||
The skewed saw then goes to a **waveshaper**. As the shape control is increased the LFO is gradually rounded and then flattened. The shaped LFO is send to the *lfo* output, and used internally to modulate the audio input. | |||
LFO Controls: | |||
* **Shape** Flattens the LFO waveform. | |||
* **Skew** Dials in the amount of asymmetry in the LFO. | |||
* **Depth** Shifts and scales the LFO. | |||
When used as a tremolo effect, you will hear **more tremolo** when these controls are turned up. | |||
## Chopper clock | |||
The LFO in Chopper may be synchronized with the ckin signal. There is a built-in **clock multiplier**. To use the synchronization, patch a clock to the ckin, and select x1 from the **clock** knob. To run at a multiple of the input clock, select x2, x3, or x4. | |||
When Chopper is being synched, the **Phase** control sets the phase difference between the external clock and the synchronized LFO. This may be used to "dial in" the tremolo so that it sounds exactly on the beat (or off the beat). | |||
There is also an internal LFO that is controlled by the **Rate** control. Set the clock control to *int* to use the internal clock. | |||
# Thread Booster<a name="booster"></a> | |||
 | |||
Thread booster raises the priority of VCV Rack's audio rendering thread. In many cases this decreases the annoying pops, ticks, and dropouts that many users are experiencing. | |||
Many users have reported that Thread Booster helps significantly. Others have reported that it does not help at all. No one has reported a detrimental effect. | |||
For a deeper dive into the Thread Booster, you should read [this document](./thread-booster.md). | |||
Thread Booster has a UI that lets you boost the priority of the audio thread. There are three arbitrary settings: normal, boosted, and real time. When the switch is in the bottom position, the plugin does nothing; the audio thread keeps its default priority. In the boost (middle) position, it sets the thread priority to the highest priority non-real-time setting. In the real-time position it attempts to set it to the highest possible priority, or near it. | |||
If setting the priority fails, the red error light lights up, and the priority stays where it was last. | |||
To use Thread Booster, just insert an instance into VCV Rack, then adjust the boost switch. In general we recommend the "real time" setting, if it is available on your computer. | |||
Once Thread booster is in your session, it will boost all the audio processing - it doesn't matter if other modules are added before or after - they all get boosted. | |||
Linux users - you must read [the detailed document](./thread-booster.md) to use this module. | |||
Note to users who downloaded the original version of Thread Booster: we've improved it a bit since then, especially on Linux and Windows. | |||
# Colors variable slope noise generator<a name="colors"></a> | |||
 | |||
Colors is a colored noise generator. It can generate all the common **"colors"** of noise, including white, pink, red, blue, and violet. It can also produce all the colors in between, as it has a **continuously variable slope**. | |||
Colors has a single control, "slope." This is the slope of the noise spectrum, from -8 dB/octave to +8 dB/octave. | |||
The slope of the noise is quite accurate in the mid-band, but at the extremes we flatten the slope to keep from boosting super-low frequencies too much, and to avoid putting out enormous amounts of highs. So the slope is flat below 40hz, and above 6kHz. | |||
## Things to be aware of | |||
When the **slope** changes, Color needs to do a lot of calculations. While this is normally not a problem, it’s possible that quickly changing the slope of many instances of Colors could cause pops and dropouts. | |||
The slope control does not respond instantly. If you turn the knob, you will hear the change, but if you were to modulate the CV very quickly you might notice the slowness. | |||
# Growler | |||
 | |||
**Growler** is a re-creation of the Vocal Animator circuit invented by Bernie Hutchins, and published in Electronotes magazine in the late 70's. It continuously morphs between different vaguely voice like tones. | |||
**To get a good sound:** run any harmonically rich signal into the input, and something good will come out. Low frequency pulse waves and distorted sounds make great input. | |||
The controls do pretty much what you would expect: | |||
* **LFO** controls the speed of the modulation LFOs. | |||
* **Fc** controls the average frequency of the multiple filters. | |||
* **Q** controls the sharpness of the filters. | |||
* **Depth** controls how much of the modulation LFOs are applied to the filters. | |||
## How Growler works | |||
 | |||
There are four **bandpass filters**, roughly tuned to some typical vocal formant frequencies: 522, 1340, 2570, and 3700 Hz. The filters are run in parallel, with their outputs summed together. | |||
The first three filter frequencies are modulated by an LFO comprised of **4 triangle wave LFOs** running at different frequencies. They are summed together in various combinations to drive each of the filters. | |||
Each **CV input stage** is the same: a knob that supplies a fixed offset and a CV input that is processed by an attenuverter. The processed CV is added to the knob voltage. See below for more on [Attenuverters](#atten) and [CV ranges](#cv). | |||
The **LFO** Rate control shifts the speed of all 4 LFOs while maintaining the ratio of their frequencies. | |||
The **Fc** control moves the frequencies of the first three filters, but not by equal amounts. The lowest filter moves at 1V/Oct, but the middle two move less. The top filter is fixed at 3700 Hz. | |||
The **Q** control does just what it says - controls the Q (resonance) of the filters. | |||
The **Modulation Depth** controls how much of the summed LFOs get to each filter. Again, the lower filters move farther, and the top filter is fixed. | |||
The smaller knobs next to the main knobs are **attenuverters**, which scale control voltages. For more on attenuverters, [see below](#atten) | |||
There are three LFO outputs next to the blinking LFOs. These may be used to modulate other modules, or as semi-random voltage sources. | |||
**Bass boost** switch. When it’s in the up position (on) there should be more bass. This is done by switching some or all of the filters from bandpass to lowpass. | |||
LFO **Matrix** switch. This is the unlabeled switch in the LFO section. When it’s down (default position) the LFOs are closely correlated. In the middle we try to make them a little bit more independent. When it’s in the up position the LFOs will often go in different directions. | |||
# Booty Shifter frequency shifter <a name="shifter"></a> | |||
**Booty Shifter** is a frequency shifter inspired by the Moog/Bode frequency shifter module. | |||
 | |||
The name "Booty Shifter" is a nod to the classic analog module, as well as to a black cat named Booty. | |||
Booty Shifter will take an audio input and shift the frequencies up or down. This is not like a pitch shift where harmonics will remain in tune; it is an absolute frequency shift in Hz, so in general **harmonics will go way out of tune.** It is similar to a ring-modulator, but less extreme and more versatile. | |||
## Getting good sounds from Booty Shifter | |||
Feed in music and shift the frequency a good amount. | |||
Feed in **speech or radio** and shift it. | |||
Feed the CV from a **sequencer** to sequence the mayhem. | |||
Shift **drums** up or down a little bit to re-tune them without the usual pitch-shifting artifacts. | |||
Small shifts in conjunction with delays can make a chorus-like effect to thicken music. | |||
## Inputs and outputs | |||
* **IN** is the audio input. | |||
* **CV** is the pitch shift control voltage. -5V will give minimum shift, +5 will give maximum. | |||
* **DN** is the down-shifted output. | |||
* **UP** is the up-shifted output. | |||
## Controls | |||
**RANGE** sets the total shift range in Hz. For example, the 50 Hz setting means that the minimum shift is 50 Hz down, and the maximum is 50 Hz up. | |||
Range value **Exp is different**. Here minimum shift is 2 Hz, maximum is 2 kHz, with an exponential response. As of version 0.6.2 the response is an accurate 1 Volt per Octave. | |||
Shift **AMT** is added to the control voltage, with a range of -5..5. | |||
## Oddities and limitations | |||
If you shift the frequency up too far, it will alias. There is no anti-aliasing, so if the highest input frequency + shift amount > sample_rate / 2, you will get aliasing. Of course the Bode analog original did not alias. | |||
If you shift the input down a lot, frequencies will go **below zero and wrap around**. Taken far enough this will completely **reverse the spectrum** of the input. This was a prized feature of the Bode original. | |||
As you shift the input down, you may start to generate a lot of subsonic energy. A **High Pass filter** may clean this up. | |||
The down shift **frequency fold-over**, while true to the original, does cause problems when trying to pitch drum tracks down a lot. High pass filtering the input before it is down-shifted can control this. | |||
# Formants vocal filter <a name="formants"></a> | |||
 | |||
Like the **Vocal Animator**, this is a filter bank tuned to the formant frequencies of typical **singing voices**. Unlike Growler, however, the filters do not animate on their own. In addition, the filters are preset to frequencies, bandwidths, and gains that are taken from **measurements of human singers**. | |||
One of the easiest ways to **get a good sound** from Formants is to use it like a regular VCF. For example, control Fc with an ADSR. Then put a second modulation source into the vowel CV - something as simple as a slow LFO will add interest. | |||
Use it as a **filter bank**. Just set the knobs for a good sound and leave it fixed to add vocal tones to a pad. Again, modulating the vowel CV can easily give great results. | |||
Try to synthesize something like **singing** by sequencing the vowel CV of several formants. Leave the Fc in place, or move it slightly as the input pitches move. | |||
Controls: | |||
* **Fc** control moves all the filters up and down by the standard one "volt" per octave. | |||
* **Vowel** control smoothly interpolates between 'a', 'e', 'i', 'o', and 'u'. | |||
* **Model** control selects different vocal models: bass, tenor, countertenor, alto, and soprano. | |||
* **Brightness** control gradually boosts the level of the higher formants. When it is all the way down, the filter gains are set by the singing models in the module, which typically fall off with increasing frequency. As this control is increased the gain of the high formant filters is brought up to match the F1 formant filter. | |||
The **LEDs across the top** indicate which formant is currently being "sung". | |||
## About Attenuverters <a name="atten"></a> | |||
The small knobs next to the bigger knobs are **attenuverters**. They scale and/or invert the control voltage inputs next to them. When they are turned all the way up the full CV comes through. As they are turned down less CV comes through. Straight up none passes. As they are turned down further the CV comes back, but inverted. | |||
Sometimes we use attenuverters with a *linear taper*, and sometimes we use an *audio taper*. If you find that on a particular module you do not like the response of the attenuverters, please log a github issue. | |||
## Control voltage ranges <a name="cv"></a> | |||
Our modules usually expect a control voltage range of **-5 to +5**. The associated offset knobs will also add -5 to +5. After attenuverters are applied to CV the knob value is added. After all that, the result is usually clipped to the -5 to +5 range. |
@@ -1,207 +1,40 @@ | |||
# Table of contents | |||
# The Squinky Labs modules for VCV Rack | |||
[Chopper](#chopper) Is a tremolo powered by a clock-synchable LFO. The LFO is highly programmable to give a range of waveforms. | |||
Below are short descriptions of our modules with links to more detailed manuals. | |||
[Thread Booster](#booster) reduces pops and clicks in VCV Rack by reprogramming VCV's audio engine. | |||
The [release notes](release-notes.md) describe recent changes to our modules. | |||
[Colors](#colors) is a colored noise generator. It can generate all the common **"colors"** of noise, including white, pink, red, blue, and violet. | |||
# Things that make sound | |||
[Growler](#growler) is a "vocal animator." It imparts random vocal timbres on anything played through it. The pseudo-random LFOs all have discrete outputs. | |||
 | |||
[Booty Shifter](#shifter) is an emulation of the legendary Moog/Bode frequency shifter. | |||
[EV3](./ev3.md) is three VCOs in a single module. Each of the three VCOs is a clone of Befaco's EvenVCO, with oscillator sync added. Like EvenVCO, it sounds good, uses little CPU, and has very little aliasing distortion. | |||
[Formants](#formants) is a programmable bank of filters that can synthesize various vowel sounds and morph between them. | |||
[Colors](./colors.md) is a colored noise generator. It can generate all the common **"colors"** of noise, including white, pink, red, blue, and violet. And all the colors in between. | |||
[Attenuverters](#atten) | |||
[Chebyshev Waveshaper VCO](../docs/chebyshev.md) can make sounds like no other VCO. It contains a VCO, ten polynomial wave-shapers, and one clipper/folder. Among other things, it is a harmonic oscillator. | |||
[CV ranges](#cv) | |||
[Functional VCO-1](./functional-vco-1.md) Is an improved version of the Fundamental VCO-1. Like Fundamental VCO-1, it will never alias, no matter what you throw at it. It is one of the few VCOs that can do sync, FM, and PWM without aliasing. Functional VCO-1 improves on Fundamental by lowering the CPU usage dramatically. | |||
# Chopper tremolo / programmable LFO <a name="chopper"></a> | |||
# Things that process sound | |||
In its simplest use, Chopper produces a very wide range of **tremolo** effects. The built-in LFO can produce a wide range of waveforms that cover many of the waveforms produced by the tremolo circuits built into **vintage guitar amplifiers**. | |||
 | |||
The LFO is sent to an output so that it may modulate other modules. | |||
[Shaper](./shaper.md). Yet another wave shaper. But unlike most, this one has almost no aliasing distortion. And a few new shapes that sound nice. | |||
There is also a **clock synchronizer** and multiplier. | |||
[Chopper](./chopper.md) Is a tremolo powered by a clock-synchable LFO. The LFO is highly programmable to give a range of waveforms. A built-in clock multiplier enables easy rhythmic effects. | |||
To use Chopper as a tremolo, send a signal to the *in* jack, and listen to the *out* jack. Leave the *clock* control at the default *int* setting. Most of the knob settings will now affect the tremolo effect. | |||
[Growler](./growler.md) is a "vocal animator." It imparts random vocal timbres on anything played through it. The pseudo-random LFOs all have discrete outputs. | |||
## Chopper LFO | |||
[Booty Shifter](./shifter.md). An emulation of the legendary Moog/Bode frequency shifter. It is great for "warping" sounds run through it. | |||
 | |||
[Formants](./formants.md) is a programmable bank of filters that can synthesize various vowel sounds and morph between them easily. | |||
To understand all the LFO settings, it helps to watch the outputs on a scope. | |||
# Other things | |||
 | |||
The LFO starts as **skewed** sawtooth. In the middle position it is a symmetric triangle wave, at one end a positive sawtooth and at the other a negative sawtooth. The signal is sent to the **saw** output. | |||
[Gray Code](./gray-code.md). Think of it as a semi-random clock divider. Or not. Gray codes have the cool property that only one bit changes at a time. Having only one “thing” change at a time can be interesting for music, so we are hoping you will find some good things to do with it. | |||
The skewed saw then goes to a **waveshaper**. As the shape control is increased the LFO is gradually rounded and then flattened. The shaped LFO is send to the *lfo* output, and used internally to modulate the audio input. | |||
[LFN](./lfn.md) is a random voltage generator made by running low frequency noise through a graphic equalizer. The equalizer gives a lot of easy control over the shape of the randomness. | |||
LFO Controls: | |||
* **Shape** Flattens the LFO waveform. | |||
* **Skew** Dials in the amount of asymmetry in the LFO. | |||
* **Depth** Shifts and scales the LFO. | |||
When used as a tremolo effect, you will hear **more tremolo** when these controls are turned up. | |||
## Chopper clock | |||
The LFO in Chopper may be synchronized with the ckin signal. There is a built-in **clock multiplier**. To use the synchronization, patch a clock to the ckin, and select x1 from the **clock** knob. To run at a multiple of the input clock, select x2, x3, or x4. | |||
When Chopper is being synched, the **Phase** control sets the phase difference between the external clock and the synchronized LFO. This may be used to "dial in" the tremolo so that it sounds exactly on the beat (or off the beat). | |||
There is also an internal LFO that is controlled by the **Rate** control. Set the clock control to *int* to use the internal clock. | |||
# Thread Booster<a name="booster"></a> | |||
Thread booster raises the priority of VCV Rack's audio rendering thread. In many cases this decreases the annoying pops, ticks, and dropouts that many users are experiencing. | |||
Many users have reported that Thread Booster helps significantly. Others have reported that it does not help at all. No one has reported a detrimental effect. | |||
For a deeper dive into the Thread Booster, you should read [this document](./thread-booster.md). | |||
Thread Booster has a UI that lets you boost the priority of the audio thread. There are three arbitrary settings: normal, boosted, and real time. When the switch is in the bottom position, the plugin does nothing; the audio thread keeps its default priority. In the boost (middle) position, it sets the thread priority to the highest priority non-real-time setting. In the real-time position it attempts to set it to the highest possible priority, or near it. | |||
If setting the priority fails, the red error light lights up, and the priority stays where it was last. | |||
To use Thread Booster, just insert an instance into VCV Rack, then adjust the boost switch. In general we recommend the "real time" setting, if it is available on your computer. | |||
Once Thread booster is in your session, it will boost all the audio processing - it doesn't matter if other modules are added before or after - they all get boosted. | |||
Linux users - you must read [the detailed document](./thread-booster.md) to use this module. | |||
Note to users who downloaded the original version of Thread Booster: we've improved it a bit since then, especially on Linux and Windows. | |||
# Colors variable slope noise generator<a name="colors"></a> | |||
 | |||
Colors is a colored noise generator. It can generate all the common **"colors"** of noise, including white, pink, red, blue, and violet. It can also produce all the colors in between, as it has a **continuously variable slope**. | |||
Colors has a single control, "slope." This is the slope of the noise spectrum, from -8 dB/octave to +8 dB/octave. | |||
The slope of the noise is quite accurate in the mid-band, but at the extremes we flatten the slope to keep from boosting super-low frequencies too much, and to avoid putting out enormous amounts of highs. So the slope is flat below 40hz, and above 6kHz. | |||
## Things to be aware of | |||
When the **slope** changes, Color needs to do a lot of calculations. While this is normally not a problem, it’s possible that quickly changing the slope of many instances of Colors could cause pops and dropouts. | |||
The slope control does not respond instantly. If you turn the knob, you will hear the change, but if you were to modulate the CV very quickly you might notice the slowness. | |||
# Growler | |||
 | |||
**Growler** is a re-creation of the Vocal Animator circuit invented by Bernie Hutchins, and published in Electronotes magazine in the late 70's. It continuously morphs between different vaguely voice like tones. | |||
**To get a good sound:** run any harmonically rich signal into the input, and something good will come out. Low frequency pulse waves and distorted sounds make great input. | |||
The controls do pretty much what you would expect: | |||
* **LFO** controls the speed of the modulation LFOs. | |||
* **Fc** controls the average frequency of the multiple filters. | |||
* **Q** controls the sharpness of the filters. | |||
* **Depth** controls how much of the modulation LFOs are applied to the filters. | |||
## How Growler works | |||
 | |||
There are four **bandpass filters**, roughly tuned to some typical vocal formant frequencies: 522, 1340, 2570, and 3700 Hz. The filters are run in parallel, with their outputs summed together. | |||
The first three filter frequencies are modulated by an LFO comprised of **4 triangle wave LFOs** running at different frequencies. They are summed together in various combinations to drive each of the filters. | |||
Each **CV input stage** is the same: a knob that supplies a fixed offset and a CV input that is processed by an attenuverter. The processed CV is added to the knob voltage. See below for more on [Attenuverters](#atten) and [CV ranges](#cv). | |||
The **LFO** Rate control shifts the speed of all 4 LFOs while maintaining the ratio of their frequencies. | |||
The **Fc** control moves the frequencies of the first three filters, but not by equal amounts. The lowest filter moves at 1V/Oct, but the middle two move less. The top filter is fixed at 3700 Hz. | |||
The **Q** control does just what it says - controls the Q (resonance) of the filters. | |||
The **Modulation Depth** controls how much of the summed LFOs get to each filter. Again, the lower filters move farther, and the top filter is fixed. | |||
The smaller knobs next to the main knobs are **attenuverters**, which scale control voltages. For more on attenuverters, [see below](#atten) | |||
There are three LFO outputs next to the blinking LFOs. These may be used to modulate other modules, or as semi-random voltage sources. | |||
**Bass boost** switch. When it’s in the up position (on) there should be more bass. This is done by switching some or all of the filters from bandpass to lowpass. | |||
LFO **Matrix** switch. This is the unlabeled switch in the LFO section. When it’s down (default position) the LFOs are closely correlated. In the middle we try to make them a little bit more independent. When it’s in the up position the LFOs will often go in different directions. | |||
# Booty Shifter frequency shifter <a name="shifter"></a> | |||
**Booty Shifter** is a frequency shifter inspired by the Moog/Bode frequency shifter module. | |||
 | |||
The name "Booty Shifter" is a nod to the classic analog module, as well as to a black cat named Booty. | |||
Booty Shifter will take an audio input and shift the frequencies up or down. This is not like a pitch shift where harmonics will remain in tune; it is an absolute frequency shift in Hz, so in general **harmonics will go way out of tune.** It is similar to a ring-modulator, but less extreme and more versatile. | |||
## Getting good sounds from Booty Shifter | |||
Feed in music and shift the frequency a good amount. | |||
Feed in **speech or radio** and shift it. | |||
Feed the CV from a **sequencer** to sequence the mayhem. | |||
Shift **drums** up or down a little bit to re-tune them without the usual pitch-shifting artifacts. | |||
Small shifts in conjunction with delays can make a chorus-like effect to thicken music. | |||
## Inputs and outputs | |||
* **IN** is the audio input. | |||
* **CV** is the pitch shift control voltage. -5V will give minimum shift, +5 will give maximum. | |||
* **DN** is the down-shifted output. | |||
* **UP** is the up-shifted output. | |||
## Controls | |||
**RANGE** sets the total shift range in Hz. For example, the 50 Hz setting means that the minimum shift is 50 Hz down, and the maximum is 50 Hz up. | |||
Range value **Exp is different**. Here minimum shift is 2 Hz, maximum is 2 kHz, with an exponential response. As of version 0.6.2 the response is an accurate 1 Volt per Octave. | |||
Shift **AMT** is added to the control voltage, with a range of -5..5. | |||
## Oddities and limitations | |||
If you shift the frequency up too far, it will alias. There is no anti-aliasing, so if the highest input frequency + shift amount > sample_rate / 2, you will get aliasing. Of course the Bode analog original did not alias. | |||
If you shift the input down a lot, frequencies will go **below zero and wrap around**. Taken far enough this will completely **reverse the spectrum** of the input. This was a prized feature of the Bode original. | |||
As you shift the input down, you may start to generate a lot of subsonic energy. A **High Pass filter** may clean this up. | |||
The down shift **frequency fold-over**, while true to the original, does cause problems when trying to pitch drum tracks down a lot. High pass filtering the input before it is down-shifted can control this. | |||
# Formants vocal filter <a name="formants"></a> | |||
 | |||
Like the **Vocal Animator**, this is a filter bank tuned to the formant frequencies of typical **singing voices**. Unlike Growler, however, the filters do not animate on their own. In addition, the filters are preset to frequencies, bandwidths, and gains that are taken from **measurements of human singers**. | |||
One of the easiest ways to **get a good sound** from Formants is to use it like a regular VCF. For example, control Fc with an ADSR. Then put a second modulation source into the vowel CV - something as simple as a slow LFO will add interest. | |||
Use it as a **filter bank**. Just set the knobs for a good sound and leave it fixed to add vocal tones to a pad. Again, modulating the vowel CV can easily give great results. | |||
Try to synthesize something like **singing** by sequencing the vowel CV of several formants. Leave the Fc in place, or move it slightly as the input pitches move. | |||
Controls: | |||
* **Fc** control moves all the filters up and down by the standard one "volt" per octave. | |||
* **Vowel** control smoothly interpolates between 'a', 'e', 'i', 'o', and 'u'. | |||
* **Model** control selects different vocal models: bass, tenor, countertenor, alto, and soprano. | |||
* **Brightness** control gradually boosts the level of the higher formants. When it is all the way down, the filter gains are set by the singing models in the module, which typically fall off with increasing frequency. As this control is increased the gain of the high formant filters is brought up to match the F1 formant filter. | |||
The **LEDs across the top** indicate which formant is currently being "sung". | |||
## About Attenuverters <a name="atten"></a> | |||
The small knobs next to the bigger knobs are **attenuverters**. They scale and/or invert the control voltage inputs next to them. When they are turned all the way up the full CV comes through. As they are turned down less CV comes through. Straight up none passes. As they are turned down further the CV comes back, but inverted. | |||
Sometimes we use attenuverters with a *linear taper*, and sometimes we use an *audio taper*. If you find that on a particular module you do not like the response of the attenuverters, please log a github issue. | |||
## Control voltage ranges <a name="cv"></a> | |||
Our modules usually expect a control voltage range of **-5 to +5**. The associated offset knobs will also add -5 to +5. After attenuverters are applied to CV the knob value is added. After all that, the result is usually clipped to the -5 to +5 range. | |||
[Thread Booster](./booster.md) reduces pops and clicks in VCV Rack by reprogramming VCV's audio engine. |
@@ -0,0 +1,145 @@ | |||
# Chebyshev | |||
Our waveshaper VCO. | |||
 | |||
## Description of the module | |||
Chebyshev polynomials have been used to generate complex tones since the early days of computer music. This special math discovered by Mr. Chebyshev enables digital generation of waveforms with any overtone structure using very little computer power. In addition, it is easy and computationally inexpensive to vary the spectrum over time. | |||
Eventually, this form of synthesis fell out of favor, as FM could provide a wider variety of timbres with acceptable CPU usage. Now, however, the distinctive sound of this form of synthesis provides another unique source of sounds for VCV Rack users. | |||
The magic of the Chebyshev polynomials is that if a sine wave with amplitude one is put into a Chebyshev polynomial, the output will still be a sine wave, but multiplied in pitch by an integer. | |||
In our implementation we include the first ten Chebyshev polynomials to generate the first ten harmonics of the harmonic series. These are then mixed together based on knob settings and control voltages to give an output tone with complete control over ten harmonics. | |||
Many of the controls in this module allow different ways of mixing together these ten harmonics. In addition external signals may be shaped by the waveshaper, with folding or clipping applied. | |||
Like all our modules, Chebyshev's CPU usage is quite low. | |||
## Some tips | |||
It can be a great help learning this module if you patch the output to a Scope module and a frequency analyzer. As you adjust the controls it will be clearer what is going on. | |||
Also, note that we will repeat that when set up normally, each Chebyshev waveshaper is producing a different harmonic of the VCO output. Because of this, we will refer to these as "harmonic levels" and "waveshaper levels" interchangeably. | |||
And - each waveshaper is a perfect harmonic only when driven by a pure sine at exactly 1Vp-p. | |||
## Signal Flow | |||
First there is a sine wave VCO. It has the controls you would expect, as well as a through-zero linear FM input, which allows a minimal DX7-style FM. | |||
The VCO output then goes to a wave folder/clipper with gain controls. This allows for some distortion effects, and keeps the signal in a range that will keep the next stage happy. | |||
The output of the folder/clipper then goes to ten parallel Chebyshev waveshapers. The outputs of these are then mixed together through a specialized mixer. | |||
When everything is set in a typical manner, each of the Chebyshev waveshapers will be outputting a pure sine wave at an integer multiple of the fundamental frequency. Thus, each one will be a discrete harmonic. | |||
## Description of the controls | |||
### VCO | |||
The controls in the upper right are all for the sine wave VCO. | |||
Octave transposes the pitch in even octaves. | |||
* **Tune** raises or lowers the pitch by up to a perfect fifth. | |||
* **Mod** controls the modulation (exponential FM) depth of the signal patched to the Mod jack. | |||
* **LFM** controls the linear FM depth of the signal patched to the LFM jack. | |||
* **V/Oct** input is where the main control voltage is patched. | |||
Mod and LFM perform different functions. Mod, like the CV input, is an exponential control. If an LFO is patched into the Mod input and the Mod depth is adjusted for a vibrato of one semitone, that vibrato will be one semitone regardless of the base pitch. But if an audio rate signal is patched into the Mod input you will tend to get “clangorous” sounds with inharmonic overtones. | |||
LFM, on the other hand, allows through-zero linear FM. While this is not very good for vibrato it does create complex timbres where the harmonics are in tune, and that "in tuneness" will remain as the mod depth is changed. Exponential FM at audio rates can also be tuned, but the tuning will disappear as the mod depth changes, making it impossible to to generate dynamic harmonic sounds. | |||
### Folder/Clipper | |||
Chebyshev polynomials are poorly behaved if they see more than one (volt) at their input. So we use a folder/clipper to make sure this doesn’t happen. | |||
The controls and CV of the Folder/Clipper: | |||
* **Fold/Clip** switch. In clip mode, it is a simple hard clipper. In fold mode it’s a waveform folder. | |||
* **Clip LED**. The LED will be green when there is signal, and red when the folder/clipper engages. | |||
* *Gain*. Controls how hard the folder/clipper is driven. Gain knob and CV are combined. | |||
* **Gain trim**. The small knob below the **gain** knob is an attenuator for the **gain** CV. New in 0.6.9. | |||
* **EG**. Also combines with the gains. | |||
* **Ext In.** When a signal is patched here it replaces the internal VCO, allowing any signal to be run through the waveshaper. | |||
Note that while you can get some cool effects with clipping and folding, they will tend to cause audible aliasing at higher frequencies. Use with care. | |||
In classic waveshaping synthesis an ADSR or similar would be connected to the EG input. By dynamically changing the level of the sine wave hitting the waveshapers a dynamic timbre will be generated. | |||
The output of the folder/clipper drives the Chebyshev waveshapers. The last group of controls all work together to determine how the waveshapers are mixed together. | |||
### Waveshaper controls | |||
There are a lot of controls that work together to determine how the waveshapers are mixed. When configured normally, that means these controls determine the ratios of all the harmonics of the VCO. | |||
The **small knobs** running up the left side individually control each waveshaper/harmonic, with fundamental on top, and harmonic 10 on the bottom. | |||
The input jacks next to them allow the levels of each harmonic to be voltage controlled. | |||
The **Preset** button toggles all ten harmonics between some good starting points, and also resets the Gain to be exactly 1. | |||
The **Even** control increases/decreases the level of all the even harmonics together. | |||
The **Odd** control increases/decreases the level of all the odd harmonics together. | |||
The **Slope** control will apply a gradual roll-off of the upper harmonics. When it is all the way down the roll-off is 18 decibels per octave. When it is all the way up it’s flat. | |||
Note that the level of the fundamental is not affected by either the Even or Odd control. | |||
The Odd, Even, and Slope controls may be thought of as subtractive. When they are all the way up, they have no effect, and you get the mix you would expect from the individual harmonic levels. When you turn these controls down they will reduce the levels of the corresponding harmonics. | |||
The **Preset** button toggles between two or three settings. It will always have a setting where the fundamental is full and all other harmonics off, and a setting where all harmonics are up full. In addition, if you started with your own setting of the harmonics, the preset button will eventually take you back there, but with the master gain set back to one. | |||
## Several patching ideas | |||
### Arbitrary waveform VCO | |||
Turn the Odd, Even, and Slope controls all the way up. Then the level of each harmonic is controller its own volume control. Mix the harmonics to get a pleasing sound, then use as a static timbre, or run it into a VCO. | |||
Or start with the individual harmonics all the way up, manipulate Even/Odd/Slope to start sculpting the harmonics. | |||
Don’t forget the Preset button - it’s your friend here. | |||
### Dynamic Waveshaping | |||
Use the built-in VCO. Adjust the harmonic mix to something nice and bright. Then connect an ADSR to the EG input. The ADSR will more or less control the brightness. Make sure the the clip LED is just on the edge of clipping when the EG input is at max. | |||
At very low levels the output will be primarily fundamental. At max level it will be determined by the waveshaper mix controls. The timbre will go from dull to bright as the EG input increases, but the evolution of the timbre will not be completely even, and definitely will be different than what you would get modulating a VCF with an ADSR. | |||
The evolution of timbres often sounds "brassy," like a brass instrument. Brass synthesis was indeed a common use of waveshaping synthesis before the era of affordable sampling and physical modelling. | |||
### Voltage controlled filter slope | |||
Most conventional VCFs allow a filter of a specific shape to be modulated up and down in frequency. A variable slope VCF lets the shape of the filter be modulated. Modulating the filter slope can be more "natural" sounding. | |||
In the case of Chebyshev we don’t have a filter, but by controlling the harmonic levels directly we can mimic one. Set up a nice bright sound, patch an ADSR into the Slope input, and try out a simulated variable slope filter. | |||
### Voltage control of spectrum | |||
The possibilities for timbral variation seem limitless if you take the time to patch controls signals into the harmonic level CV inputs. Use all the usual suspects here - clocks, LFOs, shift registers, sequencers. | |||
### FM oscillator | |||
The inclusion of the LFM input allows a simple form of FM synthesis - one operator FM. | |||
Use an external sine VCO, and patch it into the LFM input on Chebyshev. Turn up the LFM knob. Use the Preset button to turn up the fundamental and turn off the other waveshaper outputs. | |||
In FM speak, the external VCO is the modulator and the VCO in Chebyshev is the carrier. The result should be consonant if the frequency of the carrier is a small integer multiple of the modulator frequency. For example, set the modulator an octave lower than the carrier. | |||
Once again, as the modulation is increased more harmonics will be present, so use an external ADSR and VCA to modulate the level of the modulator sine before it’s patched into Chebyshev. | |||
Of course FM will work alongside the waveshapers, so feel free to go crazy with all the knobs. But don’t be disappointed if the results are harsh and strange. | |||
### Process external signals | |||
Run something other than a sine wave into Ext In, then process your signal with the folder and/or the Chebyshev waveshapers. Again, the output of the waveshapers can be pretty unpredictable as the input signal becomes more complex. | |||
## A note about Aliasing | |||
In the standard configuration there will be little, if any, aliasing. Since the highest harmonic is 10X the fundamental, the Chebyshev module can’t even start to alias until the fundamental gets to 2kHz. | |||
That said, there is no anti-aliasing in this module. The wavefolder can easily alias. Normally the LFM will not alias very much, but with high modulation depth and high pitches it will alias quite a lot. | |||
We have an informational article that talks more about aliasing [here](./aliasing.md). |
@@ -0,0 +1,36 @@ | |||
# Chopper tremolo / programmable LFO <a name="chopper"></a> | |||
 | |||
In its simplest use, Chopper produces a very wide range of **tremolo** effects. The built-in LFO can produce a wide range of waveforms that cover many of the waveforms produced by the tremolo circuits built into **vintage guitar amplifiers**. | |||
The LFO is sent to an output so that it may modulate other modules. | |||
There is also a **clock synchronizer** and multiplier. | |||
To use Chopper as a tremolo, send a signal to the *in* jack, and listen to the *out* jack. Leave the *clock* control at the default *int* setting. Most of the knob settings will now affect the tremolo effect. | |||
## Chopper LFO | |||
 | |||
To understand all the LFO settings, it helps to watch the outputs on a scope. | |||
The LFO starts as **skewed** sawtooth. In the middle position it is a symmetric triangle wave, at one end a positive sawtooth and at the other a negative sawtooth. The signal is sent to the **saw** output. | |||
The skewed saw then goes to a **waveshaper**. As the shape control is increased the LFO is gradually rounded and then flattened. The shaped LFO is send to the *lfo* output, and used internally to modulate the audio input. | |||
LFO Controls: | |||
* **Shape** Flattens the LFO waveform. | |||
* **Skew** Dials in the amount of asymmetry in the LFO. | |||
* **Depth** Shifts and scales the LFO. | |||
When used as a tremolo effect, you will hear **more tremolo** when these controls are turned up. | |||
## Chopper clock | |||
The LFO in Chopper may be synchronized with the ckin signal. There is a built-in **clock multiplier**. To use the synchronization, patch a clock to the ckin, and select x1 from the **clock** knob. To run at a multiple of the input clock, select x2, x3, or x4. | |||
When Chopper is being synched, the **Phase** control sets the phase difference between the external clock and the synchronized LFO. This may be used to "dial in" the tremolo so that it sounds exactly on the beat (or off the beat). | |||
There is also an internal LFO that is controlled by the **Rate** control. Set the clock control to *int* to use the internal clock. |
@@ -0,0 +1,9 @@ | |||
# Colors variable slope noise generator<a name="colors"></a> | |||
 | |||
Colors is a colored noise generator. It can generate all the common **"colors"** of noise, including white, pink, red, blue, and violet. It can also produce all the colors in between, as it has a **continuously variable slope**. | |||
Colors has a single control, "slope." This is the slope of the noise spectrum, from -8 dB/octave to +8 dB/octave. | |||
The slope of the noise is quite accurate in the mid-band, but at the extremes we flatten the slope to keep from boosting super-low frequencies too much, and to avoid putting out enormous amounts of highs. So the slope is flat below 40hz, and above 6kHz. |
@@ -0,0 +1,63 @@ | |||
# EV3 triple VCO | |||
 | |||
## About EV3 | |||
EV3 is made from the sound generating part of Befaco’s EvenVCO, replicated three times. dWe added sync, which was never implemented in the EvenVCO. Then we did our usual overhaul to reduce CPU usage. | |||
The result is a module containing three VCOs that together use about the same amount of CPU as a single instance of EvenVCO. | |||
While the waveform generation and alias suppression is lifted directly from EvenVCO, the control selection and response of EV3 is our own design. While the three VCOs are easiest to use together, they may be used completely independently. | |||
EvenVCO remains an excellent choice for a VCO, but there are definitely some cases where you might choose EV3 instead: | |||
* It is very easy to make giant stacked oscillator patches, like we did in the old days. | |||
* One EV3 uses less panel space and CPU than three separate instances of EvenVCO. | |||
* The sync feature is a welcome addition. | |||
* The semitone pitch offset can inspire patches and harmonies that you might not find with the standard controls on most VCOs. | |||
* It is easy to patch up chords with EV3. | |||
That said, EV3 only offers one waveform output at a time per VCO, whereas EvenVCO makes them all available at the same time. | |||
Using EV3 | |||
The Initial pitch is controlled by a stepped octave control, a stepped semitone control, and a fine tune. The octave and semitone are displayed as an octave number and a musical interval. | |||
VCO 2 and 3 have an option for hard sync. The sync input is always the saw output of VCO 1. | |||
There are independent outputs for each VCO, as well as a 3-to-1 mixer driving a mixed output. | |||
The CV connections are a bit unusual. If you patch one of the top inputs (VCO 1 inputs) it will drive all three. Each VCO will pick up its input from the first patched input. So, for example, VCO 2 will get its input from the second row, but if nothing is patched to this input it will pick up input from the first row (VCO 1). | |||
This makes for much less patching when stacking two or three VCO sections in a single voice. | |||
The controls in depth | |||
The octave knob is at the top left. It is unlabeled, but does have the octave number displayed on top of it. It has a 10 octave range, just like EvenVCO. | |||
The semitone knob just to the right will add or subtract up to 12 semitones. The label above the knob displays the semitone offset as an interval in diatonic harmony. For example, 7 semitones up is labeled “5th”. Note that the intervals displayed are always an octave plus a transposition up. So lowering 5:0 by two semitones will give you 4:m7th – so it’s displayed as one octave down and a minor 7th up. Note that some of these intervals have more than one spelling. In these cases we made some arbitrary decisions: | |||
* One semitone up is called minor second, although some would call it a flat second. | |||
* Six semitones up is called Diminished fifth, although it could be called an augmented fourth or a tritone. | |||
* No transposition is displayed as zero, although it would be more correct to call it P1, or perhaps unison. | |||
The fine control transposes the VCO pitch up and down up to a semitone. | |||
The mod knob controls the amount of pitch modulation applied to the Fm input CV. This is of course exponential pitch modulation, suitable for pitch bending and vibrato, but not so much for FM synthesis which works linear FM. | |||
Below the blue knobs are two small black knobs to control the pulse width and pulse width modulation depth. These only have an effect when the square wave output is selected. Although they are only labeled on VCO1, they function just the same on VCO 2 and 3. | |||
The switches below the knobs are radio buttons that select the waveform for each VCO. The waveforms are sine, triangle, saw-tooth, square, even, and off. The even waveform is what gave the original EvenVCO its name. It is an unusual waveform that has only even harmonics, and not odd ones (not counting the fundamental, which is there). Selecting “no waveform” can be useful when you are patching and want to hear each VCO by itself – like a mute button on a mixing console. | |||
The CV inputs are at the bottom. The top row is for VCO1, the next for VCO2, and the last row is for VCO3. | |||
V/Oct is where the main pitch CV is patched, and sets the overall pitch of the VCO. Fm is a less sensitive pitch input used to modulate the pitch. As noted above, the sensitivity of this input is controlled by the Mod knob. The last column of CV inputs is for pulse width modulation. This only has an effect when the square wave is selected. It works in conjunction with the PW and PWM knobs. | |||
The output section has a column of three output level controls, one for each VCO. Then there are the three jack, one for each VCO output, and a mixed output. | |||
We have an informational article that talks more about aliasing. It shows you how to compare different modules using a spectrum analyzer. [Aliasing Story](./aliasing.md). | |||
If you would like some information on how we reduced the CPU usage of EvenVCO, you can [find it here](../docs/vco-optimization.md). |
@@ -0,0 +1,20 @@ | |||
# Formants vocal filter <a name="formants"></a> | |||
 | |||
Like the **Vocal Animator**, this is a filter bank tuned to the formant frequencies of typical **singing voices**. Unlike Growler, however, the filters do not animate on their own. In addition, the filters are preset to frequencies, bandwidths, and gains that are taken from **measurements of human singers**. | |||
One of the easiest ways to **get a good sound** from Formants is to use it like a regular VCF. For example, control Fc with an ADSR. Then put a second modulation source into the vowel CV - something as simple as a slow LFO will add interest. | |||
Use it as a **filter bank**. Just set the knobs for a good sound and leave it fixed to add vocal tones to a pad. Again, modulating the vowel CV can easily give great results. | |||
Try to synthesize something like **singing** by sequencing the vowel CV of several formants. Leave the Fc in place, or move it slightly as the input pitches move. | |||
Controls: | |||
* **Fc** control moves all the filters up and down by the standard one "volt" per octave. | |||
* **Vowel** control smoothly interpolates between 'a', 'e', 'i', 'o', and 'u'. | |||
* **Model** control selects different vocal models: bass, tenor, countertenor, alto, and soprano. | |||
* **Brightness** control gradually boosts the level of the higher formants. When it is all the way down, the filter gains are set by the singing models in the module, which typically fall off with increasing frequency. As this control is increased the gain of the high formant filters is brought up to match the F1 formant filter. | |||
The **LEDs across the top** indicate which formant is currently being "sung". |
@@ -0,0 +1,13 @@ | |||
# Functional VCO-1 <a name="fun"></a> | |||
 | |||
Functional VCO-1 works just like its namesake. The control layout is familiar, the sound is the same, but it uses about 1/4 as much CPU as the original. | |||
We believe VCV's Fundamental VCO is an unsung hero. It's one of the few VCOs that never has audible aliasing artifacts. You can sync it, and modulate all its inputs, but the sound never falls apart. | |||
We "forked" the code to Fundamental VCO-1 and modified it a little bit to make it much more CPU efficient. Now you may use a lot more of them without pops, clicks, and dropouts. | |||
If you would like the details of how we did this optimization, you can [find them here](../docs/vco-optimization.md). | |||
We have an informational article that talks more about aliasing. It shows you how to compare different modules using a spectrum analyzer. [Aliasing Story](./aliasing.md). |
@@ -0,0 +1,18 @@ | |||
# Gray Code: eclectic clock divider | |||
 | |||
## About Gray Code | |||
A cool feature of gray codes is that only one bit changes at a time. Having only one “thing” change at a time can be interesting for music, so we are hoping you will find some good things to do with it. | |||
WikiPedia has a very good article on gray codes: https://en.wikipedia.org/wiki/Gray_code | |||
Our Gray Code module has only one control. It selects between standard gray code and balanced gray codes. With a standard gray code, the lower bits change much more often than the high bits. You can see it counting up. With the balanced gray codes, all the bits change more or less the same amount, but of course no two ever change at the same time. | |||
Each bit of the 8-bit gray code comes out to an output jack. The LED beside the output shows when it goes high and low. | |||
There is an additional output that adds up all the bits with a binary weighting, kind of like a DAC. | |||
The external clock input must be driven with a clock - there is no internal clock. | |||
Now let your imagination run wild! |
@@ -0,0 +1,39 @@ | |||
# Growler | |||
 | |||
**Growler** is a re-creation of the Vocal Animator circuit invented by Bernie Hutchins, and published in Electronotes magazine in the late 70's. It continuously morphs between different vaguely voice like tones. | |||
**To get a good sound:** run any harmonically rich signal into the input, and something good will come out. Low frequency pulse waves and distorted sounds make great input. | |||
The controls do pretty much what you would expect: | |||
* **LFO** controls the speed of the modulation LFOs. | |||
* **Fc** controls the average frequency of the multiple filters. | |||
* **Q** controls the sharpness of the filters. | |||
* **Depth** controls how much of the modulation LFOs are applied to the filters. | |||
## How Growler works | |||
 | |||
There are four **bandpass filters**, roughly tuned to some typical vocal formant frequencies: 522, 1340, 2570, and 3700 Hz. The filters are run in parallel, with their outputs summed together. | |||
The first three filter frequencies are modulated by an LFO comprised of **4 triangle wave LFOs** running at different frequencies. They are summed together in various combinations to drive each of the filters. | |||
Each **CV input stage** is the same: a knob that supplies a fixed offset and a CV input that is processed by an attenuverter. The processed CV is added to the knob voltage. See below for more on [Attenuverters](#atten) and [CV ranges](#cv). | |||
The **LFO** Rate control shifts the speed of all 4 LFOs while maintaining the ratio of their frequencies. | |||
The **Fc** control moves the frequencies of the first three filters, but not by equal amounts. The lowest filter moves at 1V/Oct, but the middle two move less. The top filter is fixed at 3700 Hz. | |||
The **Q** control does just what it says - controls the Q (resonance) of the filters. | |||
The **Modulation Depth** controls how much of the summed LFOs get to each filter. Again, the lower filters move farther, and the top filter is fixed. | |||
The smaller knobs next to the main knobs are **attenuverters**, which scale control voltages. For more on attenuverters, [see below](#atten) | |||
There are three LFO outputs next to the blinking LFOs. These may be used to modulate other modules, or as semi-random voltage sources. | |||
**Bass boost** switch. When it’s in the up position (on) there should be more bass. This is done by switching some or all of the filters from bandpass to lowpass. | |||
LFO **Matrix** switch. This is the unlabeled switch in the LFO section. When it’s down (default position) the LFOs are closely correlated. In the middle we try to make them a little bit more independent. When it’s in the up position the LFOs will often go in different directions. |
@@ -0,0 +1,17 @@ | |||
# LFN Low Frequency Noise Generator <a name="lfn"></a> | |||
 | |||
LFN stands for Low Frequency Noise. Technically it is a white noise generator run through a graphic equalizer at extremely low frequencies. People may find it easier to think of it as a random voltage source with unique control over the output. | |||
The top knob, which is unlabeled, sets the "base frequency" of LFN. | |||
The five other knobs, and the CV inputs beside them, control the gain of the graphic equalizers sections. Beside each EQ gain knob is a label indicating what frequency range that knob controls. | |||
For example, it the base frequency is 1.0, the EQ sections will be at 1Hz, 2Hz, 4Hz, 8Hz, and 16Hz. If the base frequency is 0.2, The EQ sections will be at 0.2Hz, 0.4Hz, 0.8Hz, 1.6Hz, and 3.2Hz. | |||
But instead of thinking about frequencies like 1Hz, which are a little difficult to imagine, think of the knobs as mixing very slow random voltages, with less slow ones. For example if LFN is driving a pitch quantizer into a VCO, turn all the knobs down to zero except the first, lowest, one. This will make a series of pitches slowly rising and falling. Then bring in a little of the faster channels. The pitch will still be slowly rising and falling, but will also quickly move up and down by smaller steps. | |||
A good way to learn what makes LFN tick is to set it slow and watch it on the scope. At the same time run it straight into a VCO. Experiment with different mixes of the slow knobs and the fast ones. | |||
As you would expect from Squinky Labs, the CPU usage of LFN is very low. In fact it is one of our leanest modules yet. So feel free to use as many instances as you like. |
@@ -0,0 +1,19 @@ | |||
# Release notes for Squinky Labs modules | |||
## 0.6.9 | |||
Introduced three new modules: EV3, Gray Code, and Shaper. | |||
Added a trim control for external gain CV in Chebyshev. Previously saved patches may require that the gain trim be increased. | |||
Minor graphic tweaks to module panels. | |||
## 0.6.8 | |||
Introduced Chebyshev waveshaper VCO. | |||
Introduced release notes. | |||
Lowered the distortion in sin oscillator that is used in all modules. | |||
Re-ordered and re-worded module names in the browser. |
@@ -0,0 +1,105 @@ | |||
# Shaper precision waveshaper | |||
 | |||
## About Shaper | |||
Shaper is a waveshaper offering many different shape options. Some of these shapes are commonly found in other wave shapers, and some are unique to Shaper. It can be used to modify the waveforms from a VCO, or to add distortion to some other sound. And, as usual, the creative user may use it to process control voltages, or other "left field" uses. | |||
A unique feature of Shaper is that it has very little aliasing, whereas most we have seen have a lot of aliasing. The other special thing about Shaper is that it has a few shapes that are good for "soft overdrive". | |||
Although there are many creative ways to use a wave shaper, the two most common are as a mangled waveform shaper, and as a distortion/overdrive effect. | |||
In the first use case, the waveshaper is often connected directly to the output of a VCO. This gives a large number of different sounds from the VCO. In this use, typically extreme settings are used, with folding being a classic example. | |||
In the second use case, as a distortion/overdrive effect, often less extreme settings are used. For example, it would be unusual to run a recording of singing through a wavefolder, but a gentle overdrive is pretty common. | |||
The switch with the labels 16X, 4X, and 1X controls the amount of oversampling. This is how Shaper keeps aliasing under control. Waveshapers by their nature generate a lot of harmonics at high frequencies, and these tend to “fold back” into the audio range as aliasing. Oversampling reduces this effect by doing all the processing at a higher sample rate, then removing the frequencies that are too high, and reducing the sample rate back down. The more oversampling, the less aliasing. | |||
At 16X, Shaper is oversampling by a factor of 16. So for a 44,100 sampling rate, Shaper would be working at 705kHz! This is the oversampling rate used by Fundamental VCO-1 and Functional VCO-1. At this setting it is very difficult to hear or measure any aliasing at all, although it is present in tiny amounts. | |||
In general we recommend 16X, but there are several reasons you might want to set it lower. Firstly, you may actually want aliasing. When using Shaper as an extreme mangler the extra grit and digital nasties may fit perfectly. The other reason is CPU usage. Although Shaper at X16 does not use a large amount of CPU, it uses a lot less at 4X or 1X. It’s pretty much proportional to the setting. At 16X Shaper does 16 times as much work as at 1X. | |||
If you are using one of the gentler settings of Shaper, 4X may have completely inaudible alisasing also. With some settings, however, we can measure significant aliasing at 4X. If you have plenty of CPU, just leave it at 16X or 1X. But if you are running out of CPU 4X can be a very workable and smooth sounding alternative. | |||
Aside from the Oversampling selector, there are three controls: | |||
*Gain* – boosts the input signal, which tends to cause more shaping, distorting, and mangling. Controlled by the gain knob, and the gain CV. There is an attenuator on the gain CV. | |||
*Offset* – shifts the signal before it hits the waveshaper. In general that will increase the level of even harmonics in the output. Many of the shapes will output no even harmonics at all if the offset is zero. Like the gain, the offset is controlled by a knob, and a CV with attenuator. | |||
The offset is bidirectional, so there is no offset when the knob is straight up and the CV is zero. | |||
*Shape* – this is the big unlabeled button. It selects from the different shapes that Shaper can produce. The name of the selected shape is to the right of the knob. | |||
## Some notes on the different shapes | |||
### Smooth | |||
 | |||
Smooth is inspired by the asymmetrical distortion curve of a vacuum tube triode. It is by no means a model of a tube at all, but the shape is similar. | |||
Smooth uses gentle curves, and wants to be used more as a distortion or thickener that a full on mangler. | |||
With the Smooth setting, the offset control doesn’t actually control the offset; it controls the amount of asymmetry in the output. So, like a normal offset it will bring in even harmonics, but the way the even harmonics come in at different levels is unique. | |||
Where normally there are the least even harmonics when the knob is in the middle, with Smooth there are no even harmonics when it’s all the way down. | |||
### Clip | |||
 | |||
Classic hard clipping. With the gain high the output will be a square wave. | |||
Clip is not very useful for shaping a VCO output, since most VCOs already put out a square wave. But it is useful for generating a lot of distortion. It is similar to some guitar fuzz-boxes. | |||
### Emitter Coupled | |||
 | |||
Models the saturation of an emitter coupled pair amplifier input, as was commonly found in the classic 3080 “OTA” chip that was used in many analog synthesizers and some phase shifters. Even when driven hard, Emitter Coupled will not distort as much as some of the other shapes, and definitely won’t mangle a sound. | |||
### Full Wave | |||
 | |||
A very simple shape, but one that has some unique characteristics. This is just the absolute value function we learned about in school. Its unique characteristic is that while it generates a lot of distortion harmonics, it does not compress the dynamic range of music played through it. | |||
It is called Full Wave because this is an idealized version of the shape that will come out of a full wave rectifier circuit. | |||
While the full wave shape is usually too harsh and "buzzy" to use as an overdrive/distortion, it does make a nice mangler. | |||
### Half Wave | |||
 | |||
Has much in common with the full wave shape, but its radical asymmetry guarantees that a lot of even harmonics will be in the output. | |||
Unlike most of the shapes, both of the rectifier shapes have the same harmonic content regardless of the gain setting. So here it only functions as a volume control. | |||
### Fold | |||
 | |||
The "wavefolder" is a legendary and classic synthesizer module. Both Buchla and Serge offer wavefolder modules, and they were (and remain) very popular. The wavefolder we have implemented in Shaper is not an attempt at an emulation of those classics, but is a mathematically simple folding of the input. | |||
One thing all wavefolders have in common is that the sound changes quite a bit as the gain increases, sounding something like a filter sweep, or even more like the sweep of a synched VCO. So one of the first things to try is modulating the gain input with an envelope or other modulation source. | |||
One thing our wavefolder has in common with the analog classics is that it has little or no aliasing. Wavefolding generates a huge amount of high harmonics, so a digital implementation that does not deal with the aliasing is going to sound different from Shaper (or an analog module). | |||
That said, musicians can undoubtedly find uses for the fully aliased version. We encourage you to try at 16X and at 1X. | |||
### Fold 2 | |||
 | |||
Whereas “Fold” is a standard wavefolder, Fold2 is a little bit different. It is asymmetric no matter what offset is fed into it. It also generates more high harmonics than any other shape. | |||
### Crush | |||
 | |||
Crush simulates bit reduction by using a continuous voltage quantizer. As the gain is turned up it will pass 16 bits, down to 8 bits, and finally one bit. | |||
## More Info | |||
We have an informational article that talks more about aliasing. It goes into some specifics about Shaper, but also has some useful general information. | |||
[Aliasing Story](./aliasing.md). |
@@ -0,0 +1,46 @@ | |||
# Booty Shifter frequency shifter <a name="shifter"></a> | |||
**Booty Shifter** is a frequency shifter inspired by the Moog/Bode frequency shifter module. | |||
 | |||
The name "Booty Shifter" is a nod to the classic analog module, as well as to a black cat named Booty. | |||
Booty Shifter will take an audio input and shift the frequencies up or down. This is not like a pitch shift where harmonics will remain in tune; it is an absolute frequency shift in Hz, so in general **harmonics will go way out of tune.** It is similar to a ring-modulator, but less extreme and more versatile. | |||
## Getting good sounds from Booty Shifter | |||
Feed in music and shift the frequency a good amount. | |||
Feed in **speech or radio** and shift it. | |||
Feed the CV from a **sequencer** to sequence the mayhem. | |||
Shift **drums** up or down a little bit to re-tune them without the usual pitch-shifting artifacts. | |||
Small shifts in conjunction with delays can make a chorus-like effect to thicken music. | |||
## Inputs and outputs | |||
* **IN** is the audio input. | |||
* **CV** is the pitch shift control voltage. -5V will give minimum shift, +5 will give maximum. | |||
* **DN** is the down-shifted output. | |||
* **UP** is the up-shifted output. | |||
## Controls | |||
**RANGE** sets the total shift range in Hz. For example, the 50 Hz setting means that the minimum shift is 50 Hz down, and the maximum is 50 Hz up. | |||
Range value **Exp is different**. Here minimum shift is 2 Hz, maximum is 2 kHz, with an exponential response. As of version 0.6.2 the response is an accurate 1 Volt per Octave. | |||
Shift **AMT** is added to the control voltage, with a range of -5..5. | |||
## Oddities and limitations | |||
If you shift the frequency up too far, it will alias. There is no anti-aliasing, so if the highest input frequency + shift amount > sample_rate / 2, you will get aliasing. Of course the Bode analog original did not alias. | |||
If you shift the input down a lot, frequencies will go **below zero and wrap around**. Taken far enough this will completely **reverse the spectrum** of the input. This was a prized feature of the Bode original. | |||
As you shift the input down, you may start to generate a lot of subsonic energy. A **High Pass filter** may clean this up. | |||
The down shift **frequency fold-over**, while true to the original, does cause problems when trying to pitch drum tracks down a lot. High pass filtering the input before it is down-shifted can control this. |
@@ -0,0 +1,72 @@ | |||
# Notes about the creation of Functional VCO-1 | |||
## About the Original | |||
Fundamental VCO-1 is a very high quality, excellent sounding VCO. It does exactly what it claims to do with very little digital artifacts. VCO-1 is a very popular module, but it does use a lot of CPU. For this reason it seemed like a good candidate for a CPU diet. VCV users continue to complain about popping and clicking with large patches, so we hope improvements to this popular module will help. | |||
Fundamental VCO-1 uses 16X oversampling to generate standard waveforms, in both "analog" and "digital" versions. The oversampling keeps the aliasing low, allows hard and soft sync with low aliasing, and suppresses aliasing from audio rate modulation. | |||
As such, Fundamental VCO-1 is a very good substitute for an analog VCO. The only down-side is that the 16X oversampling increases the CPU usage dramatically. | |||
In revamping this VCO, we wanted to try as much as possible to preserve the sound exactly like the original. We did not add any new features, or try to make anything "better". And when we put the code on a diet we did not want to lower the sound quality of this workhorse module. | |||
## Initial Measurements | |||
It is difficult to accurately measure the CPU usage of a VCV module. Since version 0.6 there have been CPU meters which are useful for getting an overall picture of CPU usage; but the CPU meters do not enable stable, accurate, and repeatable measurements. | |||
So we more or less run the plugins in an isolated test framework. This lets us get precise measurements. The down side is that the isolated system is different from running in VCV, and the numbers won’t correlate exactly. | |||
We use an arbitrary scale for our measurements, where "100" means that the plugin under test seems to be using 1% of the available CPU on one core of our quite old Intel Core i5 Windows-7 computer. | |||
Here are the initial measurements we took before any optimizations were done, along with some Squinky Labs modules for reference: | |||
* Fundamental VCO-1, all outputs patched, digital: 798 | |||
* Fundamental VCO-1, saw only, digital: 489 | |||
* Fundamental VCO-1, saw only, analog: 270 | |||
* SL Formants: 84.1 | |||
* SL Growler: 50.9 | |||
* SL Chopper: 14.9 | |||
* SL Booty Shifter: 11.2 | |||
* SL Colors: 11.6 | |||
Fundamental VCO-1 uses a *lot* of CPU. Since it is so heavy in its CPU usage, we thought it would be easy to make it much faster. But it was not as easy as we had hoped. | |||
## General approach to optimization | |||
Every theory must be validated by experiment. So we look at the code, formulate a theory, throw together a simplified implementation of the theory, and compare before and after measurements. | |||
If the CPU usage goes down a lot, the experiment is a success, and we try to make a full implementation that preserves the drop in CPU usage without compromising the quality. | |||
Then repeat this process over and over until done. | |||
## What we did to Fundamental VCO-1 | |||
VCO-1 already had some optimization. Although the waveform generation runs for all waveforms all the time, the decimation filters are only run for outputs that are connected. The decimation filters are the same ones used by the VCV Rack audio engine, so we assume they are linear phase FIR filters, as is customary for high quality sample rate conversion. | |||
But, since this is an oversampling VCO, the waveform generation is running at 16X sample rate. Any extra work in this "inner loop" is going to be magnified by 16. | |||
That said, our experiments showed few surprises. | |||
* Since cosf() is called all the time in the 16X waveform generation loop, it was a no-brainer to replace it with the same sin lookup table we use in most of our modules. Likewise, we made sure that the cosf lookup is only called if the SIN output is connected. | |||
* The powf() call is also slow and it was worthwhile getting rid of the powf call, although the gain was not enormous since powf is only called once per sample. | |||
* We refactored the inner loop to make sure that the waveform generation is only done for waveforms whose output is patched. | |||
* The decimation filters use a lot of CPU, so we replaced the stock ones with simple 6-pole butterworth lowpass filters. | |||
* It would of course save CPU to reduce the oversampling rate, but we did not want to decrease the quality. | |||
Again, most of the software required was already in the code-base for our other modules. The one thing that was difficult here was devising test software to measure the aliasing. We wanted to be sure that our faster decimation filters were not increasing the level of aliasing. While this new alias test is not perfect, it did give us confidence that we were not increasing the aliasing with our substituted filters. | |||
## Results | |||
Before and after: | |||
* Fundamental VCO-1, all outputs patched, digital: 798 -> 187.8 (X4.2) | |||
* Fundamental VCO-1, saw only, digital: 489 -> 83.7 (X5.8) | |||
* Fundamental VCO-1, saw only, analog: 270 -> 83 (X2.3) | |||
## Addendum for EV3 VCO | |||
When we looked at EvenVCO, we found many of the same issues as we found in Fundamental VCO-1. The same extremely slow trig and exponential functions that we replaced with lookup tables. Work was being done for waveforms that weren't patched. | |||
But some other things were different. Since EvenVCO is a MinBLEP VCO, it does not need anti-alias filters (as such). The existing MinBLEP code is quite efficient. But in the VCO we did achieve significant speedup by getting rid of the one routine that generates all the waves, and instead have dedicated routines for each waveform. This eliminated a lot of conditional branching. | |||
While EvenVCO is already quite efficient, it was still worthwhile to make it faster. We believe out triple version uses about the same CPU as a single instance of EvenVCO. |
@@ -49,7 +49,6 @@ bool FFT::forward(FFTDataCpx* out, const FFTDataReal& in) | |||
return true; | |||
} | |||
bool FFT::inverse(FFTDataReal* out, const FFTDataCpx& in) | |||
{ | |||
if (out->buffer.size() != in.buffer.size()) { | |||
@@ -83,16 +82,16 @@ bool FFT::inverse(FFTDataReal* out, const FFTDataCpx& in) | |||
return true; | |||
} | |||
int FFT::freqToBin(float freq, float sampleRate, int numBins) | |||
int FFT::freqToBin(double freq, double sampleRate, int numBins) | |||
{ | |||
assert(freq <= (sampleRate / 2)); | |||
// bin(numBins) <> sr / 2; | |||
return (int)((freq / sampleRate)*(numBins * 2)); | |||
return (int)((freq / sampleRate)*(numBins)); | |||
} | |||
float FFT::bin2Freq(int bin, float sampleRate, int numBins) | |||
double FFT::bin2Freq(int bin, double sampleRate, int numBins) | |||
{ | |||
return sampleRate * float(bin) / (float(numBins) * 2); | |||
return sampleRate * double(bin) / double(numBins); | |||
} | |||
static float randomPhase() | |||
@@ -120,8 +119,8 @@ static void makeNegSlope(FFTDataCpx* output, const ColoredNoiseSpec& spec) | |||
static float k = -spec.slope * log2(lowFreqCorner); | |||
for (int i = bin40 + 1; i < numBins; ++i) { | |||
if (i < numBins / 2) { | |||
const float f = FFT::bin2Freq(i, spec.sampleRate, numBins); | |||
const float gainDb = std::log2(f) * spec.slope + k; | |||
const double f = FFT::bin2Freq(i, spec.sampleRate, numBins); | |||
const double gainDb = std::log2(f) * spec.slope + k; | |||
const float gain = float(AudioMath::gainFromDb(gainDb)); | |||
output->set(i, std::polar(gain, randomPhase())); | |||
} else { | |||
@@ -143,8 +142,8 @@ static void makePosSlope(FFTDataCpx* output, const ColoredNoiseSpec& spec) | |||
static float k = -spec.slope * log2(spec.highFreqCorner); | |||
for (int i = binHigh - 1; i > 0; --i) { | |||
if (i < numBins / 2) { | |||
const float f = FFT::bin2Freq(i, spec.sampleRate, numBins); | |||
const float gainDb = std::log2(f) * spec.slope + k; | |||
const double f = FFT::bin2Freq(i, spec.sampleRate, numBins); | |||
const double gainDb = std::log2(f) * spec.slope + k; | |||
const float gain = float(AudioMath::gainFromDb(gainDb)); | |||
gainMax = std::max(gain, gainMax); | |||
output->set(i, std::polar(gain, randomPhase())); | |||
@@ -189,10 +188,11 @@ static float getPeak(const FFTDataReal& data) | |||
return peak; | |||
} | |||
void FFT::normalize(FFTDataReal* data) | |||
void FFT::normalize(FFTDataReal* data, float maxValue) | |||
{ | |||
assert(maxValue > 0); | |||
const float peak = getPeak(*data); | |||
const float correction = 1.0f / peak; | |||
const float correction = maxValue / peak; | |||
for (int i = 0; i < data->size(); ++i) { | |||
float x = data->get(i); | |||
x *= correction; | |||
@@ -1,8 +1,9 @@ | |||
#pragma once | |||
class FFTDataCpx; | |||
class FFTDataReal; | |||
//class FFTDataCpx; | |||
//class FFTDataReal; | |||
#include "FFTData.h" | |||
class ColoredNoiseSpec | |||
{ | |||
@@ -33,7 +34,7 @@ public: | |||
*/ | |||
static void makeNoiseSpectrum(FFTDataCpx* output, const ColoredNoiseSpec&); | |||
static void normalize(FFTDataReal*); | |||
static float bin2Freq(int bin, float sampleRate, int numBins); | |||
static int freqToBin(float freq, float sampleRate, int numBins); | |||
static void normalize(FFTDataReal*, float maxValue); | |||
static double bin2Freq(int bin, double sampleRate, int numBins); | |||
static int freqToBin(double freq, double sampleRate, int numBins); | |||
}; |
@@ -15,12 +15,12 @@ NoiseMessage* FFTCrossFader::step(float* out) | |||
// curPlayOffset1 is the index into buffer 1, but also the crossfade index | |||
assert(curPlayOffset[1] < crossfadeSamples); | |||
float buffer0Value = dataFrames[0]->dataBuffer->get(curPlayOffset[0]) * | |||
(crossfadeSamples - (curPlayOffset[1]+1)); | |||
float buffer0Value = dataFrames[0]->dataBuffer->get(curPlayOffset[0]) * | |||
(crossfadeSamples - (curPlayOffset[1] + 1)); | |||
float buffer1Value = dataFrames[1]->dataBuffer->get(curPlayOffset[1]) * curPlayOffset[1]; | |||
// TODO: do we need to pre-divide | |||
*out = (buffer1Value + buffer0Value) / (crossfadeSamples-1); | |||
*out = (buffer1Value + buffer0Value) / (crossfadeSamples - 1); | |||
if (makeupGain) { | |||
float gain = std::sqrt(2.0f) - 1; | |||
float offset = float(curPlayOffset[1]); | |||
@@ -70,8 +70,7 @@ NoiseMessage * FFTCrossFader::acceptData(NoiseMessage* msg) | |||
if (dataFrames[0] == nullptr) { | |||
dataFrames[0] = msg; | |||
curPlayOffset[0] = 0; | |||
} | |||
else if (dataFrames[1] == nullptr) { | |||
} else if (dataFrames[1] == nullptr) { | |||
dataFrames[1] = msg; | |||
curPlayOffset[1] = 0; | |||
} else { | |||
@@ -1,67 +1,10 @@ | |||
#include "FFTData.h" | |||
#include "kiss_fft.h" | |||
#include "kiss_fftr.h" | |||
#include <assert.h> | |||
//template <typename T> | |||
int FFTDataCpx::_count = 0; | |||
FFTDataCpx::FFTDataCpx(int numBins) : | |||
buffer(numBins) | |||
{ | |||
++_count; | |||
} | |||
template <> | |||
int FFTData<cpx>::_count = 0; | |||
FFTDataCpx::~FFTDataCpx() | |||
{ | |||
// We need to manually delete the cfg, since only "we" know | |||
// what type it is. | |||
if (kiss_cfg) { | |||
free(kiss_cfg); | |||
} | |||
--_count; | |||
} | |||
cpx FFTDataCpx::get(int index) const | |||
{ | |||
assert(index < (int)buffer.size()); | |||
return buffer[index]; | |||
} | |||
void FFTDataCpx::set(int index, cpx value) | |||
{ | |||
assert(index < (int)buffer.size()); | |||
buffer[index] = value; | |||
} | |||
/******************************************************************/ | |||
int FFTDataReal::_count = 0; | |||
FFTDataReal::FFTDataReal(int numBins) : | |||
buffer(numBins) | |||
{ | |||
++_count; | |||
} | |||
FFTDataReal::~FFTDataReal() | |||
{ | |||
// We need to manually delete the cfg, since only "we" know | |||
// what type it is. | |||
if (kiss_cfg) { | |||
free(kiss_cfg); | |||
} | |||
--_count; | |||
} | |||
float FFTDataReal::get(int index) const | |||
{ | |||
assert(index < (int)buffer.size()); | |||
return buffer[index]; | |||
} | |||
void FFTDataReal::set(int index, float value) | |||
{ | |||
assert(index < (int)buffer.size()); | |||
buffer[index] = value; | |||
} | |||
template <> | |||
int FFTDataReal::_count = 0; |
@@ -2,6 +2,7 @@ | |||
#include <complex> | |||
#include <vector> | |||
#include <assert.h> | |||
class FFT; | |||
@@ -13,22 +14,34 @@ class FFT; | |||
*/ | |||
using cpx = std::complex<float>; | |||
class FFTDataCpx | |||
template <typename T> | |||
class FFTData | |||
{ | |||
public: | |||
friend FFT; | |||
FFTDataCpx(int numBins); | |||
~FFTDataCpx(); | |||
cpx get(int bin) const; | |||
void set(int bin, cpx value); | |||
FFTData(int numBins); | |||
~FFTData(); | |||
T get(int bin) const; | |||
void set(int bin, T value); | |||
int size() const | |||
{ | |||
return (int) buffer.size(); | |||
} | |||
T * data() | |||
{ | |||
return buffer.data(); | |||
} | |||
float getAbs(int bin) const | |||
{ | |||
return std::abs(buffer[bin]); | |||
} | |||
static int _count; | |||
private: | |||
std::vector<cpx> buffer; | |||
std::vector<T> buffer; | |||
/** | |||
* we store this without type so that clients don't need | |||
@@ -40,31 +53,39 @@ private: | |||
mutable void * kiss_cfg = 0; | |||
}; | |||
/** | |||
* Holds an fft frame of real data. | |||
*/ | |||
class FFTDataReal | |||
using FFTDataReal = FFTData<float>; | |||
using FFTDataCpx = FFTData<cpx>; | |||
//int FFTDataCpx::_count = 0; | |||
template <typename T> | |||
inline FFTData<T>::FFTData(int numBins) : | |||
buffer(numBins) | |||
{ | |||
public: | |||
friend FFT; | |||
FFTDataReal(int numBins); | |||
~FFTDataReal(); | |||
float get(int numBin) const; | |||
void set(int numBin, float value); | |||
int size() const | |||
{ | |||
return (int) buffer.size(); | |||
++_count; | |||
} | |||
template <typename T> | |||
inline FFTData<T>::~FFTData() | |||
{ | |||
// We need to manually delete the cfg, since only "we" know | |||
// what type it is. | |||
if (kiss_cfg) { | |||
free(kiss_cfg); | |||
} | |||
static int _count; | |||
private: | |||
std::vector<float> buffer; | |||
--_count; | |||
} | |||
/** | |||
* we store this without type so that clients don't need | |||
* to pull in the kiss_fft headers. It's mutable so it can | |||
* be lazy created by FFT functions. | |||
* Note that the cfg has a "direction" baked into it. For | |||
* now we assume that all FFT with real input will be forward FFTs. | |||
*/ | |||
mutable void * kiss_cfg = 0; | |||
}; | |||
template <typename T> | |||
inline T FFTData<T>::get(int index) const | |||
{ | |||
assert(index < (int) buffer.size() && index >= 0); | |||
return buffer[index]; | |||
} | |||
template <typename T> | |||
inline void FFTData<T>::set(int index, T value) | |||
{ | |||
assert(index < (int) buffer.size() && index >= 0); | |||
buffer[index] = value; | |||
} |
@@ -34,6 +34,13 @@ public: | |||
T A2(int stage) const; | |||
void dump() const; | |||
/** | |||
* Direct access for the rare client that | |||
* wants to see taps as a straight array. | |||
*/ | |||
T getAtIndex(int index); | |||
void setAtIndex(T x, int index); | |||
private: | |||
T _taps[5 * N]; | |||
}; | |||
@@ -88,50 +95,64 @@ inline T& BiquadParams<T, N>::B2(int stage) | |||
} | |||
template <typename T, int N> | |||
T BiquadParams<T, N>::A1(int stage) const | |||
inline T BiquadParams<T, N>::A1(int stage) const | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5 + 3]; | |||
} | |||
template <typename T, int N> | |||
T BiquadParams<T, N>::A2(int stage) const | |||
inline T BiquadParams<T, N>::A2(int stage) const | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5 + 4]; | |||
} | |||
template <typename T, int N> | |||
T BiquadParams<T, N>::B0(int stage) const | |||
inline T BiquadParams<T, N>::B0(int stage) const | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5]; | |||
} | |||
template <typename T, int N> | |||
T BiquadParams<T, N>::B1(int stage) const | |||
inline T BiquadParams<T, N>::B1(int stage) const | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5 + 1]; | |||
} | |||
template <typename T, int N> | |||
T BiquadParams<T, N>::B2(int stage) const | |||
inline T BiquadParams<T, N>::B2(int stage) const | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5 + 2]; | |||
} | |||
template <typename T, int N> | |||
T& BiquadParams<T, N>::A1(int stage) | |||
inline T& BiquadParams<T, N>::A1(int stage) | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5 + 3]; | |||
} | |||
template <typename T, int N> | |||
T& BiquadParams<T, N>::A2(int stage) | |||
inline T& BiquadParams<T, N>::A2(int stage) | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5 + 4]; | |||
} | |||
template <typename T, int N> | |||
inline T BiquadParams<T, N>::getAtIndex(int index) | |||
{ | |||
assert(index < N * 5); | |||
return _taps[index]; | |||
} | |||
template <typename T, int N> | |||
inline void BiquadParams<T, N>::setAtIndex(T x, int index) | |||
{ | |||
assert(index < N * 5); | |||
_taps[index] = x; | |||
} |
@@ -1,6 +1,8 @@ | |||
#pragma once | |||
extern int _numBiquads; | |||
/** | |||
* Structure to hold the mutable state of a biquad filter: in this case the delay memory. | |||
* | |||
@@ -8,6 +8,37 @@ | |||
#include "BiquadFilter.h" | |||
#include <memory> | |||
template <typename T> | |||
void ButterworthFilterDesigner<T>::designEightPoleLowpass(BiquadParams<T, 4>& outParams, T frequency) | |||
{ | |||
using Filter = Dsp::ButterLowPass<8, 1>; | |||
std::unique_ptr<Filter> lp(new Filter()); // std::make_unique is not until C++14 | |||
lp->SetupAs(frequency); | |||
assert(lp->GetStageCount() == 4); | |||
BiquadFilter<T>::fillFromStages(outParams, lp->Stages(), lp->GetStageCount()); | |||
} | |||
template <typename T> | |||
void ButterworthFilterDesigner<T>::designSixPoleLowpass(BiquadParams<T, 3>& outParams, T frequency) | |||
{ | |||
using Filter = Dsp::ButterLowPass<6, 1>; | |||
std::unique_ptr<Filter> lp6(new Filter()); // std::make_unique is not until C++14 | |||
lp6->SetupAs(frequency); | |||
assert(lp6->GetStageCount() == 3); | |||
BiquadFilter<T>::fillFromStages(outParams, lp6->Stages(), lp6->GetStageCount()); | |||
} | |||
template <typename T> | |||
void ButterworthFilterDesigner<T>::designFivePoleLowpass(BiquadParams<T, 3>& outParams, T frequency) | |||
{ | |||
using Filter = Dsp::ButterLowPass<5, 1>; | |||
std::unique_ptr<Filter> lp5(new Filter()); // std::make_unique is not until C++14 | |||
lp5->SetupAs(frequency); | |||
assert(lp5->GetStageCount() == 3); | |||
BiquadFilter<T>::fillFromStages(outParams, lp5->Stages(), lp5->GetStageCount()); | |||
} | |||
template <typename T> | |||
void ButterworthFilterDesigner<T>::designThreePoleLowpass(BiquadParams<T, 2>& outParams, T frequency) | |||
{ | |||
@@ -18,6 +49,25 @@ void ButterworthFilterDesigner<T>::designThreePoleLowpass(BiquadParams<T, 2>& ou | |||
BiquadFilter<T>::fillFromStages(outParams, lp3->Stages(), lp3->GetStageCount()); | |||
} | |||
template <typename T> | |||
void ButterworthFilterDesigner<T>::designFourPoleLowpass(BiquadParams<T, 2>& outParams, T frequency) | |||
{ | |||
using Filter = Dsp::ButterLowPass<4, 1>; | |||
std::unique_ptr<Filter> lp4(new Filter()); // std::make_unique is not until C++14 | |||
lp4->SetupAs(frequency); | |||
assert(lp4->GetStageCount() == 2); | |||
BiquadFilter<T>::fillFromStages(outParams, lp4->Stages(), lp4->GetStageCount()); | |||
} | |||
template <typename T> | |||
void ButterworthFilterDesigner<T>::designFourPoleHighpass(BiquadParams<T, 2>& outParams, T frequency) | |||
{ | |||
using Filter = Dsp::ButterHighPass<4, 1>; | |||
std::unique_ptr<Filter> lp4(new Filter()); // std::make_unique is not until C++14 | |||
lp4->SetupAs(frequency); | |||
assert(lp4->GetStageCount() == 2); | |||
BiquadFilter<T>::fillFromStages(outParams, lp4->Stages(), lp4->GetStageCount()); | |||
} | |||
template <typename T> | |||
void ButterworthFilterDesigner<T>::designTwoPoleLowpass(BiquadParams<T, 1>& outParams, T frequency) | |||
{ | |||
@@ -28,6 +78,32 @@ void ButterworthFilterDesigner<T>::designTwoPoleLowpass(BiquadParams<T, 1>& outP | |||
BiquadFilter<T>::fillFromStages(outParams, lp2->Stages(), lp2->GetStageCount()); | |||
} | |||
template <typename T> | |||
void ButterworthFilterDesigner<T>::designSixPoleElliptic(BiquadParams<T, 3>& outParams, T frequency, T rippleDb, T stopbandAttenDb) | |||
{ | |||
assert(stopbandAttenDb > 0); | |||
using Filter = Dsp::EllipticLowPass<6, 1>; | |||
std::unique_ptr<Filter> ellip6(new Filter()); | |||
// void SetupAs( CalcT cutoffFreq, CalcT passRippleDb, CalcT rollOff ) | |||
ellip6->SetupAs(frequency, rippleDb, stopbandAttenDb); | |||
assert(ellip6->GetStageCount() == 3); | |||
BiquadFilter<T>::fillFromStages(outParams, ellip6->Stages(), ellip6->GetStageCount()); | |||
} | |||
template <typename T> | |||
void ButterworthFilterDesigner<T>::designEightPoleElliptic(BiquadParams<T, 4>& outParams, T frequency, T rippleDb, T stopbandAttenDb) | |||
{ | |||
assert(stopbandAttenDb > 0); | |||
using Filter = Dsp::EllipticLowPass<8, 1>; | |||
std::unique_ptr<Filter> ellip8(new Filter()); | |||
// void SetupAs( CalcT cutoffFreq, CalcT passRippleDb, CalcT rollOff ) | |||
ellip8->SetupAs(frequency, rippleDb, stopbandAttenDb); | |||
assert(ellip8->GetStageCount() == 4); | |||
BiquadFilter<T>::fillFromStages(outParams, ellip8->Stages(), ellip8->GetStageCount()); | |||
} | |||
// Explicit instantiation, so we can put implementation into .cpp file | |||
// TODO: option to take out float version (if we don't need it) | |||
// Or put all in header | |||
@@ -9,6 +9,14 @@ class ButterworthFilterDesigner | |||
{ | |||
public: | |||
ButterworthFilterDesigner() = delete; // we are only static | |||
static void designEightPoleLowpass(BiquadParams<T, 4>& pOut, T frequency); | |||
static void designSixPoleLowpass(BiquadParams<T, 3>& pOut, T frequency); | |||
static void designThreePoleLowpass(BiquadParams<T, 2>& pOut, T frequency); | |||
static void designFourPoleLowpass(BiquadParams<T, 2>& pOut, T frequency); | |||
static void designFourPoleHighpass(BiquadParams<T, 2>& pOut, T frequency); | |||
static void designFivePoleLowpass(BiquadParams<T, 3>& pOut, T frequency); | |||
static void designTwoPoleLowpass(BiquadParams<T, 1>& pOut, T frequency); | |||
static void designSixPoleElliptic(BiquadParams<T, 3>& pOut, T frequency, T rippleDb, T stopbandAttenDb); | |||
static void designEightPoleElliptic(BiquadParams<T, 4>& pOut, T frequency, T rippleDb, T stopbandAttenDb); | |||
}; |
@@ -0,0 +1,41 @@ | |||
#pragma once | |||
#include "BiquadParams.h" | |||
#include "ButterworthFilterDesigner.h" | |||
#include "LookupTable.h" | |||
/** | |||
* Interpolating lookup for filter parameters | |||
*/ | |||
class ButterworthLookup4PHP | |||
{ | |||
public: | |||
ButterworthLookup4PHP(); | |||
void get(BiquadParams<float, 2>& params, float normalizedCutoff); | |||
private: | |||
static const int numTables = 10; | |||
LookupTableParams<float> tables[numTables]; // five params per two biquads | |||
}; | |||
inline ButterworthLookup4PHP::ButterworthLookup4PHP() | |||
{ | |||
const int numBins = 256; | |||
for (int index = 0; index < numTables; ++index) { | |||
LookupTable<float>::init(tables[index], numBins, 100.0f / 44100.0f, 2000 / 44100.0f, [index](double x) { | |||
// first design a filter at x hz | |||
BiquadParams<float, 2> params; | |||
ButterworthFilterDesigner<float>::designFourPoleHighpass(params, float(x)); | |||
// then save off tap 0; | |||
return params.getAtIndex(index); | |||
}); | |||
} | |||
} | |||
inline void ButterworthLookup4PHP::get(BiquadParams<float, 2>& params, float normalizedCutoff) | |||
{ | |||
for (int i = 0; i < numTables; ++i) { | |||
// const int stage = i < numTables / 2; | |||
float p = LookupTable<float>::lookup(tables[i], normalizedCutoff, true); | |||
params.setAtIndex(p, i); | |||
} | |||
} |
@@ -0,0 +1,134 @@ | |||
#pragma once | |||
#include "StateVariableFilter.h" | |||
// Unfinished single stage eq | |||
class GraphicEq | |||
{ | |||
public: | |||
GraphicEq(int stages, float bw); | |||
float run(float); | |||
void setGain(int stage, float g) | |||
{ | |||
gain[stage] = g; | |||
// printf("just set gain[%d] to %f\n", stage, g); | |||
} | |||
private: | |||
StateVariableFilterParams<float> params[6]; | |||
StateVariableFilterState<float> states[6]; | |||
float gain[6]; | |||
const int _stages; | |||
}; | |||
// todo: refactor | |||
inline GraphicEq::GraphicEq(int stages, float bw) : _stages(stages) | |||
{ | |||
assert(stages < 6); | |||
// .5, 1 stage is 78..128. 2stage 164 273 / | |||
// .8 67..148 / 63..314 / 72..456 | |||
const float baseFreq = 100.f / 44100.f; | |||
float freq = baseFreq; | |||
for (int i = 0; i < stages; ++i) { | |||
params[i].setMode(StateVariableFilterParams<float>::Mode::BandPass); | |||
params[i].setFreq(freq); | |||
params[i].setNormalizedBandwidth(bw); | |||
freq *= 2.f; | |||
gain[i] = 1; | |||
} | |||
} | |||
inline float GraphicEq::run(float input) | |||
{ | |||
// printf("run filter with "); | |||
float out = 0; | |||
for (int i = 0; i < _stages; ++i) { | |||
// printf("%f ", gain[i]); | |||
out += StateVariableFilter<float>::run(input, states[i], params[i]) * gain[i]; | |||
} | |||
// printf("\n"); | |||
return out; | |||
} | |||
/** | |||
* Two bandpass filters in series. | |||
*/ | |||
class TwoStageBandpass | |||
{ | |||
public: | |||
TwoStageBandpass(); | |||
float run(float); | |||
void setFreq(float); | |||
private: | |||
StateVariableFilterParams<float> params[2]; | |||
StateVariableFilterState<float> state[2]; | |||
}; | |||
inline TwoStageBandpass::TwoStageBandpass() | |||
{ | |||
for (int i = 0; i <= 1; ++i) { | |||
params[i].setMode(StateVariableFilterParams<float>::Mode::BandPass); | |||
params[i].setFreq(.1f); | |||
params[i].setNormalizedBandwidth(1); | |||
} | |||
} | |||
inline void TwoStageBandpass::setFreq(float freq) | |||
{ | |||
for (int i = 0; i <= 1; ++i) { | |||
params[i].setFreq(freq); | |||
} | |||
} | |||
inline float TwoStageBandpass::run(float input) | |||
{ | |||
auto y = StateVariableFilter<float>::run(input, state[0], params[0]); | |||
auto z = StateVariableFilter<float>::run(y, state[1], params[1]); | |||
return z; | |||
} | |||
/** | |||
* Octave EQ using dual bandpass sections | |||
* Currently hard-wired to 100 Hz. | |||
*/ | |||
template <int NumStages> | |||
class GraphicEq2 | |||
{ | |||
public: | |||
GraphicEq2() | |||
{ | |||
float freq = 100.0f / 44100.0f; | |||
for (int i = 0; i < NumStages; ++i) { | |||
filters[i].setFreq(freq); | |||
freq *= 2.0f; | |||
} | |||
} | |||
float run(float); | |||
void setGain(int stage, float g) | |||
{ | |||
assert(stage < NumStages); | |||
gain[stage] = g; | |||
} | |||
int getNumStages() | |||
{ | |||
return NumStages; | |||
} | |||
private: | |||
TwoStageBandpass filters[NumStages]; | |||
float gain[NumStages]; | |||
}; | |||
template <int NumStages> | |||
inline float GraphicEq2<NumStages>::run(float input) | |||
{ | |||
float out = 0; | |||
for (int i = 0; i < NumStages; ++i) { | |||
out += filters[i].run(input) * gain[i]; | |||
} | |||
; | |||
return out; | |||
} |
@@ -0,0 +1,53 @@ | |||
#pragma once | |||
#include <assert.h> | |||
#include "AudioMath.h" | |||
template <typename T> | |||
class LowpassFilterState | |||
{ | |||
public: | |||
T z=0; | |||
}; | |||
template <typename T> | |||
class LowpassFilterParams | |||
{ | |||
public: | |||
T k=0; | |||
T l=0; | |||
}; | |||
template <typename T> | |||
class LowpassFilter | |||
{ | |||
public: | |||
/** | |||
* fs is normalize frequency | |||
*/ | |||
static void setCutoff(LowpassFilterParams<T>& params, T fs); | |||
static T run(T input, LowpassFilterState<T>& state, const LowpassFilterParams<T>& params); | |||
}; | |||
template <typename T> | |||
inline void LowpassFilter<T>::setCutoff(LowpassFilterParams<T>& params, T fs) | |||
{ | |||
assert(fs > 00 && fs < .5); | |||
params.k = T(1.0 - (std::exp(-2.0 * AudioMath::Pi * fs))); | |||
params.l = T(1.0 - params.k); | |||
} | |||
/* | |||
void go_dbl(double x) | |||
{ | |||
_z = _z * _l + _k * x; | |||
} | |||
*/ | |||
template <typename T> | |||
inline T LowpassFilter<T>::run(T input, LowpassFilterState<T>& state, const LowpassFilterParams<T>& params) | |||
{ | |||
state.z = state.z * params.l + params.k * input; | |||
return state.z; | |||
} |
@@ -111,6 +111,7 @@ public: | |||
* units are 1 == sample rate | |||
*/ | |||
void setFreq(T f); | |||
void setFreqAccurate(T f); | |||
void setMode(Mode m) | |||
{ | |||
mode = m; | |||
@@ -145,6 +146,12 @@ inline void StateVariableFilterParams<T>::setFreq(T fc) | |||
fcGain = T(AudioMath::Pi) * T(2) * fc; | |||
} | |||
template <typename T> | |||
inline void StateVariableFilterParams<T>::setFreqAccurate(T fc) | |||
{ | |||
fcGain = T(2) * std::sin( T(AudioMath::Pi) * fc); | |||
} | |||
/*******************************************************************************************/ | |||
template <typename T> | |||
@@ -0,0 +1,588 @@ | |||
#pragma once | |||
// Need to make this compile in MS tools for unit tests | |||
#if defined(_MSC_VER) | |||
#define __attribute__(x) | |||
#pragma warning (push) | |||
#pragma warning ( disable: 4244 4267 ) | |||
#endif | |||
#ifndef _CLAMP | |||
#define _CLAMP | |||
namespace std { | |||
inline float clamp(float v, float lo, float hi) | |||
{ | |||
assert(lo < hi); | |||
#define sMIN(a,b) (((a)>(b))?(b):(a)) | |||
#define sMAX(a,b) (((a)>(b))?(a):(b)) | |||
//return std::min(hi, std::max(v, lo)); | |||
return sMIN(hi, sMAX(v, lo)); | |||
#undef sMIN | |||
#undef sMAX | |||
} | |||
} | |||
#endif | |||
//#include "math.hpp" | |||
#include "dsp/minblep.hpp" | |||
#include "dsp/filter.hpp" | |||
#include "AudioMath.h" | |||
#include "ObjectCache.h" | |||
#include <functional> | |||
// until c++17 | |||
#ifndef _CLAMP | |||
#define _CLAMP | |||
namespace std { | |||
inline float clamp(float v, float lo, float hi) | |||
{ | |||
assert(lo < hi); | |||
return std::min(hi, std::max(v, lo)); | |||
} | |||
} | |||
#endif | |||
/* VCO core using MinBLEP to reduce aliasing. | |||
* Originally based on Befaco EvenVCO | |||
*/ | |||
class MinBLEPVCO | |||
{ | |||
public: | |||
friend class TestMB; | |||
/** | |||
* ph is the "phase (-1..0)" | |||
*/ | |||
using SyncCallback = std::function<void(float p)>; | |||
MinBLEPVCO(); | |||
enum class Waveform | |||
{ | |||
Sin, Tri, Saw, Square, Even, END | |||
}; | |||
void step(); | |||
void setNormalizedFreq(float f, float st) | |||
{ | |||
normalizedFreq = std::clamp(f, 1e-6f, 0.5f); | |||
sampleTime = st; | |||
} | |||
void setWaveform(Waveform); | |||
float getOutput() const | |||
{ | |||
return output; | |||
} | |||
/** | |||
* Send the sync waveform to VCO. | |||
* usually called from outside. | |||
*/ | |||
void onMasterSync(float phase); | |||
void setSyncCallback(SyncCallback); | |||
void setPulseWidth(float); | |||
void setSyncEnabled(bool f) | |||
{ | |||
syncEnabled = f; | |||
} | |||
private: | |||
float output = 0; | |||
Waveform waveform = Waveform::Saw; | |||
float phase = 0.0; | |||
float normalizedFreq = 0; | |||
float sampleTime = 0; | |||
SyncCallback syncCallback = nullptr; | |||
float tri = 0; | |||
bool syncEnabled = false; | |||
bool gotSyncCallback = false; | |||
float syncCallbackCrossing = 0; | |||
/** | |||
* References to shared lookup tables. | |||
* Destructor will free them automatically. | |||
*/ | |||
std::shared_ptr<LookupTableParams<float>> sinLookup = {ObjectCache<float>::getSinLookup()}; | |||
/** Whether we are past the pulse width already */ | |||
bool halfPhase = false; | |||
int loopCounter = 0; // still used? | |||
float pulseWidth = .5; | |||
rack::MinBLEP<16> syncMinBLEP; | |||
rack::MinBLEP<16> aMinBLEP; | |||
rack::MinBLEP<16> bMinBLEP; | |||
bool aIsNext = false; | |||
rack::MinBLEP<16>* getNextMinBLEP(); | |||
/** | |||
* Waveform generation helper | |||
*/ | |||
void step_even(); | |||
void step_saw(); | |||
void step_sq(); | |||
void step_sin(); | |||
void step_tri(); | |||
/** | |||
* input = phase, 0..1 | |||
* output = sin(2pi * input) | |||
*/ | |||
float sineLook(float input) const; | |||
float evenLook(float input) const; | |||
std::string name; | |||
bool lastSq = false; | |||
bool isSqHigh() const; | |||
}; | |||
inline MinBLEPVCO::MinBLEPVCO() | |||
{ | |||
syncMinBLEP.minblep = rack::minblep_16_32; | |||
syncMinBLEP.oversample = 32; | |||
aMinBLEP.minblep = rack::minblep_16_32; | |||
aMinBLEP.oversample = 32; | |||
bMinBLEP.minblep = rack::minblep_16_32; | |||
bMinBLEP.oversample = 32; | |||
} | |||
inline rack::MinBLEP<16>* MinBLEPVCO::getNextMinBLEP() | |||
{ | |||
aIsNext = !aIsNext; | |||
return aIsNext ? &aMinBLEP : &bMinBLEP; | |||
} | |||
inline void MinBLEPVCO::setSyncCallback(SyncCallback cb) | |||
{ | |||
assert(!syncCallback); | |||
syncCallback = cb; | |||
} | |||
inline void MinBLEPVCO::setWaveform(Waveform wf) | |||
{ | |||
waveform = wf; | |||
} | |||
inline void MinBLEPVCO::setPulseWidth(float pw) | |||
{ | |||
pulseWidth = pw; | |||
} | |||
inline void MinBLEPVCO::step() | |||
{ | |||
// call the dedicated dispatch routines for the special case waveforms. | |||
switch (waveform) { | |||
case Waveform::Saw: | |||
step_saw(); | |||
break; | |||
case Waveform::Square: | |||
step_sq(); | |||
break; | |||
case Waveform::Sin: | |||
step_sin(); | |||
break; | |||
case Waveform::Tri: | |||
// Tri sync doesn't work -> use sin | |||
if (syncEnabled) { | |||
step_sin(); | |||
} else { | |||
step_tri(); | |||
} | |||
break; | |||
case Waveform::Even: | |||
step_even(); | |||
break; | |||
case Waveform::END: | |||
output = 0; | |||
break; // don't do anything if no outputs | |||
default: | |||
assert(false); | |||
} | |||
} | |||
// callback from master sync when it rolls over | |||
inline void MinBLEPVCO::onMasterSync(float masterPhase) | |||
{ | |||
gotSyncCallback = true; | |||
syncCallbackCrossing = masterPhase; | |||
} | |||
inline void MinBLEPVCO::step_saw() | |||
{ | |||
phase += normalizedFreq; | |||
const float predictedPhase = phase; | |||
if (gotSyncCallback) { | |||
const float excess = -syncCallbackCrossing * normalizedFreq; | |||
// Figure out where our sub-sample phase should be after reset | |||
// reset to zero | |||
const float newPhase = excess; | |||
phase = newPhase; | |||
} | |||
if (phase >= 1.0) { | |||
phase -= 1.0; | |||
} | |||
// see if we jumped | |||
if (phase != predictedPhase) { | |||
const float jump = phase - predictedPhase; | |||
// printf("%s jump = %f\n", name.c_str(), jump); fflush(stdout); | |||
if (gotSyncCallback) { | |||
const float crossing = syncCallbackCrossing; | |||
syncMinBLEP.jump(crossing, jump); | |||
if (syncCallback) { | |||
syncCallback(crossing); | |||
} | |||
} else { | |||
// phase overflowed | |||
const float crossing = -phase / normalizedFreq; | |||
aMinBLEP.jump(crossing, jump); | |||
if (syncCallback) { | |||
syncCallback(crossing); | |||
} | |||
} | |||
} | |||
float totalPhase = phase; | |||
totalPhase += aMinBLEP.shift(); | |||
totalPhase += syncMinBLEP.shift(); | |||
float saw = -1.0 + 2.0 * totalPhase; | |||
output = 5.0*saw; | |||
gotSyncCallback = false; | |||
} | |||
inline bool MinBLEPVCO::isSqHigh() const | |||
{ | |||
return phase >= pulseWidth; | |||
} | |||
inline void MinBLEPVCO::step_sq() | |||
{ | |||
bool phaseDidOverflow = false; | |||
phase += normalizedFreq; | |||
if (gotSyncCallback) { | |||
const float excess = -syncCallbackCrossing * normalizedFreq; | |||
// reset phase to near zero on sync | |||
phase = excess; | |||
} | |||
if (phase > 1.0f) { | |||
phase -= 1.0f; | |||
phaseDidOverflow = true; | |||
} | |||
// now examine for any pending edges, | |||
// and if found apply minBLEP and | |||
// send sync signal | |||
bool newSq = isSqHigh(); | |||
if (newSq != lastSq) { | |||
lastSq = newSq; | |||
const float jump = newSq ? 2 : -2; | |||
if (gotSyncCallback) { | |||
const float crossing = syncCallbackCrossing; | |||
syncMinBLEP.jump(crossing, jump); | |||
if (syncCallback) { | |||
syncCallback(crossing); | |||
} | |||
} else if (phaseDidOverflow) { | |||
const float crossing = -phase / normalizedFreq; | |||
aMinBLEP.jump(crossing, jump); | |||
if (syncCallback) { | |||
syncCallback(crossing); | |||
} | |||
} else { | |||
// crossed PW boundary | |||
const float crossing = -(phase - pulseWidth) / normalizedFreq; | |||
bMinBLEP.jump(crossing, jump); | |||
} | |||
} | |||
float square = newSq ? 1.0f : -1.0f; | |||
square += aMinBLEP.shift(); | |||
square += bMinBLEP.shift(); | |||
square += syncMinBLEP.shift(); | |||
output = 5.0*square; | |||
gotSyncCallback = false; | |||
} | |||
inline float MinBLEPVCO::sineLook(float input) const | |||
{ | |||
// want cosine, but only have sine lookup | |||
float adjPhase = input + .25f; | |||
if (adjPhase >= 1) { | |||
adjPhase -= 1; | |||
} | |||
return -LookupTable<float>::lookup(*sinLookup, adjPhase, true); | |||
} | |||
inline void MinBLEPVCO::step_sin() | |||
{ | |||
if (gotSyncCallback) { | |||
gotSyncCallback = false; | |||
// All calculations based on slave sync discontinuity happening at | |||
// the same sub-sample as the mater discontinuity. | |||
// First, figure out how much excess phase we are going to have after reset | |||
const float excess = -syncCallbackCrossing * normalizedFreq; | |||
// Figure out where our sub-sample phase should be after reset | |||
const float newPhase = .5 + excess; | |||
const float oldOutput = sineLook(phase); | |||
const float newOutput = sineLook(newPhase); | |||
const float jump = newOutput - oldOutput; | |||
syncMinBLEP.jump(syncCallbackCrossing, jump); | |||
this->phase = newPhase; | |||
// return; | |||
} else { | |||
phase += normalizedFreq; | |||
// Reset phase if at end of cycle | |||
if (phase >= 1.0) { | |||
phase -= 1.0; | |||
if (syncCallback) { | |||
float crossing = -phase / normalizedFreq; | |||
syncCallback(crossing); | |||
} | |||
} | |||
} | |||
float sine = sineLook(phase); | |||
sine += syncMinBLEP.shift(); | |||
output = 5.0*sine; | |||
} | |||
inline void MinBLEPVCO::step_tri() | |||
{ | |||
if (gotSyncCallback) { | |||
gotSyncCallback = false; | |||
// All calculations based on slave sync discontinuity happening at | |||
// the same sub-sample as the mater discontinuity. | |||
// First, figure out how much excess phase we are going to have after reset | |||
const float excess = -syncCallbackCrossing * normalizedFreq; | |||
// Figure out where our sub-sample phase should be after reset | |||
const float newPhase = .5 + excess; | |||
const float jump = -2.f * (phase - newPhase); | |||
#ifdef _LOG | |||
printf("%s: got sync ph=%.2f nph=%.2f excess=%.2f send cross %.2f jump %.2f \n", name.c_str(), | |||
phase, newPhase, | |||
excess, | |||
syncCallbackCrossing, jump); | |||
#endif | |||
syncMinBLEP.jump(syncCallbackCrossing, jump); | |||
this->phase = newPhase; | |||
return; | |||
} | |||
float oldPhase = phase; | |||
phase += normalizedFreq; | |||
if (oldPhase < 0.5 && phase >= 0.5) { | |||
const float crossing = -(phase - 0.5) / normalizedFreq; | |||
aMinBLEP.jump(crossing, 2.0); | |||
} | |||
// Reset phase if at end of cycle | |||
if (phase >= 1.0) { | |||
phase -= 1.0; | |||
float crossing = -phase / normalizedFreq; | |||
aMinBLEP.jump(crossing, -2.0); | |||
halfPhase = false; | |||
if (syncCallback) { | |||
syncCallback(crossing); | |||
} | |||
} | |||
// Outputs | |||
float triSquare = (phase < 0.5) ? -1.0 : 1.0; | |||
triSquare += aMinBLEP.shift(); | |||
triSquare += syncMinBLEP.shift(); | |||
// Integrate square for triangle | |||
tri += 4.0 * triSquare * normalizedFreq; | |||
tri *= (1.0 - 40.0 * sampleTime); | |||
// Set output | |||
output = 5.0*tri; | |||
} | |||
inline float calcDoubleSaw(float phase) | |||
{ | |||
return (phase < 0.5) ? (-1.0 + 4.0*phase) : (-1.0 + 4.0*(phase - 0.5)); | |||
} | |||
inline float MinBLEPVCO::evenLook(float input) const | |||
{ | |||
float doubleSaw = calcDoubleSaw(input); | |||
const float sine = sineLook(input); | |||
const float even = 0.55 * (doubleSaw + 1.27 * sine); | |||
return even; | |||
} | |||
inline void MinBLEPVCO::step_even() | |||
{ | |||
float oldPhase = phase; | |||
phase += normalizedFreq; | |||
float syncJump = 0; | |||
if (gotSyncCallback) { | |||
// All calculations based on slave sync discontinuity happening at | |||
// the same sub-sample as the mater discontinuity. | |||
// First, figure out how much excess phase we are going to have after reset | |||
const float excess = -syncCallbackCrossing * normalizedFreq; | |||
// Figure out where our sub-sample phase should be after reset | |||
const float newPhase = .5 + excess; | |||
// const float jump = -2.f * (phase - newPhase); | |||
#ifdef _LOG | |||
printf("%s: got sync ph=%.2f nph=%.2f excess=%.2f send cross %.2f jump %.2f \n", name.c_str(), | |||
phase, newPhase, | |||
excess, | |||
syncCallbackCrossing, jump); | |||
#endif | |||
// syncMinBLEP.jump(syncCallbackCrossing, jump); | |||
syncJump = evenLook(newPhase) - evenLook(this->phase); | |||
this->phase = newPhase; | |||
} | |||
bool jump5 = false; | |||
bool jump1 = false; | |||
if (oldPhase < 0.5 && phase >= 0.5) { | |||
jump5 = true; | |||
} | |||
// Reset phase if at end of cycle | |||
if (phase >= 1.0) { | |||
phase -= 1.0; | |||
jump1 = true; | |||
} | |||
if (gotSyncCallback) { | |||
const float crossing = syncCallbackCrossing; | |||
// FIXME!! | |||
float jump = syncJump; | |||
syncMinBLEP.jump(crossing, jump); | |||
if (syncCallback) { | |||
syncCallback(crossing); | |||
} | |||
} else if (jump1) { | |||
const float jump = -2; | |||
float crossing = -phase / normalizedFreq; | |||
aMinBLEP.jump(crossing, jump); | |||
if (syncCallback) { | |||
syncCallback(crossing); | |||
} | |||
} else if (jump5) { | |||
const float jump = -2; | |||
const float crossing = -(phase - 0.5) / normalizedFreq; | |||
aMinBLEP.jump(crossing, jump); | |||
} | |||
// note that non-sync minBLEP is added to double saw, | |||
// but for sync it's added to even. | |||
const float sine = sineLook(phase); | |||
float doubleSaw = (phase < 0.5) ? (-1.0 + 4.0*phase) : (-1.0 + 4.0*(phase - 0.5)); | |||
doubleSaw += aMinBLEP.shift(); | |||
float even = 0.55 * (doubleSaw + 1.27 * sine); | |||
even += syncMinBLEP.shift(); | |||
output = 5.0*even; | |||
gotSyncCallback = false; | |||
} | |||
#if 0 // old way | |||
inline void MinBLEPVCO::step_even() | |||
{ | |||
if (gotSyncCallback) { | |||
gotSyncCallback = false; | |||
// All calculations based on slave sync discontinuity happening at | |||
// the same sub-sample as the mater discontinuity. | |||
// First, figure out how much excess phase we are going to have after reset | |||
const float excess = -syncCallbackCrossing * normalizedFreq; | |||
// Figure out where our sub-sample phase should be after reset | |||
const float newPhase = .5 + excess; | |||
const float jump = -2.f * (phase - newPhase); | |||
#ifdef _LOG | |||
printf("%s: got sync ph=%.2f nph=%.2f excess=%.2f send cross %.2f jump %.2f \n", name.c_str(), | |||
phase, newPhase, | |||
excess, | |||
syncCallbackCrossing, jump); | |||
#endif | |||
syncMinBLEP.jump(syncCallbackCrossing, jump); | |||
this->phase = newPhase; | |||
return; | |||
} | |||
float oldPhase = phase; | |||
phase += normalizedFreq; | |||
if (oldPhase < 0.5 && phase >= 0.5) { | |||
float crossing = -(phase - 0.5) / normalizedFreq; | |||
aMinBLEP.jump(crossing, -2.0); | |||
} | |||
// Reset phase if at end of cycle | |||
if (phase >= 1.0) { | |||
phase -= 1.0; | |||
float crossing = -phase / normalizedFreq; | |||
aMinBLEP.jump(crossing, -2.0); | |||
if (syncCallback) { | |||
syncCallback(crossing); | |||
} | |||
} | |||
//sine = -cosf(2*AudioMath::Pi * phase); | |||
// want cosine, but only have sine lookup | |||
float adjPhase = phase + .25f; | |||
if (adjPhase >= 1) { | |||
adjPhase -= 1; | |||
} | |||
const float sine = -LookupTable<float>::lookup(*sinLookup, adjPhase, true); | |||
float doubleSaw = (phase < 0.5) ? (-1.0 + 4.0*phase) : (-1.0 + 4.0*(phase - 0.5)); | |||
doubleSaw += aMinBLEP.shift(); | |||
doubleSaw += syncMinBLEP.shift(); | |||
const float even = 0.55 * (doubleSaw + 1.27 * sine); | |||
//TBase::outputs[SINE_OUTPUT].value = 5.0*sine; | |||
output = 5.0*even; | |||
} | |||
#endif | |||
#if defined(_MSC_VER) | |||
#pragma warning (pop) | |||
#endif | |||
@@ -78,9 +78,9 @@ template<typename T, bool frequencyCanBeNegative> | |||
inline void SawOscillator<T, frequencyCanBeNegative>::setFrequency(SawOscillatorParams<T>& params, T freq) | |||
{ | |||
if (frequencyCanBeNegative) { | |||
assert(freq >= -.5 && freq < .5); | |||
assert(freq >= -.5 && freq <= .5); | |||
} else { | |||
assert(freq >= 0 && freq < .5); | |||
assert(freq >= 0 && freq <= .5); | |||
} | |||
params.phaseIncrement = freq; | |||
} | |||
@@ -1,5 +1,7 @@ | |||
#pragma once | |||
#include <assert.h> | |||
#include "AudioMath.h" | |||
#include "LookupTable.h" | |||
#include "ObjectCache.h" | |||
@@ -30,25 +32,14 @@ public: | |||
template<typename T, bool frequencyCanBeNegative> | |||
inline void SinOscillator<T, frequencyCanBeNegative>::setFrequency(SinOscillatorParams<T>& params, T frequency) | |||
{ | |||
std::function<double(double)> f = AudioMath::makeFunc_Sin(); | |||
// TODO: figure out a better initialization strategy | |||
// and a better strategy for table size | |||
// with 4096 thd was -130 db. let's use less memory! | |||
// if (!params.lookupParams.isValid()) { | |||
// LookupTable<T>::init(params.lookupParams, 256, 0, 1, f); | |||
// } | |||
assert(params.lookupParams->isValid()); | |||
SawOscillator<T, true>::setFrequency(params.sawParams, frequency); | |||
SawOscillator<T, frequencyCanBeNegative>::setFrequency(params.sawParams, frequency); | |||
} | |||
template<typename T, bool frequencyCanBeNegative> | |||
inline T SinOscillator<T, frequencyCanBeNegative>::run( | |||
SinOscillatorState<T>& state, const SinOscillatorParams<T>& params) | |||
{ | |||
const T temp = SawOscillator<T, frequencyCanBeNegative>::runSaw(state.sawState, params.sawParams); | |||
const T ret = LookupTable<T>::lookup(*params.lookupParams, temp); | |||
return ret; | |||
@@ -58,7 +49,6 @@ template<typename T, bool frequencyCanBeNegative> | |||
inline void SinOscillator<T, frequencyCanBeNegative>::runQuadrature( | |||
T& output, T& outputQuadrature, SinOscillatorState<T>& state, const SinOscillatorParams<T>& params) | |||
{ | |||
T saw, quadratureSaw; | |||
SawOscillator<T, frequencyCanBeNegative>::runQuadrature(saw, quadratureSaw, state.sawState, params.sawParams); | |||
output = LookupTable<T>::lookup(*params.lookupParams, saw); | |||
@@ -0,0 +1,483 @@ | |||
/** | |||
* This file contains a modified version of EvenVCO.cpp, from the | |||
* Befaco repo. See LICENSE-dist.txt for full license info. | |||
* | |||
* This code has been modified extensively by Squinky Labs. Mainly modifications were: | |||
* re-code hot-spots to lower CPU usage. | |||
* Fix compiler warnings. | |||
* Make it compile in Visual Studio | |||
*/ | |||
// Need to make this compile in MS tools for unit tests | |||
#if defined(_MSC_VER) | |||
#define __attribute__(x) | |||
#pragma warning (push) | |||
#pragma warning ( disable: 4244 4267 ) | |||
#endif | |||
#include "dsp/minblep.hpp" | |||
#include "dsp/filter.hpp" | |||
#include "AudioMath.h" | |||
#include "ObjectCache.h" | |||
using namespace rack; | |||
template <class TBase> | |||
struct EvenVCO : TBase | |||
{ | |||
EvenVCO(struct Module * module); | |||
EvenVCO(); | |||
enum ParamIds | |||
{ | |||
OCTAVE_PARAM, | |||
TUNE_PARAM, | |||
PWM_PARAM, | |||
NUM_PARAMS | |||
}; | |||
enum InputIds | |||
{ | |||
PITCH1_INPUT, | |||
PITCH2_INPUT, | |||
FM_INPUT, | |||
PWM_INPUT, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds | |||
{ | |||
TRI_OUTPUT, | |||
SINE_OUTPUT, | |||
EVEN_OUTPUT, | |||
SAW_OUTPUT, | |||
SQUARE_OUTPUT, | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds | |||
{ | |||
NUM_LIGHTS | |||
}; | |||
float phase = 0.0; | |||
float tri = 0.0; | |||
/** | |||
* References to shared lookup tables. | |||
* Destructor will free them automatically. | |||
*/ | |||
std::shared_ptr<LookupTableParams<float>> sinLookup; | |||
std::function<float(float)> expLookup; | |||
/** Whether we are past the pulse width already */ | |||
bool halfPhase = false; | |||
MinBLEP<16> triSquareMinBLEP; | |||
MinBLEP<16> triMinBLEP; | |||
MinBLEP<16> sineMinBLEP; | |||
MinBLEP<16> doubleSawMinBLEP; | |||
MinBLEP<16> sawMinBLEP; | |||
MinBLEP<16> squareMinBLEP; | |||
void step() override; | |||
void step_even(float deltaPhase); | |||
void step_saw(float deltaPhase); | |||
void step_sq(float deltaPhase); | |||
void step_sin(float deltaPhase); | |||
void step_tri(float deltaPhase); | |||
void step_all(float deltaPhase); | |||
void step_old(); | |||
void initialize(); | |||
void zeroOutputsExcept(int except); | |||
int dispatcher = 0; | |||
int loopCounter = 0; | |||
/** | |||
* To avoid scanning outputs for changes every sample, we | |||
* save the state here. | |||
*/ | |||
bool doSaw = false; | |||
bool doEven = false; | |||
bool doTri = false; | |||
bool doSq = false; | |||
bool doSin = false; | |||
/** | |||
* Variables added purely to enable unit testing | |||
*/ | |||
float _freq = 0; | |||
float _testFreq = 0; | |||
}; | |||
template <class TBase> | |||
inline EvenVCO<TBase>::EvenVCO() : TBase() | |||
{ | |||
initialize(); | |||
} | |||
template <class TBase> | |||
inline EvenVCO<TBase>::EvenVCO(struct Module * module) : TBase(module) | |||
{ | |||
initialize(); | |||
} | |||
template <class TBase> | |||
inline void EvenVCO<TBase>::initialize() | |||
{ | |||
triSquareMinBLEP.minblep = rack::minblep_16_32; | |||
triSquareMinBLEP.oversample = 32; | |||
triMinBLEP.minblep = minblep_16_32; | |||
triMinBLEP.oversample = 32; | |||
sineMinBLEP.minblep = minblep_16_32; | |||
sineMinBLEP.oversample = 32; | |||
doubleSawMinBLEP.minblep = minblep_16_32; | |||
doubleSawMinBLEP.oversample = 32; | |||
sawMinBLEP.minblep = minblep_16_32; | |||
sawMinBLEP.oversample = 32; | |||
squareMinBLEP.minblep = minblep_16_32; | |||
squareMinBLEP.oversample = 32; | |||
sinLookup = ObjectCache<float>::getSinLookup(); | |||
expLookup = ObjectCache<float>::getExp2Ex(); | |||
} | |||
template <class TBase> | |||
void EvenVCO<TBase>::zeroOutputsExcept(int except) | |||
{ | |||
for (int i = 0; i < NUM_OUTPUTS; ++i) { | |||
if (i != except) { | |||
// if we do even, we do sin at same time | |||
if ((i == SINE_OUTPUT) && (except == EVEN_OUTPUT)) { | |||
} else { | |||
TBase::outputs[i].value = 0; | |||
} | |||
} | |||
} | |||
} | |||
template <class TBase> | |||
inline void EvenVCO<TBase>::step_even(float deltaPhase) | |||
{ | |||
float oldPhase = phase; | |||
phase += deltaPhase; | |||
if (oldPhase < 0.5 && phase >= 0.5) { | |||
float crossing = -(phase - 0.5) / deltaPhase; | |||
doubleSawMinBLEP.jump(crossing, -2.0); | |||
} | |||
// Reset phase if at end of cycle | |||
if (phase >= 1.0) { | |||
phase -= 1.0; | |||
float crossing = -phase / deltaPhase; | |||
doubleSawMinBLEP.jump(crossing, -2.0); | |||
} | |||
//sine = -cosf(2*AudioMath::Pi * phase); | |||
// want cosine, but only have sine lookup | |||
float adjPhase = phase + .25f; | |||
if (adjPhase >= 1) { | |||
adjPhase -= 1; | |||
} | |||
const float sine = -LookupTable<float>::lookup(*sinLookup, adjPhase, true); | |||
float doubleSaw = (phase < 0.5) ? (-1.0 + 4.0*phase) : (-1.0 + 4.0*(phase - 0.5)); | |||
doubleSaw += doubleSawMinBLEP.shift(); | |||
const float even = 0.55 * (doubleSaw + 1.27 * sine); | |||
TBase::outputs[SINE_OUTPUT].value = 5.0*sine; | |||
TBase::outputs[EVEN_OUTPUT].value = 5.0*even; | |||
} | |||
template <class TBase> | |||
inline void EvenVCO<TBase>::step_saw(float deltaPhase) | |||
{ | |||
phase += deltaPhase; | |||
// Reset phase if at end of cycle | |||
if (phase >= 1.0) { | |||
phase -= 1.0; | |||
float crossing = -phase / deltaPhase; | |||
static float cMin = 100; | |||
static float cMax = -100; | |||
cMin = std::min(crossing, cMin); | |||
cMax = std::max(crossing, cMax); | |||
// printf("sawJump ph=%.2f, delta=%.2f cross=%.2f (%.2f, %.2f)\n", phase, deltaPhase, crossing, cMin, cMax); | |||
sawMinBLEP.jump(crossing, -2.0); | |||
} | |||
float saw = -1.0 + 2.0*phase; | |||
saw += sawMinBLEP.shift(); | |||
TBase::outputs[SAW_OUTPUT].value = 5.0*saw; | |||
} | |||
template <class TBase> | |||
inline void EvenVCO<TBase>::step_sin(float deltaPhase) | |||
{ | |||
phase += deltaPhase; | |||
// Reset phase if at end of cycle | |||
if (phase >= 1.0) { | |||
phase -= 1.0; | |||
} | |||
// want cosine, but only have sine lookup | |||
float adjPhase = phase + .25f; | |||
if (adjPhase >= 1) { | |||
adjPhase -= 1; | |||
} | |||
const float sine = -LookupTable<float>::lookup(*sinLookup, adjPhase, true); | |||
TBase::outputs[SINE_OUTPUT].value = 5.0*sine; | |||
} | |||
template <class TBase> | |||
inline void EvenVCO<TBase>::step_tri(float deltaPhase) | |||
{ | |||
float oldPhase = phase; | |||
phase += deltaPhase; | |||
if (oldPhase < 0.5 && phase >= 0.5) { | |||
const float crossing = -(phase - 0.5) / deltaPhase; | |||
triSquareMinBLEP.jump(crossing, 2.0); | |||
} | |||
// Reset phase if at end of cycle | |||
if (phase >= 1.0) { | |||
phase -= 1.0; | |||
float crossing = -phase / deltaPhase; | |||
triSquareMinBLEP.jump(crossing, -2.0); | |||
halfPhase = false; | |||
} | |||
// Outputs | |||
float triSquare = (phase < 0.5) ? -1.0 : 1.0; | |||
triSquare += triSquareMinBLEP.shift(); | |||
// Integrate square for triangle | |||
tri += 4.0 * triSquare * _freq * TBase::engineGetSampleTime(); | |||
tri *= (1.0 - 40.0 * TBase::engineGetSampleTime()); | |||
// Set output | |||
TBase::outputs[TRI_OUTPUT].value = 5.0*tri; | |||
} | |||
template <class TBase> | |||
inline void EvenVCO<TBase>::step_sq(float deltaPhase) | |||
{ | |||
phase += deltaPhase; | |||
// Pulse width | |||
float pw; | |||
if (doSq) { | |||
pw = TBase::params[PWM_PARAM].value + TBase::inputs[PWM_INPUT].value / 5.0; | |||
const float minPw = 0.05f; | |||
pw = rescale(clamp(pw, -1.0f, 1.0f), -1.0f, 1.0f, minPw, 1.0f - minPw); | |||
if (!halfPhase && phase >= pw) { | |||
float crossing = -(phase - pw) / deltaPhase; | |||
squareMinBLEP.jump(crossing, 2.0); | |||
halfPhase = true; | |||
} | |||
} | |||
// Reset phase if at end of cycle | |||
if (phase >= 1.0) { | |||
phase -= 1.0; | |||
float crossing = -phase / deltaPhase; | |||
squareMinBLEP.jump(crossing, -2.0); | |||
halfPhase = false; | |||
} | |||
float square = (phase < pw) ? -1.0 : 1.0; | |||
square += squareMinBLEP.shift(); | |||
TBase::outputs[SQUARE_OUTPUT].value = 5.0*square; | |||
} | |||
template <class TBase> | |||
inline void EvenVCO<TBase>::step() | |||
{ | |||
// We don't need to look for connected outputs every cycle. | |||
// do it less often, and store results. | |||
if (--loopCounter < 0) { | |||
loopCounter = 16; | |||
doSaw = TBase::outputs[SAW_OUTPUT].active; | |||
doEven = TBase::outputs[EVEN_OUTPUT].active; | |||
doTri = TBase::outputs[TRI_OUTPUT].active; | |||
doSq = TBase::outputs[SQUARE_OUTPUT].active; | |||
doSin = TBase::outputs[SINE_OUTPUT].active; | |||
if (doSaw && !doEven && !doTri && !doSq && !doSin) { | |||
dispatcher = SAW_OUTPUT; | |||
zeroOutputsExcept(SAW_OUTPUT); | |||
} else if (!doSaw && doEven && !doTri && !doSq) { | |||
dispatcher = EVEN_OUTPUT; | |||
zeroOutputsExcept(EVEN_OUTPUT); | |||
} else if (!doSaw && !doEven && !doTri && !doSq && doSin) { | |||
dispatcher = SINE_OUTPUT; | |||
zeroOutputsExcept(SINE_OUTPUT); | |||
} else if (!doSaw && !doEven && doTri && !doSq && !doSin) { | |||
dispatcher = TRI_OUTPUT; | |||
zeroOutputsExcept(TRI_OUTPUT); | |||
} else if (!doSaw && !doEven && !doTri && doSq && !doSin) { | |||
dispatcher = SQUARE_OUTPUT; | |||
zeroOutputsExcept(SQUARE_OUTPUT); | |||
} else { | |||
dispatcher = NUM_OUTPUTS; | |||
} | |||
} | |||
// Compute frequency, pitch is 1V/oct | |||
float pitch = 1.0 + roundf(TBase::params[OCTAVE_PARAM].value) + TBase::params[TUNE_PARAM].value / 12.0; | |||
pitch += TBase::inputs[PITCH1_INPUT].value + TBase::inputs[PITCH2_INPUT].value; | |||
pitch += TBase::inputs[FM_INPUT].value / 4.0; | |||
#if 1 // Use lookup table for pitch lookup | |||
const float q = float(log2(261.626)); // move up to pitch range of EvenVCO | |||
pitch += q; | |||
_freq = expLookup(pitch); | |||
#else | |||
_freq = 261.626 * powf(2.0, pitch); | |||
_freq = clamp(_freq, 0.0f, 20000.0f); | |||
#endif | |||
// Advance phase | |||
float f = (_testFreq) ? _testFreq : _freq; | |||
float deltaPhase = clamp(f * TBase::engineGetSampleTime(), 1e-6f, 0.5f); | |||
// call the dedicated dispatch routines for the special case waveforms. | |||
switch (dispatcher) { | |||
case SAW_OUTPUT: | |||
step_saw(deltaPhase); | |||
break; | |||
case EVEN_OUTPUT: | |||
step_even(deltaPhase); | |||
break; | |||
case SINE_OUTPUT: | |||
step_sin(deltaPhase); | |||
break; | |||
case TRI_OUTPUT: | |||
step_tri(deltaPhase); | |||
break; | |||
case SQUARE_OUTPUT: | |||
step_sq(deltaPhase); | |||
break; | |||
case NUM_OUTPUTS: | |||
step_all(deltaPhase); | |||
break; | |||
default: | |||
assert(false); | |||
} | |||
} | |||
/** | |||
* Less optimized version that can do all waveform combinations | |||
*/ | |||
template <class TBase> | |||
inline void EvenVCO<TBase>::step_all(float deltaPhase) | |||
{ | |||
float oldPhase = phase; | |||
phase += deltaPhase; | |||
if (oldPhase < 0.5 && phase >= 0.5) { | |||
const float crossing = -(phase - 0.5) / deltaPhase; | |||
if (doTri) { | |||
triSquareMinBLEP.jump(crossing, 2.0); | |||
} | |||
if (doEven) { | |||
doubleSawMinBLEP.jump(crossing, -2.0); | |||
} | |||
} | |||
// Pulse width | |||
float pw; | |||
if (doSq) { | |||
pw = TBase::params[PWM_PARAM].value + TBase::inputs[PWM_INPUT].value / 5.0; | |||
const float minPw = 0.05f; | |||
pw = rescale(clamp(pw, -1.0f, 1.0f), -1.0f, 1.0f, minPw, 1.0f - minPw); | |||
if (!halfPhase && phase >= pw) { | |||
const float crossing = -(phase - pw) / deltaPhase; | |||
squareMinBLEP.jump(crossing, 2.0); | |||
halfPhase = true; | |||
} | |||
} | |||
// Reset phase if at end of cycle | |||
if (phase >= 1.0) { | |||
phase -= 1.0; | |||
float crossing = -phase / deltaPhase; | |||
if (doTri) { | |||
triSquareMinBLEP.jump(crossing, -2.0); | |||
} | |||
if (doEven) { | |||
doubleSawMinBLEP.jump(crossing, -2.0); | |||
} | |||
if (doSq) { | |||
squareMinBLEP.jump(crossing, -2.0); | |||
} | |||
if (doSaw) { | |||
sawMinBLEP.jump(crossing, -2.0); | |||
} | |||
halfPhase = false; | |||
} | |||
// Outputs | |||
if (doTri) { | |||
float triSquare = (phase < 0.5) ? -1.0 : 1.0; | |||
triSquare += triSquareMinBLEP.shift(); | |||
// Integrate square for triangle | |||
tri += 4.0 * triSquare * _freq * TBase::engineGetSampleTime(); | |||
tri *= (1.0 - 40.0 * TBase::engineGetSampleTime()); | |||
} | |||
float sine = 0; | |||
float even = 0; | |||
float saw = 0; | |||
float square = 0; | |||
if (doSin || doEven) { | |||
//sine = -cosf(2*AudioMath::Pi * phase); | |||
// want cosine, but only have sine lookup | |||
float adjPhase = phase + .25f; | |||
if (adjPhase >= 1) { | |||
adjPhase -= 1; | |||
} | |||
sine = -LookupTable<float>::lookup(*sinLookup, adjPhase, true); | |||
} | |||
if (doEven) { | |||
float doubleSaw = (phase < 0.5) ? (-1.0 + 4.0*phase) : (-1.0 + 4.0*(phase - 0.5)); | |||
doubleSaw += doubleSawMinBLEP.shift(); | |||
even = 0.55 * (doubleSaw + 1.27 * sine); | |||
} | |||
if (doSaw) { | |||
saw = -1.0 + 2.0*phase; | |||
saw += sawMinBLEP.shift(); | |||
} | |||
if (doSq) { | |||
square = (phase < pw) ? -1.0 : 1.0; | |||
square += squareMinBLEP.shift(); | |||
} else { | |||
TBase::outputs[SQUARE_OUTPUT].value = 0; | |||
} | |||
// Set outputs | |||
// get rid of redundant stuff here | |||
TBase::outputs[TRI_OUTPUT].value = doTri ? 5.0*tri : 0; | |||
TBase::outputs[SINE_OUTPUT].value = 5.0*sine; | |||
TBase::outputs[EVEN_OUTPUT].value = 5.0*even; | |||
TBase::outputs[SAW_OUTPUT].value = 5.0*saw; | |||
TBase::outputs[SQUARE_OUTPUT].value = 5.0*square; | |||
} | |||
#if defined(_MSC_VER) | |||
#pragma warning (pop) | |||
#endif |
@@ -0,0 +1,195 @@ | |||
/** | |||
* This file is the original source for Befaco EvenVCO. It has been modified | |||
* slightly to allow it to be compiles in the Squinky Labs test framework. | |||
* | |||
* This file is only use for before/after profiling of EvilVCO. | |||
*/ | |||
//#include "Befaco.hpp" | |||
#include "dsp/minblep.hpp" | |||
#include "dsp/filter.hpp" | |||
#include "AudioMath.h" | |||
template <class TBase> | |||
struct EvenVCO_orig : TBase | |||
{ | |||
EvenVCO_orig(struct Module * module); | |||
EvenVCO_orig(); | |||
void initialize(); | |||
enum ParamIds { | |||
OCTAVE_PARAM, | |||
TUNE_PARAM, | |||
PWM_PARAM, | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
PITCH1_INPUT, | |||
PITCH2_INPUT, | |||
FM_INPUT, | |||
SYNC_INPUT, | |||
PWM_INPUT, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds { | |||
TRI_OUTPUT, | |||
SINE_OUTPUT, | |||
EVEN_OUTPUT, | |||
SAW_OUTPUT, | |||
SQUARE_OUTPUT, | |||
NUM_OUTPUTS | |||
}; | |||
float phase = 0.0; | |||
/** The value of the last sync input */ | |||
float sync = 0.0; | |||
/** The outputs */ | |||
float tri = 0.0; | |||
/** Whether we are past the pulse width already */ | |||
bool halfPhase = false; | |||
MinBLEP<16> triSquareMinBLEP; | |||
MinBLEP<16> triMinBLEP; | |||
MinBLEP<16> sineMinBLEP; | |||
MinBLEP<16> doubleSawMinBLEP; | |||
MinBLEP<16> sawMinBLEP; | |||
MinBLEP<16> squareMinBLEP; | |||
RCFilter triFilter; | |||
void step() override; | |||
}; | |||
template <class TBase> | |||
inline EvenVCO_orig<TBase>::EvenVCO_orig() : TBase() | |||
{ | |||
initialize(); | |||
} | |||
template <class TBase> | |||
inline EvenVCO_orig<TBase>::EvenVCO_orig(struct Module * module) : TBase(module) | |||
{ | |||
initialize(); | |||
} | |||
template <class TBase> | |||
inline void EvenVCO_orig<TBase>::initialize() | |||
{ | |||
triSquareMinBLEP.minblep = minblep_16_32; | |||
triSquareMinBLEP.oversample = 32; | |||
triMinBLEP.minblep = minblep_16_32; | |||
triMinBLEP.oversample = 32; | |||
sineMinBLEP.minblep = minblep_16_32; | |||
sineMinBLEP.oversample = 32; | |||
doubleSawMinBLEP.minblep = minblep_16_32; | |||
doubleSawMinBLEP.oversample = 32; | |||
sawMinBLEP.minblep = minblep_16_32; | |||
sawMinBLEP.oversample = 32; | |||
squareMinBLEP.minblep = minblep_16_32; | |||
squareMinBLEP.oversample = 32; | |||
} | |||
template <class TBase> | |||
inline void EvenVCO_orig<TBase>::step() { | |||
// Compute frequency, pitch is 1V/oct | |||
float pitch = 1.0 + roundf(TBase::params[OCTAVE_PARAM].value) + TBase::params[TUNE_PARAM].value / 12.0; | |||
pitch += TBase::inputs[PITCH1_INPUT].value + TBase::inputs[PITCH2_INPUT].value; | |||
pitch += TBase::inputs[FM_INPUT].value / 4.0; | |||
float freq = 261.626 * powf(2.0, pitch); | |||
freq = clamp(freq, 0.0f, 20000.0f); | |||
// Pulse width | |||
float pw = TBase::params[PWM_PARAM].value + TBase::inputs[PWM_INPUT].value / 5.0; | |||
const float minPw = 0.05; | |||
pw = rescale(clamp(pw, -1.0f, 1.0f), -1.0f, 1.0f, minPw, 1.0f - minPw); | |||
// Advance phase | |||
float deltaPhase = clamp(freq * TBase::engineGetSampleTime(), 1e-6f, 0.5f); | |||
float oldPhase = phase; | |||
phase += deltaPhase; | |||
if (oldPhase < 0.5 && phase >= 0.5) { | |||
float crossing = -(phase - 0.5) / deltaPhase; | |||
triSquareMinBLEP.jump(crossing, 2.0); | |||
doubleSawMinBLEP.jump(crossing, -2.0); | |||
} | |||
if (!halfPhase && phase >= pw) { | |||
float crossing = -(phase - pw) / deltaPhase; | |||
squareMinBLEP.jump(crossing, 2.0); | |||
halfPhase = true; | |||
} | |||
// Reset phase if at end of cycle | |||
if (phase >= 1.0) { | |||
phase -= 1.0; | |||
float crossing = -phase / deltaPhase; | |||
triSquareMinBLEP.jump(crossing, -2.0); | |||
doubleSawMinBLEP.jump(crossing, -2.0); | |||
squareMinBLEP.jump(crossing, -2.0); | |||
sawMinBLEP.jump(crossing, -2.0); | |||
halfPhase = false; | |||
} | |||
// Outputs | |||
float triSquare = (phase < 0.5) ? -1.0 : 1.0; | |||
triSquare += triSquareMinBLEP.shift(); | |||
// Integrate square for triangle | |||
tri += 4.0 * triSquare * freq * TBase::engineGetSampleTime(); | |||
tri *= (1.0 - 40.0 * TBase::engineGetSampleTime()); | |||
float sine = -cosf(2*AudioMath::Pi * phase); | |||
float doubleSaw = (phase < 0.5) ? (-1.0 + 4.0*phase) : (-1.0 + 4.0*(phase - 0.5)); | |||
doubleSaw += doubleSawMinBLEP.shift(); | |||
float even = 0.55 * (doubleSaw + 1.27 * sine); | |||
float saw = -1.0 + 2.0*phase; | |||
saw += sawMinBLEP.shift(); | |||
float square = (phase < pw) ? -1.0 : 1.0; | |||
square += squareMinBLEP.shift(); | |||
// Set outputs | |||
TBase::outputs[TRI_OUTPUT].value = 5.0*tri; | |||
TBase::outputs[SINE_OUTPUT].value = 5.0*sine; | |||
TBase::outputs[EVEN_OUTPUT].value = 5.0*even; | |||
TBase::outputs[SAW_OUTPUT].value = 5.0*saw; | |||
TBase::outputs[SQUARE_OUTPUT].value = 5.0*square; | |||
} | |||
#if 0 | |||
struct EvenVCOWidget : ModuleWidget { | |||
EvenVCOWidget(EvenVCO *module) : ModuleWidget(module) { | |||
setPanel(SVG::load(assetPlugin(plugin, "res/EvenVCO.svg"))); | |||
addChild(Widget::create<Knurlie>(Vec(15, 0))); | |||
addChild(Widget::create<Knurlie>(Vec(15, 365))); | |||
addChild(Widget::create<Knurlie>(Vec(15*6, 0))); | |||
addChild(Widget::create<Knurlie>(Vec(15*6, 365))); | |||
addParam(ParamWidget::create<BefacoBigSnapKnob>(Vec(22, 32), module, EvenVCO::OCTAVE_PARAM, -5.0, 4.0, 0.0)); | |||
addParam(ParamWidget::create<BefacoTinyKnob>(Vec(73, 131), module, EvenVCO::TUNE_PARAM, -7.0, 7.0, 0.0)); | |||
addParam(ParamWidget::create<Davies1900hRedKnob>(Vec(16, 230), module, EvenVCO::PWM_PARAM, -1.0, 1.0, 0.0)); | |||
addInput(Port::create<PJ301MPort>(Vec(8, 120), Port::INPUT, module, EvenVCO::PITCH1_INPUT)); | |||
addInput(Port::create<PJ301MPort>(Vec(19, 157), Port::INPUT, module, EvenVCO::PITCH2_INPUT)); | |||
addInput(Port::create<PJ301MPort>(Vec(48, 183), Port::INPUT, module, EvenVCO::FM_INPUT)); | |||
addInput(Port::create<PJ301MPort>(Vec(86, 189), Port::INPUT, module, EvenVCO::SYNC_INPUT)); | |||
addInput(Port::create<PJ301MPort>(Vec(72, 236), Port::INPUT, module, EvenVCO::PWM_INPUT)); | |||
addOutput(Port::create<PJ301MPort>(Vec(10, 283), Port::OUTPUT, module, EvenVCO::TRI_OUTPUT)); | |||
addOutput(Port::create<PJ301MPort>(Vec(87, 283), Port::OUTPUT, module, EvenVCO::SINE_OUTPUT)); | |||
addOutput(Port::create<PJ301MPort>(Vec(48, 306), Port::OUTPUT, module, EvenVCO::EVEN_OUTPUT)); | |||
addOutput(Port::create<PJ301MPort>(Vec(10, 327), Port::OUTPUT, module, EvenVCO::SAW_OUTPUT)); | |||
addOutput(Port::create<PJ301MPort>(Vec(87, 327), Port::OUTPUT, module, EvenVCO::SQUARE_OUTPUT)); | |||
} | |||
}; | |||
Model *modelEvenVCO = Model::create<EvenVCO, EvenVCOWidget>("Befaco", "EvenVCO", "EvenVCO", OSCILLATOR_TAG); | |||
#endif |
@@ -0,0 +1,447 @@ | |||
/** | |||
* This is a modified version of the VCV Fundamental VCO. | |||
* See LICENSE-dist.txt for full license info. | |||
* This code has been modified extensively by Squinky Labs. Mainly modifications were: | |||
* re-code hot-spots to lower CPU usage. | |||
* Fix compiler warnings. | |||
* Make it compile in Visual Studio | |||
*/ | |||
#pragma once | |||
#if defined(_MSC_VER) | |||
#pragma warning (push) | |||
#pragma warning (disable: 4305 4244 4267) | |||
#endif | |||
#if !defined(M_PI) | |||
#define M_PI 3.14159265358979323846264338327950288 | |||
#endif | |||
#include "dsp/functions.hpp" | |||
#include "dsp/filter.hpp" | |||
#include <random> | |||
#include "BiquadFilter.h" | |||
#include "BiquadParams.h" | |||
#include "BiquadState.h" | |||
#include "ButterworthFilterDesigner.h" | |||
#include "ObjectCache.h" | |||
#include "IIRDecimator.h" | |||
extern float sqsawTable[2048]; | |||
extern float sqtriTable[2048]; | |||
// When this is defined, will use Squinky Labs anti-aliasing decimators, | |||
// rather than rack::Decimator<> | |||
#define _USEIIR | |||
template <int OVERSAMPLE, int QUALITY> | |||
struct VoltageControlledOscillator | |||
{ | |||
float sampleTime = 0; | |||
bool analog = false; | |||
bool soft = false; | |||
float lastSyncValue = 0.0f; | |||
float phase = 0.0f; | |||
float freq; | |||
float pw = 0.5f; | |||
float pitch; | |||
bool syncEnabled = false; | |||
bool syncDirection = false; | |||
/** | |||
* flags to help decide not to do redundant work | |||
*/ | |||
bool sinEnabled = false; | |||
bool sqEnabled = false; | |||
bool sawEnabled = false; | |||
bool triEnabled = false; | |||
#ifdef _USEIIR | |||
IIRDecimator sinDecimator; | |||
IIRDecimator triDecimator; | |||
IIRDecimator sawDecimator; | |||
IIRDecimator sqrDecimator; | |||
#else | |||
rack::Decimator<OVERSAMPLE, QUALITY> sinDecimator; | |||
rack::Decimator<OVERSAMPLE, QUALITY> triDecimator; | |||
rack::Decimator<OVERSAMPLE, QUALITY> sawDecimator; | |||
rack::Decimator<OVERSAMPLE, QUALITY> sqrDecimator; | |||
#endif | |||
RCFilter sqrFilter; | |||
// For analog detuning effect | |||
float pitchSlew = 0.0f; | |||
int pitchSlewIndex = 0; | |||
float sinBuffer[OVERSAMPLE] = {}; | |||
float triBuffer[OVERSAMPLE] = {}; | |||
float sawBuffer[OVERSAMPLE] = {}; | |||
float sqrBuffer[OVERSAMPLE] = {}; | |||
// Use interpolating lookups for these transcendentals | |||
std::shared_ptr<LookupTableParams<float>> sinLookup; | |||
std::function<float(float)> expLookup; | |||
void init() | |||
{ | |||
sinLookup = ObjectCache<float>::getSinLookup(); | |||
expLookup = ObjectCache<float>::getExp2Ex(); | |||
// Set anti-alias 3-db down point an octave below nyquist: .25 | |||
//float cutoff = .25f / float(OVERSAMPLE); | |||
sinDecimator.setup(16); | |||
sinDecimator.setup(16); | |||
sawDecimator.setup(16); | |||
sqrDecimator.setup(16); | |||
triDecimator.setup(16); | |||
} | |||
// Use the standard c++ library for random generation | |||
std::default_random_engine generator{99}; | |||
std::normal_distribution<double> distribution{0, 1.0}; | |||
float noise() | |||
{ | |||
return (float) distribution(generator); | |||
} | |||
void setPitch(float pitchKnob, float pitchCv) | |||
{ | |||
// Compute frequency | |||
pitch = pitchKnob; | |||
if (analog) { | |||
// Apply pitch slew | |||
const float pitchSlewAmount = 3.0f; | |||
pitch += pitchSlew * pitchSlewAmount; | |||
} else { | |||
// Quantize coarse knob if digital mode | |||
pitch = roundf(pitch); | |||
} | |||
pitch += pitchCv; | |||
// Note C4 | |||
// freq = 261.626f * powf(2.0f, pitch / 12.0f); | |||
const float q = float(log2(261.626)); // move up to pitch range up | |||
pitch = (pitch / 12.0f) + q; | |||
freq = expLookup(pitch); | |||
} | |||
void setPulseWidth(float pulseWidth) | |||
{ | |||
const float pwMin = 0.01f; | |||
pw = clamp(pulseWidth, pwMin, 1.0f - pwMin); | |||
} | |||
void process(float deltaTime, float syncValue) | |||
{ | |||
assert(sinLookup); | |||
assert(sampleTime > 0); | |||
if (analog) { | |||
// Adjust pitch slew | |||
if (++pitchSlewIndex > 64) { | |||
const float pitchSlewTau = 100.0f; // Time constant for leaky integrator in seconds | |||
pitchSlew += (noise() - pitchSlew / pitchSlewTau) *sampleTime; | |||
pitchSlewIndex = 0; | |||
} | |||
} | |||
// Advance phase | |||
float deltaPhaseOver = clamp(freq * deltaTime, 1e-6, 0.5f) * (1.0f / OVERSAMPLE); | |||
// Detect sync | |||
int syncIndex = -1; // Index in the oversample loop where sync occurs [0, OVERSAMPLE) | |||
float syncCrossing = 0.0f; // Offset that sync occurs [0.0f, 1.0f) | |||
if (syncEnabled) { | |||
syncValue -= 0.01f; | |||
if (syncValue > 0.0f && lastSyncValue <= 0.0f) { | |||
float deltaSync = syncValue - lastSyncValue; | |||
syncCrossing = 1.0f - syncValue / deltaSync; | |||
syncCrossing *= OVERSAMPLE; | |||
syncIndex = (int) syncCrossing; | |||
syncCrossing -= syncIndex; | |||
} | |||
lastSyncValue = syncValue; | |||
} | |||
if (syncDirection) | |||
deltaPhaseOver *= -1.0f; | |||
if (sqEnabled) { | |||
sqrFilter.setCutoff(40.0f * deltaTime); | |||
} | |||
for (int i = 0; i < OVERSAMPLE; i++) { | |||
if (syncIndex == i) { | |||
if (soft) { | |||
syncDirection = !syncDirection; | |||
deltaPhaseOver *= -1.0f; | |||
} else { | |||
// phase = syncCrossing * deltaPhase / OVERSAMPLE; | |||
phase = 0.0f; | |||
} | |||
} | |||
if (sinEnabled) { | |||
if (analog) { | |||
// Quadratic approximation of sine, slightly richer harmonics | |||
if (phase < 0.5f) | |||
sinBuffer[i] = 1.f - 16.f * powf(phase - 0.25f, 2); | |||
else | |||
sinBuffer[i] = -1.f + 16.f * powf(phase - 0.75f, 2); | |||
sinBuffer[i] *= 1.08f; | |||
} else { | |||
// sinBuffer[i] = sinf(2.f*M_PI * phase); | |||
sinBuffer[i] = LookupTable<float>::lookup(*sinLookup, phase, true); | |||
} | |||
} | |||
if (triEnabled) { | |||
if (analog) { | |||
triBuffer[i] = 1.25f * interpolateLinear(sqtriTable, phase * 2047.f); | |||
} else { | |||
if (phase < 0.25f) | |||
triBuffer[i] = 4.f * phase; | |||
else if (phase < 0.75f) | |||
triBuffer[i] = 2.f - 4.f * phase; | |||
else | |||
triBuffer[i] = -4.f + 4.f * phase; | |||
} | |||
} | |||
if (sawEnabled) { | |||
if (analog) { | |||
sawBuffer[i] = 1.66f * interpolateLinear(sqsawTable, phase * 2047.f); | |||
} else { | |||
if (phase < 0.5f) | |||
sawBuffer[i] = 2.f * phase; | |||
else | |||
sawBuffer[i] = -2.f + 2.f * phase; | |||
} | |||
} | |||
if (sqEnabled) { | |||
sqrBuffer[i] = (phase < pw) ? 1.f : -1.f; | |||
if (analog) { | |||
// Simply filter here | |||
sqrFilter.process(sqrBuffer[i]); | |||
sqrBuffer[i] = 0.71f * sqrFilter.highpass(); | |||
} | |||
} | |||
// don't divide by oversample every time. | |||
// don't do that expensive mod | |||
phase += deltaPhaseOver; | |||
while (phase > 1.0f) { | |||
phase -= 1.0f; | |||
} | |||
while (phase < 0) { | |||
phase += 1.0f; | |||
} | |||
} | |||
} | |||
float sin() | |||
{ | |||
return sinDecimator.process(sinBuffer); | |||
} | |||
float tri() | |||
{ | |||
return triDecimator.process(triBuffer); | |||
} | |||
float saw() | |||
{ | |||
return sawDecimator.process(sawBuffer); | |||
} | |||
float sqr() | |||
{ | |||
return sqrDecimator.process(sqrBuffer); | |||
} | |||
#if 0 | |||
float light() | |||
{ | |||
return sinf(2 * M_PI * phase); | |||
} | |||
#endif | |||
}; | |||
#if 0 // let's remove from regular builds | |||
template <int OVERSAMPLE, int QUALITY> | |||
struct VoltageControlledOscillatorOrig | |||
{ | |||
float sampleTime = 0; | |||
bool analog = false; | |||
bool soft = false; | |||
float lastSyncValue = 0.0f; | |||
float phase = 0.0f; | |||
float freq; | |||
float pw = 0.5f; | |||
float pitch; | |||
bool syncEnabled = false; | |||
bool syncDirection = false; | |||
rack::Decimator<OVERSAMPLE, QUALITY> sinDecimator; | |||
rack::Decimator<OVERSAMPLE, QUALITY> triDecimator; | |||
rack::Decimator<OVERSAMPLE, QUALITY> sawDecimator; | |||
rack::Decimator<OVERSAMPLE, QUALITY> sqrDecimator; | |||
RCFilter sqrFilter; | |||
// For analog detuning effect | |||
float pitchSlew = 0.0f; | |||
int pitchSlewIndex = 0; | |||
float sinBuffer[OVERSAMPLE] = {}; | |||
float triBuffer[OVERSAMPLE] = {}; | |||
float sawBuffer[OVERSAMPLE] = {}; | |||
float sqrBuffer[OVERSAMPLE] = {}; | |||
std::default_random_engine generator{99}; | |||
std::normal_distribution<double> distribution{-1.0, 1.0}; | |||
float noise() | |||
{ | |||
return (float) distribution(generator); | |||
} | |||
void setPitch(float pitchKnob, float pitchCv) | |||
{ | |||
// Compute frequency | |||
pitch = pitchKnob; | |||
if (analog) { | |||
// Apply pitch slew | |||
const float pitchSlewAmount = 3.0f; | |||
pitch += pitchSlew * pitchSlewAmount; | |||
} else { | |||
// Quantize coarse knob if digital mode | |||
pitch = roundf(pitch); | |||
} | |||
pitch += pitchCv; | |||
// Note C4 | |||
freq = 261.626f * powf(2.0f, pitch / 12.0f); | |||
} | |||
void setPulseWidth(float pulseWidth) | |||
{ | |||
const float pwMin = 0.01f; | |||
pw = clamp(pulseWidth, pwMin, 1.0f - pwMin); | |||
} | |||
void init() | |||
{ | |||
} | |||
void process(float deltaTime, float syncValue) | |||
{ | |||
assert(sampleTime > 0); | |||
if (analog) { | |||
// Adjust pitch slew | |||
if (++pitchSlewIndex > 32) { | |||
const float pitchSlewTau = 100.0f; // Time constant for leaky integrator in seconds | |||
// pitchSlew += (randomNormal() - pitchSlew / pitchSlewTau) * sampleTime; | |||
pitchSlew += (noise() - pitchSlew / pitchSlewTau) *sampleTime; | |||
pitchSlewIndex = 0; | |||
} | |||
} | |||
// Advance phase | |||
float deltaPhase = clamp(freq * deltaTime, 1e-6, 0.5f); | |||
// Detect sync | |||
int syncIndex = -1; // Index in the oversample loop where sync occurs [0, OVERSAMPLE) | |||
float syncCrossing = 0.0f; // Offset that sync occurs [0.0f, 1.0f) | |||
if (syncEnabled) { | |||
syncValue -= 0.01f; | |||
if (syncValue > 0.0f && lastSyncValue <= 0.0f) { | |||
float deltaSync = syncValue - lastSyncValue; | |||
syncCrossing = 1.0f - syncValue / deltaSync; | |||
syncCrossing *= OVERSAMPLE; | |||
syncIndex = (int) syncCrossing; | |||
syncCrossing -= syncIndex; | |||
} | |||
lastSyncValue = syncValue; | |||
} | |||
if (syncDirection) | |||
deltaPhase *= -1.0f; | |||
sqrFilter.setCutoff(40.0f * deltaTime); | |||
for (int i = 0; i < OVERSAMPLE; i++) { | |||
if (syncIndex == i) { | |||
if (soft) { | |||
syncDirection = !syncDirection; | |||
deltaPhase *= -1.0f; | |||
} else { | |||
// phase = syncCrossing * deltaPhase / OVERSAMPLE; | |||
phase = 0.0f; | |||
} | |||
} | |||
if (analog) { | |||
// Quadratic approximation of sine, slightly richer harmonics | |||
if (phase < 0.5f) | |||
sinBuffer[i] = 1.f - 16.f * powf(phase - 0.25f, 2); | |||
else | |||
sinBuffer[i] = -1.f + 16.f * powf(phase - 0.75f, 2); | |||
sinBuffer[i] *= 1.08f; | |||
} else { | |||
sinBuffer[i] = sinf(2.f*M_PI * phase); | |||
} | |||
if (analog) { | |||
triBuffer[i] = 1.25f * interpolateLinear(sqtriTable, phase * 2047.f); | |||
} else { | |||
if (phase < 0.25f) | |||
triBuffer[i] = 4.f * phase; | |||
else if (phase < 0.75f) | |||
triBuffer[i] = 2.f - 4.f * phase; | |||
else | |||
triBuffer[i] = -4.f + 4.f * phase; | |||
} | |||
if (analog) { | |||
sawBuffer[i] = 1.66f * interpolateLinear(sqsawTable, phase * 2047.f); | |||
} else { | |||
if (phase < 0.5f) | |||
sawBuffer[i] = 2.f * phase; | |||
else | |||
sawBuffer[i] = -2.f + 2.f * phase; | |||
} | |||
sqrBuffer[i] = (phase < pw) ? 1.f : -1.f; | |||
if (analog) { | |||
// Simply filter here | |||
sqrFilter.process(sqrBuffer[i]); | |||
sqrBuffer[i] = 0.71f * sqrFilter.highpass(); | |||
} | |||
// Advance phase | |||
phase += deltaPhase / OVERSAMPLE; | |||
phase = eucmod(phase, 1.0f); | |||
} | |||
} | |||
float sin() | |||
{ | |||
return sinDecimator.process(sinBuffer); | |||
} | |||
float tri() | |||
{ | |||
return triDecimator.process(triBuffer); | |||
} | |||
float saw() | |||
{ | |||
return sawDecimator.process(sawBuffer); | |||
} | |||
float sqr() | |||
{ | |||
return sqrDecimator.process(sqrBuffer); | |||
} | |||
float light() | |||
{ | |||
return sinf(2 * M_PI * phase); | |||
} | |||
}; | |||
#endif | |||
#if defined(_MSC_VER) | |||
#pragma warning(pop) | |||
#endif |
@@ -0,0 +1,669 @@ | |||
#include "AsymWaveShaper.h" | |||
extern float symmetry_table_0[]; | |||
extern float symmetry_table_1[]; | |||
extern float symmetry_table_2[]; | |||
extern float symmetry_table_3[]; | |||
extern float symmetry_table_4[]; | |||
extern float symmetry_table_5[]; | |||
extern float symmetry_table_6[]; | |||
extern float symmetry_table_7[]; | |||
extern float symmetry_table_8[]; | |||
extern float symmetry_table_9[]; | |||
extern float symmetry_table_10[]; | |||
extern float symmetry_table_11[]; | |||
extern float symmetry_table_12[]; | |||
extern float symmetry_table_13[]; | |||
extern float symmetry_table_14[]; | |||
extern float symmetry_table_15[]; | |||
float * lookup_tables[16] = { | |||
symmetry_table_0, | |||
symmetry_table_1, | |||
symmetry_table_2, | |||
symmetry_table_3, | |||
symmetry_table_4, | |||
symmetry_table_5, | |||
symmetry_table_6, | |||
symmetry_table_7, | |||
symmetry_table_8, | |||
symmetry_table_9, | |||
symmetry_table_10, | |||
symmetry_table_11, | |||
symmetry_table_12, | |||
symmetry_table_13, | |||
symmetry_table_14, | |||
symmetry_table_15, | |||
}; | |||
AsymWaveShaper::AsymWaveShaper() | |||
{ | |||
for (int i = 0; i < iSymmetryTables; ++i) { | |||
const float* entries = lookup_tables[i]; | |||
LookupTable<float>::initDiscrete(tables[i], iNumPoints, entries); | |||
} | |||
} | |||
void AsymWaveShaper::genTableValues(const Spline& spline, int numPoints) | |||
{ | |||
const double x0 = spline[0].first; | |||
// first build non-uniform lookup | |||
NonUniformLookup nu; | |||
const double delta = 1.0 / (numPoints * 8); // let's oversample in t space | |||
for (double t = 0; t <= 1; t += delta) { | |||
auto pt = calcPoint(spline, t); | |||
//printf("adding point to table:%f, %f\n", pt.first, pt.second); | |||
nu.add(pt.first, pt.second); | |||
} | |||
// next output uniform | |||
for (int i = 0; i < numPoints; ++i) { | |||
double x = x0 + (double(i) / numPoints); | |||
double y = nu.lookup(x); | |||
printf("%ff", y); | |||
if (i != numPoints - 1) { | |||
printf(", "); | |||
if ((i % 8) == 7) { | |||
printf("\n"); | |||
} | |||
} | |||
} | |||
} | |||
void AsymWaveShaper::genTable(int index, double symmetry) | |||
{ | |||
printf("float symmetry_table_%d[%d] = {\n", index, iNumPoints); | |||
genTableValues(makeSplineLeft(symmetry), iNumPoints / 2); | |||
printf(",\n"); | |||
genTableValues(makeSplineRight(symmetry), iNumPoints / 2); | |||
printf("\n};\n"); | |||
fflush(stdout); | |||
} | |||
Spline AsymWaveShaper::makeSplineRight(double symmetry) | |||
{ | |||
Spline ret; | |||
ret.push_back(std::pair<double, double>(0.0, 0.0)); | |||
ret.push_back(std::pair<double, double>(0.5, 1.0)); | |||
ret.push_back(std::pair<double, double>(0.5, 1.0)); | |||
ret.push_back(std::pair<double, double>(1.0, 1.0)); | |||
return ret; | |||
} | |||
Spline AsymWaveShaper::makeSplineLeft(double symmetry) | |||
{ | |||
// symmetry from 0..1 | |||
Spline ret; | |||
ret.push_back(std::pair<double, double>(-1, -symmetry)); | |||
ret.push_back(std::pair<double, double>(-symmetry/2.f, -symmetry)); | |||
ret.push_back(std::pair<double, double>(-symmetry/2.f, -symmetry)); | |||
ret.push_back(std::pair<double, double>(0, 0)); | |||
return ret; | |||
} | |||
std::pair<double, double> AsymWaveShaper::calcPoint(const Spline& spline, double t) | |||
{ | |||
std::pair<double, double> ret; | |||
ret.first = pow(1 - t, 3) * spline[0].first + | |||
3 * t * pow(1 - t, 2) * spline[1].first + | |||
3 * pow(t, 2) * (1 - t) * spline[2].first | |||
+ pow(t, 3) * spline[3].first; | |||
ret.second = pow(1 - t, 3) * spline[0].second + | |||
3 * t * pow(1 - t, 2) * spline[1].second + | |||
3 * pow(t, 2) * (1 - t) * spline[2].second | |||
+ pow(t, 3) * spline[3].second; | |||
return ret; | |||
} | |||
float symmetry_table_0[256] = { | |||
0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | |||
0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | |||
0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | |||
0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | |||
0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | |||
0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | |||
0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | |||
0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | |||
0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | |||
0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | |||
0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | |||
0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | |||
0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | |||
0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | |||
0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | |||
0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, | |||
0.000000f, 0.015625f, 0.031249f, 0.046871f, 0.062490f, 0.078106f, 0.093716f, 0.109321f, | |||
0.124918f, 0.140506f, 0.156084f, 0.171650f, 0.187202f, 0.202740f, 0.218261f, 0.233762f, | |||
0.249243f, 0.264701f, 0.280132f, 0.295536f, 0.310909f, 0.326249f, 0.341552f, 0.356815f, | |||
0.372035f, 0.387208f, 0.402331f, 0.417399f, 0.432408f, 0.447354f, 0.462232f, 0.477036f, | |||
0.491762f, 0.506403f, 0.520954f, 0.535409f, 0.549760f, 0.564001f, 0.578125f, 0.592123f, | |||
0.605989f, 0.619714f, 0.633288f, 0.646704f, 0.659952f, 0.673023f, 0.685907f, 0.698593f, | |||
0.711074f, 0.723337f, 0.735373f, 0.747171f, 0.758723f, 0.770018f, 0.781047f, 0.791801f, | |||
0.802270f, 0.812447f, 0.822325f, 0.831897f, 0.841157f, 0.850101f, 0.858724f, 0.867024f, | |||
0.875000f, 0.882649f, 0.889974f, 0.896976f, 0.903657f, 0.910022f, 0.916075f, 0.921822f, | |||
0.927270f, 0.932426f, 0.937297f, 0.941893f, 0.946223f, 0.950296f, 0.954123f, 0.957712f, | |||
0.961074f, 0.964218f, 0.967157f, 0.969898f, 0.972452f, 0.974829f, 0.977038f, 0.979089f, | |||
0.980989f, 0.982748f, 0.984375f, 0.985876f, 0.987260f, 0.988534f, 0.989704f, 0.990778f, | |||
0.991762f, 0.992661f, 0.993482f, 0.994229f, 0.994908f, 0.995524f, 0.996081f, 0.996583f, | |||
0.997035f, 0.997440f, 0.997802f, 0.998124f, 0.998409f, 0.998661f, 0.998882f, 0.999076f, | |||
0.999243f, 0.999387f, 0.999511f, 0.999615f, 0.999702f, 0.999775f, 0.999834f, 0.999881f, | |||
0.999918f, 0.999946f, 0.999966f, 0.999981f, 0.999990f, 0.999996f, 0.999999f, 1.000000f | |||
}; | |||
float symmetry_table_1[256] = { | |||
-0.066667f, -0.066667f, -0.066667f, -0.066667f, -0.066667f, -0.066667f, -0.066666f, -0.066666f, | |||
-0.066666f, -0.066666f, -0.066665f, -0.066665f, -0.066664f, -0.066663f, -0.066663f, -0.066662f, | |||
-0.066661f, -0.066659f, -0.066658f, -0.066656f, -0.066654f, -0.066652f, -0.066650f, -0.066647f, | |||
-0.066644f, -0.066641f, -0.066638f, -0.066634f, -0.066630f, -0.066625f, -0.066621f, -0.066615f, | |||
-0.066610f, -0.066603f, -0.066597f, -0.066590f, -0.066582f, -0.066574f, -0.066565f, -0.066556f, | |||
-0.066546f, -0.066535f, -0.066523f, -0.066511f, -0.066498f, -0.066484f, -0.066470f, -0.066454f, | |||
-0.066438f, -0.066420f, -0.066402f, -0.066382f, -0.066361f, -0.066339f, -0.066316f, -0.066292f, | |||
-0.066266f, -0.066238f, -0.066210f, -0.066179f, -0.066147f, -0.066113f, -0.066078f, -0.066040f, | |||
-0.066001f, -0.065959f, -0.065916f, -0.065870f, -0.065821f, -0.065770f, -0.065717f, -0.065660f, | |||
-0.065601f, -0.065538f, -0.065473f, -0.065404f, -0.065331f, -0.065255f, -0.065174f, -0.065089f, | |||
-0.065000f, -0.064906f, -0.064808f, -0.064703f, -0.064594f, -0.064478f, -0.064356f, -0.064228f, | |||
-0.064092f, -0.063949f, -0.063798f, -0.063639f, -0.063470f, -0.063292f, -0.063103f, -0.062903f, | |||
-0.062691f, -0.062465f, -0.062226f, -0.061972f, -0.061702f, -0.061413f, -0.061106f, -0.060777f, | |||
-0.060425f, -0.060048f, -0.059643f, -0.059207f, -0.058737f, -0.058228f, -0.057677f, -0.057076f, | |||
-0.056421f, -0.055703f, -0.054911f, -0.054035f, -0.053059f, -0.051965f, -0.050726f, -0.049311f, | |||
-0.047673f, -0.045747f, -0.043439f, -0.040598f, -0.036977f, -0.032128f, -0.025192f, -0.014656f, | |||
0.000000f, 0.015625f, 0.031249f, 0.046871f, 0.062490f, 0.078106f, 0.093716f, 0.109321f, | |||
0.124918f, 0.140506f, 0.156084f, 0.171650f, 0.187202f, 0.202740f, 0.218261f, 0.233762f, | |||
0.249243f, 0.264701f, 0.280132f, 0.295536f, 0.310909f, 0.326249f, 0.341552f, 0.356815f, | |||
0.372035f, 0.387208f, 0.402331f, 0.417399f, 0.432408f, 0.447354f, 0.462232f, 0.477036f, | |||
0.491762f, 0.506403f, 0.520954f, 0.535409f, 0.549760f, 0.564001f, 0.578125f, 0.592123f, | |||
0.605989f, 0.619714f, 0.633288f, 0.646704f, 0.659952f, 0.673023f, 0.685907f, 0.698593f, | |||
0.711074f, 0.723337f, 0.735373f, 0.747171f, 0.758723f, 0.770018f, 0.781047f, 0.791801f, | |||
0.802270f, 0.812447f, 0.822325f, 0.831897f, 0.841157f, 0.850101f, 0.858724f, 0.867024f, | |||
0.875000f, 0.882649f, 0.889974f, 0.896976f, 0.903657f, 0.910022f, 0.916075f, 0.921822f, | |||
0.927270f, 0.932426f, 0.937297f, 0.941893f, 0.946223f, 0.950296f, 0.954123f, 0.957712f, | |||
0.961074f, 0.964218f, 0.967157f, 0.969898f, 0.972452f, 0.974829f, 0.977038f, 0.979089f, | |||
0.980989f, 0.982748f, 0.984375f, 0.985876f, 0.987260f, 0.988534f, 0.989704f, 0.990778f, | |||
0.991762f, 0.992661f, 0.993482f, 0.994229f, 0.994908f, 0.995524f, 0.996081f, 0.996583f, | |||
0.997035f, 0.997440f, 0.997802f, 0.998124f, 0.998409f, 0.998661f, 0.998882f, 0.999076f, | |||
0.999243f, 0.999387f, 0.999511f, 0.999615f, 0.999702f, 0.999775f, 0.999834f, 0.999881f, | |||
0.999918f, 0.999946f, 0.999966f, 0.999981f, 0.999990f, 0.999996f, 0.999999f, 1.000000f | |||
}; | |||
float symmetry_table_2[256] = { | |||
-0.133333f, -0.133333f, -0.133333f, -0.133333f, -0.133333f, -0.133333f, -0.133333f, -0.133332f, | |||
-0.133332f, -0.133331f, -0.133330f, -0.133329f, -0.133328f, -0.133326f, -0.133324f, -0.133322f, | |||
-0.133320f, -0.133317f, -0.133314f, -0.133310f, -0.133306f, -0.133301f, -0.133296f, -0.133290f, | |||
-0.133284f, -0.133276f, -0.133269f, -0.133260f, -0.133251f, -0.133241f, -0.133230f, -0.133218f, | |||
-0.133205f, -0.133191f, -0.133176f, -0.133160f, -0.133143f, -0.133124f, -0.133104f, -0.133083f, | |||
-0.133060f, -0.133036f, -0.133010f, -0.132982f, -0.132953f, -0.132921f, -0.132888f, -0.132852f, | |||
-0.132815f, -0.132775f, -0.132732f, -0.132688f, -0.132640f, -0.132590f, -0.132537f, -0.132480f, | |||
-0.132421f, -0.132358f, -0.132292f, -0.132222f, -0.132148f, -0.132070f, -0.131988f, -0.131902f, | |||
-0.131810f, -0.131714f, -0.131613f, -0.131506f, -0.131393f, -0.131274f, -0.131149f, -0.131018f, | |||
-0.130879f, -0.130733f, -0.130579f, -0.130417f, -0.130246f, -0.130066f, -0.129876f, -0.129676f, | |||
-0.129465f, -0.129243f, -0.129008f, -0.128761f, -0.128500f, -0.128224f, -0.127932f, -0.127624f, | |||
-0.127299f, -0.126954f, -0.126590f, -0.126203f, -0.125794f, -0.125360f, -0.124899f, -0.124410f, | |||
-0.123889f, -0.123335f, -0.122745f, -0.122115f, -0.121442f, -0.120723f, -0.119952f, -0.119125f, | |||
-0.118235f, -0.117277f, -0.116243f, -0.115125f, -0.113911f, -0.112591f, -0.111150f, -0.109572f, | |||
-0.107837f, -0.105920f, -0.103793f, -0.101419f, -0.098751f, -0.095733f, -0.092290f, -0.088326f, | |||
-0.083713f, -0.078285f, -0.071824f, -0.064055f, -0.054668f, -0.043400f, -0.030217f, -0.015502f, | |||
0.000000f, 0.015625f, 0.031249f, 0.046871f, 0.062490f, 0.078106f, 0.093716f, 0.109321f, | |||
0.124918f, 0.140506f, 0.156084f, 0.171650f, 0.187202f, 0.202740f, 0.218261f, 0.233762f, | |||
0.249243f, 0.264701f, 0.280132f, 0.295536f, 0.310909f, 0.326249f, 0.341552f, 0.356815f, | |||
0.372035f, 0.387208f, 0.402331f, 0.417399f, 0.432408f, 0.447354f, 0.462232f, 0.477036f, | |||
0.491762f, 0.506403f, 0.520954f, 0.535409f, 0.549760f, 0.564001f, 0.578125f, 0.592123f, | |||
0.605989f, 0.619714f, 0.633288f, 0.646704f, 0.659952f, 0.673023f, 0.685907f, 0.698593f, | |||
0.711074f, 0.723337f, 0.735373f, 0.747171f, 0.758723f, 0.770018f, 0.781047f, 0.791801f, | |||
0.802270f, 0.812447f, 0.822325f, 0.831897f, 0.841157f, 0.850101f, 0.858724f, 0.867024f, | |||
0.875000f, 0.882649f, 0.889974f, 0.896976f, 0.903657f, 0.910022f, 0.916075f, 0.921822f, | |||
0.927270f, 0.932426f, 0.937297f, 0.941893f, 0.946223f, 0.950296f, 0.954123f, 0.957712f, | |||
0.961074f, 0.964218f, 0.967157f, 0.969898f, 0.972452f, 0.974829f, 0.977038f, 0.979089f, | |||
0.980989f, 0.982748f, 0.984375f, 0.985876f, 0.987260f, 0.988534f, 0.989704f, 0.990778f, | |||
0.991762f, 0.992661f, 0.993482f, 0.994229f, 0.994908f, 0.995524f, 0.996081f, 0.996583f, | |||
0.997035f, 0.997440f, 0.997802f, 0.998124f, 0.998409f, 0.998661f, 0.998882f, 0.999076f, | |||
0.999243f, 0.999387f, 0.999511f, 0.999615f, 0.999702f, 0.999775f, 0.999834f, 0.999881f, | |||
0.999918f, 0.999946f, 0.999966f, 0.999981f, 0.999990f, 0.999996f, 0.999999f, 1.000000f | |||
}; | |||
float symmetry_table_3[256] = { | |||
-0.200000f, -0.200000f, -0.200000f, -0.200000f, -0.200000f, -0.199999f, -0.199999f, -0.199998f, | |||
-0.199997f, -0.199996f, -0.199995f, -0.199993f, -0.199991f, -0.199988f, -0.199985f, -0.199981f, | |||
-0.199977f, -0.199972f, -0.199967f, -0.199960f, -0.199953f, -0.199945f, -0.199937f, -0.199927f, | |||
-0.199916f, -0.199904f, -0.199891f, -0.199877f, -0.199861f, -0.199844f, -0.199825f, -0.199805f, | |||
-0.199783f, -0.199759f, -0.199734f, -0.199706f, -0.199677f, -0.199645f, -0.199611f, -0.199575f, | |||
-0.199536f, -0.199494f, -0.199449f, -0.199402f, -0.199351f, -0.199297f, -0.199240f, -0.199179f, | |||
-0.199114f, -0.199046f, -0.198973f, -0.198895f, -0.198813f, -0.198726f, -0.198634f, -0.198537f, | |||
-0.198434f, -0.198325f, -0.198210f, -0.198089f, -0.197960f, -0.197825f, -0.197681f, -0.197530f, | |||
-0.197370f, -0.197202f, -0.197024f, -0.196837f, -0.196639f, -0.196430f, -0.196210f, -0.195978f, | |||
-0.195733f, -0.195474f, -0.195201f, -0.194914f, -0.194610f, -0.194289f, -0.193951f, -0.193594f, | |||
-0.193216f, -0.192818f, -0.192396f, -0.191951f, -0.191479f, -0.190981f, -0.190453f, -0.189894f, | |||
-0.189302f, -0.188674f, -0.188007f, -0.187300f, -0.186548f, -0.185748f, -0.184897f, -0.183990f, | |||
-0.183023f, -0.181990f, -0.180886f, -0.179704f, -0.178437f, -0.177078f, -0.175616f, -0.174041f, | |||
-0.172341f, -0.170504f, -0.168512f, -0.166349f, -0.163992f, -0.161418f, -0.158598f, -0.155499f, | |||
-0.152081f, -0.148298f, -0.144093f, -0.139402f, -0.134148f, -0.128240f, -0.121576f, -0.114043f, | |||
-0.105522f, -0.095903f, -0.085107f, -0.073109f, -0.059969f, -0.045841f, -0.030958f, -0.015591f, | |||
0.000000f, 0.015625f, 0.031249f, 0.046871f, 0.062490f, 0.078106f, 0.093716f, 0.109321f, | |||
0.124918f, 0.140506f, 0.156084f, 0.171650f, 0.187202f, 0.202740f, 0.218261f, 0.233762f, | |||
0.249243f, 0.264701f, 0.280132f, 0.295536f, 0.310909f, 0.326249f, 0.341552f, 0.356815f, | |||
0.372035f, 0.387208f, 0.402331f, 0.417399f, 0.432408f, 0.447354f, 0.462232f, 0.477036f, | |||
0.491762f, 0.506403f, 0.520954f, 0.535409f, 0.549760f, 0.564001f, 0.578125f, 0.592123f, | |||
0.605989f, 0.619714f, 0.633288f, 0.646704f, 0.659952f, 0.673023f, 0.685907f, 0.698593f, | |||
0.711074f, 0.723337f, 0.735373f, 0.747171f, 0.758723f, 0.770018f, 0.781047f, 0.791801f, | |||
0.802270f, 0.812447f, 0.822325f, 0.831897f, 0.841157f, 0.850101f, 0.858724f, 0.867024f, | |||
0.875000f, 0.882649f, 0.889974f, 0.896976f, 0.903657f, 0.910022f, 0.916075f, 0.921822f, | |||
0.927270f, 0.932426f, 0.937297f, 0.941893f, 0.946223f, 0.950296f, 0.954123f, 0.957712f, | |||
0.961074f, 0.964218f, 0.967157f, 0.969898f, 0.972452f, 0.974829f, 0.977038f, 0.979089f, | |||
0.980989f, 0.982748f, 0.984375f, 0.985876f, 0.987260f, 0.988534f, 0.989704f, 0.990778f, | |||
0.991762f, 0.992661f, 0.993482f, 0.994229f, 0.994908f, 0.995524f, 0.996081f, 0.996583f, | |||
0.997035f, 0.997440f, 0.997802f, 0.998124f, 0.998409f, 0.998661f, 0.998882f, 0.999076f, | |||
0.999243f, 0.999387f, 0.999511f, 0.999615f, 0.999702f, 0.999775f, 0.999834f, 0.999881f, | |||
0.999918f, 0.999946f, 0.999966f, 0.999981f, 0.999990f, 0.999996f, 0.999999f, 1.000000f | |||
}; | |||
float symmetry_table_4[256] = { | |||
-0.266667f, -0.266667f, -0.266667f, -0.266666f, -0.266666f, -0.266666f, -0.266665f, -0.266664f, | |||
-0.266663f, -0.266661f, -0.266659f, -0.266656f, -0.266653f, -0.266649f, -0.266644f, -0.266639f, | |||
-0.266632f, -0.266625f, -0.266617f, -0.266607f, -0.266596f, -0.266584f, -0.266571f, -0.266556f, | |||
-0.266540f, -0.266522f, -0.266502f, -0.266480f, -0.266457f, -0.266431f, -0.266402f, -0.266372f, | |||
-0.266338f, -0.266303f, -0.266264f, -0.266222f, -0.266177f, -0.266128f, -0.266077f, -0.266021f, | |||
-0.265961f, -0.265897f, -0.265829f, -0.265756f, -0.265679f, -0.265596f, -0.265508f, -0.265414f, | |||
-0.265315f, -0.265209f, -0.265096f, -0.264977f, -0.264850f, -0.264716f, -0.264573f, -0.264423f, | |||
-0.264263f, -0.264094f, -0.263915f, -0.263726f, -0.263526f, -0.263314f, -0.263090f, -0.262854f, | |||
-0.262604f, -0.262340f, -0.262061f, -0.261766f, -0.261455f, -0.261126f, -0.260778f, -0.260411f, | |||
-0.260023f, -0.259613f, -0.259180f, -0.258722f, -0.258238f, -0.257726f, -0.257185f, -0.256613f, | |||
-0.256007f, -0.255366f, -0.254687f, -0.253968f, -0.253206f, -0.252398f, -0.251540f, -0.250630f, | |||
-0.249664f, -0.248637f, -0.247544f, -0.246381f, -0.245143f, -0.243822f, -0.242413f, -0.240908f, | |||
-0.239298f, -0.237575f, -0.235729f, -0.233747f, -0.231617f, -0.229325f, -0.226855f, -0.224189f, | |||
-0.221306f, -0.218183f, -0.214794f, -0.211110f, -0.207097f, -0.202719f, -0.197933f, -0.192692f, | |||
-0.186945f, -0.180635f, -0.173704f, -0.166089f, -0.157731f, -0.148574f, -0.138577f, -0.127713f, | |||
-0.115983f, -0.103419f, -0.090082f, -0.076066f, -0.061485f, -0.046465f, -0.031134f, -0.015611f, | |||
0.000000f, 0.015625f, 0.031249f, 0.046871f, 0.062490f, 0.078106f, 0.093716f, 0.109321f, | |||
0.124918f, 0.140506f, 0.156084f, 0.171650f, 0.187202f, 0.202740f, 0.218261f, 0.233762f, | |||
0.249243f, 0.264701f, 0.280132f, 0.295536f, 0.310909f, 0.326249f, 0.341552f, 0.356815f, | |||
0.372035f, 0.387208f, 0.402331f, 0.417399f, 0.432408f, 0.447354f, 0.462232f, 0.477036f, | |||
0.491762f, 0.506403f, 0.520954f, 0.535409f, 0.549760f, 0.564001f, 0.578125f, 0.592123f, | |||
0.605989f, 0.619714f, 0.633288f, 0.646704f, 0.659952f, 0.673023f, 0.685907f, 0.698593f, | |||
0.711074f, 0.723337f, 0.735373f, 0.747171f, 0.758723f, 0.770018f, 0.781047f, 0.791801f, | |||
0.802270f, 0.812447f, 0.822325f, 0.831897f, 0.841157f, 0.850101f, 0.858724f, 0.867024f, | |||
0.875000f, 0.882649f, 0.889974f, 0.896976f, 0.903657f, 0.910022f, 0.916075f, 0.921822f, | |||
0.927270f, 0.932426f, 0.937297f, 0.941893f, 0.946223f, 0.950296f, 0.954123f, 0.957712f, | |||
0.961074f, 0.964218f, 0.967157f, 0.969898f, 0.972452f, 0.974829f, 0.977038f, 0.979089f, | |||
0.980989f, 0.982748f, 0.984375f, 0.985876f, 0.987260f, 0.988534f, 0.989704f, 0.990778f, | |||
0.991762f, 0.992661f, 0.993482f, 0.994229f, 0.994908f, 0.995524f, 0.996081f, 0.996583f, | |||
0.997035f, 0.997440f, 0.997802f, 0.998124f, 0.998409f, 0.998661f, 0.998882f, 0.999076f, | |||
0.999243f, 0.999387f, 0.999511f, 0.999615f, 0.999702f, 0.999775f, 0.999834f, 0.999881f, | |||
0.999918f, 0.999946f, 0.999966f, 0.999981f, 0.999990f, 0.999996f, 0.999999f, 1.000000f | |||
}; | |||
float symmetry_table_5[256] = { | |||
-0.333333f, -0.333333f, -0.333333f, -0.333333f, -0.333333f, -0.333332f, -0.333331f, -0.333330f, | |||
-0.333328f, -0.333325f, -0.333322f, -0.333318f, -0.333314f, -0.333308f, -0.333301f, -0.333294f, | |||
-0.333284f, -0.333274f, -0.333262f, -0.333249f, -0.333234f, -0.333217f, -0.333198f, -0.333177f, | |||
-0.333153f, -0.333128f, -0.333099f, -0.333068f, -0.333034f, -0.332997f, -0.332957f, -0.332913f, | |||
-0.332865f, -0.332814f, -0.332758f, -0.332698f, -0.332633f, -0.332564f, -0.332489f, -0.332409f, | |||
-0.332323f, -0.332231f, -0.332132f, -0.332027f, -0.331915f, -0.331795f, -0.331667f, -0.331531f, | |||
-0.331386f, -0.331232f, -0.331068f, -0.330894f, -0.330710f, -0.330513f, -0.330305f, -0.330085f, | |||
-0.329851f, -0.329603f, -0.329340f, -0.329062f, -0.328767f, -0.328455f, -0.328125f, -0.327775f, | |||
-0.327406f, -0.327014f, -0.326600f, -0.326162f, -0.325699f, -0.325209f, -0.324690f, -0.324141f, | |||
-0.323560f, -0.322946f, -0.322295f, -0.321606f, -0.320877f, -0.320104f, -0.319286f, -0.318419f, | |||
-0.317500f, -0.316525f, -0.315491f, -0.314393f, -0.313228f, -0.311990f, -0.310674f, -0.309274f, | |||
-0.307785f, -0.306199f, -0.304509f, -0.302706f, -0.300783f, -0.298728f, -0.296532f, -0.294183f, | |||
-0.291667f, -0.288969f, -0.286075f, -0.282967f, -0.279625f, -0.276029f, -0.272155f, -0.267977f, | |||
-0.263468f, -0.258598f, -0.253334f, -0.247642f, -0.241485f, -0.234825f, -0.227623f, -0.219844f, | |||
-0.211450f, -0.202413f, -0.192708f, -0.182321f, -0.171248f, -0.159501f, -0.147103f, -0.134096f, | |||
-0.120530f, -0.106468f, -0.091979f, -0.077137f, -0.062014f, -0.046678f, -0.031194f, -0.015618f, | |||
0.000000f, 0.015625f, 0.031249f, 0.046871f, 0.062490f, 0.078106f, 0.093716f, 0.109321f, | |||
0.124918f, 0.140506f, 0.156084f, 0.171650f, 0.187202f, 0.202740f, 0.218261f, 0.233762f, | |||
0.249243f, 0.264701f, 0.280132f, 0.295536f, 0.310909f, 0.326249f, 0.341552f, 0.356815f, | |||
0.372035f, 0.387208f, 0.402331f, 0.417399f, 0.432408f, 0.447354f, 0.462232f, 0.477036f, | |||
0.491762f, 0.506403f, 0.520954f, 0.535409f, 0.549760f, 0.564001f, 0.578125f, 0.592123f, | |||
0.605989f, 0.619714f, 0.633288f, 0.646704f, 0.659952f, 0.673023f, 0.685907f, 0.698593f, | |||
0.711074f, 0.723337f, 0.735373f, 0.747171f, 0.758723f, 0.770018f, 0.781047f, 0.791801f, | |||
0.802270f, 0.812447f, 0.822325f, 0.831897f, 0.841157f, 0.850101f, 0.858724f, 0.867024f, | |||
0.875000f, 0.882649f, 0.889974f, 0.896976f, 0.903657f, 0.910022f, 0.916075f, 0.921822f, | |||
0.927270f, 0.932426f, 0.937297f, 0.941893f, 0.946223f, 0.950296f, 0.954123f, 0.957712f, | |||
0.961074f, 0.964218f, 0.967157f, 0.969898f, 0.972452f, 0.974829f, 0.977038f, 0.979089f, | |||
0.980989f, 0.982748f, 0.984375f, 0.985876f, 0.987260f, 0.988534f, 0.989704f, 0.990778f, | |||
0.991762f, 0.992661f, 0.993482f, 0.994229f, 0.994908f, 0.995524f, 0.996081f, 0.996583f, | |||
0.997035f, 0.997440f, 0.997802f, 0.998124f, 0.998409f, 0.998661f, 0.998882f, 0.999076f, | |||
0.999243f, 0.999387f, 0.999511f, 0.999615f, 0.999702f, 0.999775f, 0.999834f, 0.999881f, | |||
0.999918f, 0.999946f, 0.999966f, 0.999981f, 0.999990f, 0.999996f, 0.999999f, 1.000000f | |||
}; | |||
float symmetry_table_6[256] = { | |||
-0.400000f, -0.400000f, -0.400000f, -0.400000f, -0.399999f, -0.399998f, -0.399997f, -0.399995f, | |||
-0.399992f, -0.399989f, -0.399985f, -0.399979f, -0.399973f, -0.399965f, -0.399956f, -0.399946f, | |||
-0.399933f, -0.399919f, -0.399903f, -0.399885f, -0.399864f, -0.399840f, -0.399814f, -0.399785f, | |||
-0.399753f, -0.399718f, -0.399679f, -0.399636f, -0.399589f, -0.399538f, -0.399482f, -0.399421f, | |||
-0.399355f, -0.399284f, -0.399207f, -0.399123f, -0.399034f, -0.398937f, -0.398833f, -0.398721f, | |||
-0.398601f, -0.398473f, -0.398335f, -0.398188f, -0.398031f, -0.397863f, -0.397684f, -0.397493f, | |||
-0.397289f, -0.397073f, -0.396842f, -0.396597f, -0.396336f, -0.396059f, -0.395765f, -0.395452f, | |||
-0.395120f, -0.394768f, -0.394395f, -0.393999f, -0.393579f, -0.393134f, -0.392662f, -0.392162f, | |||
-0.391632f, -0.391071f, -0.390476f, -0.389846f, -0.389178f, -0.388471f, -0.387721f, -0.386926f, | |||
-0.386084f, -0.385192f, -0.384246f, -0.383243f, -0.382179f, -0.381050f, -0.379852f, -0.378581f, | |||
-0.377232f, -0.375798f, -0.374275f, -0.372655f, -0.370933f, -0.369102f, -0.367152f, -0.365075f, | |||
-0.362862f, -0.360503f, -0.357987f, -0.355302f, -0.352434f, -0.349371f, -0.346095f, -0.342592f, | |||
-0.338842f, -0.334828f, -0.330528f, -0.325920f, -0.320981f, -0.315687f, -0.310013f, -0.303931f, | |||
-0.297415f, -0.290439f, -0.282976f, -0.275004f, -0.266499f, -0.257444f, -0.247826f, -0.237638f, | |||
-0.226877f, -0.215553f, -0.203678f, -0.191275f, -0.178375f, -0.165012f, -0.151228f, -0.137068f, | |||
-0.122577f, -0.107804f, -0.092794f, -0.077591f, -0.062236f, -0.046768f, -0.031219f, -0.015621f, | |||
0.000000f, 0.015625f, 0.031249f, 0.046871f, 0.062490f, 0.078106f, 0.093716f, 0.109321f, | |||
0.124918f, 0.140506f, 0.156084f, 0.171650f, 0.187202f, 0.202740f, 0.218261f, 0.233762f, | |||
0.249243f, 0.264701f, 0.280132f, 0.295536f, 0.310909f, 0.326249f, 0.341552f, 0.356815f, | |||
0.372035f, 0.387208f, 0.402331f, 0.417399f, 0.432408f, 0.447354f, 0.462232f, 0.477036f, | |||
0.491762f, 0.506403f, 0.520954f, 0.535409f, 0.549760f, 0.564001f, 0.578125f, 0.592123f, | |||
0.605989f, 0.619714f, 0.633288f, 0.646704f, 0.659952f, 0.673023f, 0.685907f, 0.698593f, | |||
0.711074f, 0.723337f, 0.735373f, 0.747171f, 0.758723f, 0.770018f, 0.781047f, 0.791801f, | |||
0.802270f, 0.812447f, 0.822325f, 0.831897f, 0.841157f, 0.850101f, 0.858724f, 0.867024f, | |||
0.875000f, 0.882649f, 0.889974f, 0.896976f, 0.903657f, 0.910022f, 0.916075f, 0.921822f, | |||
0.927270f, 0.932426f, 0.937297f, 0.941893f, 0.946223f, 0.950296f, 0.954123f, 0.957712f, | |||
0.961074f, 0.964218f, 0.967157f, 0.969898f, 0.972452f, 0.974829f, 0.977038f, 0.979089f, | |||
0.980989f, 0.982748f, 0.984375f, 0.985876f, 0.987260f, 0.988534f, 0.989704f, 0.990778f, | |||
0.991762f, 0.992661f, 0.993482f, 0.994229f, 0.994908f, 0.995524f, 0.996081f, 0.996583f, | |||
0.997035f, 0.997440f, 0.997802f, 0.998124f, 0.998409f, 0.998661f, 0.998882f, 0.999076f, | |||
0.999243f, 0.999387f, 0.999511f, 0.999615f, 0.999702f, 0.999775f, 0.999834f, 0.999881f, | |||
0.999918f, 0.999946f, 0.999966f, 0.999981f, 0.999990f, 0.999996f, 0.999999f, 1.000000f | |||
}; | |||
float symmetry_table_7[256] = { | |||
-0.466667f, -0.466667f, -0.466667f, -0.466666f, -0.466665f, -0.466664f, -0.466662f, -0.466660f, | |||
-0.466657f, -0.466652f, -0.466646f, -0.466639f, -0.466631f, -0.466621f, -0.466608f, -0.466594f, | |||
-0.466578f, -0.466559f, -0.466537f, -0.466512f, -0.466484f, -0.466453f, -0.466418f, -0.466379f, | |||
-0.466336f, -0.466288f, -0.466235f, -0.466178f, -0.466114f, -0.466045f, -0.465969f, -0.465887f, | |||
-0.465798f, -0.465701f, -0.465596f, -0.465483f, -0.465361f, -0.465229f, -0.465087f, -0.464934f, | |||
-0.464771f, -0.464595f, -0.464407f, -0.464205f, -0.463990f, -0.463759f, -0.463513f, -0.463251f, | |||
-0.462971f, -0.462672f, -0.462354f, -0.462015f, -0.461654f, -0.461270f, -0.460862f, -0.460428f, | |||
-0.459967f, -0.459477f, -0.458956f, -0.458403f, -0.457817f, -0.457194f, -0.456532f, -0.455831f, | |||
-0.455086f, -0.454296f, -0.453458f, -0.452569f, -0.451625f, -0.450624f, -0.449562f, -0.448434f, | |||
-0.447238f, -0.445968f, -0.444620f, -0.443189f, -0.441669f, -0.440054f, -0.438338f, -0.436516f, | |||
-0.434578f, -0.432518f, -0.430327f, -0.427996f, -0.425515f, -0.422875f, -0.420063f, -0.417069f, | |||
-0.413879f, -0.410479f, -0.406856f, -0.402994f, -0.398875f, -0.394484f, -0.389802f, -0.384810f, | |||
-0.379489f, -0.373819f, -0.367779f, -0.361349f, -0.354510f, -0.347243f, -0.339529f, -0.331352f, | |||
-0.322701f, -0.313562f, -0.303931f, -0.293803f, -0.283181f, -0.272071f, -0.260484f, -0.248436f, | |||
-0.235946f, -0.223041f, -0.209746f, -0.196093f, -0.182113f, -0.167839f, -0.153304f, -0.138541f, | |||
-0.123580f, -0.108453f, -0.093188f, -0.077810f, -0.062344f, -0.046811f, -0.031232f, -0.015623f, | |||
0.000000f, 0.015625f, 0.031249f, 0.046871f, 0.062490f, 0.078106f, 0.093716f, 0.109321f, | |||
0.124918f, 0.140506f, 0.156084f, 0.171650f, 0.187202f, 0.202740f, 0.218261f, 0.233762f, | |||
0.249243f, 0.264701f, 0.280132f, 0.295536f, 0.310909f, 0.326249f, 0.341552f, 0.356815f, | |||
0.372035f, 0.387208f, 0.402331f, 0.417399f, 0.432408f, 0.447354f, 0.462232f, 0.477036f, | |||
0.491762f, 0.506403f, 0.520954f, 0.535409f, 0.549760f, 0.564001f, 0.578125f, 0.592123f, | |||
0.605989f, 0.619714f, 0.633288f, 0.646704f, 0.659952f, 0.673023f, 0.685907f, 0.698593f, | |||
0.711074f, 0.723337f, 0.735373f, 0.747171f, 0.758723f, 0.770018f, 0.781047f, 0.791801f, | |||
0.802270f, 0.812447f, 0.822325f, 0.831897f, 0.841157f, 0.850101f, 0.858724f, 0.867024f, | |||
0.875000f, 0.882649f, 0.889974f, 0.896976f, 0.903657f, 0.910022f, 0.916075f, 0.921822f, | |||
0.927270f, 0.932426f, 0.937297f, 0.941893f, 0.946223f, 0.950296f, 0.954123f, 0.957712f, | |||
0.961074f, 0.964218f, 0.967157f, 0.969898f, 0.972452f, 0.974829f, 0.977038f, 0.979089f, | |||
0.980989f, 0.982748f, 0.984375f, 0.985876f, 0.987260f, 0.988534f, 0.989704f, 0.990778f, | |||
0.991762f, 0.992661f, 0.993482f, 0.994229f, 0.994908f, 0.995524f, 0.996081f, 0.996583f, | |||
0.997035f, 0.997440f, 0.997802f, 0.998124f, 0.998409f, 0.998661f, 0.998882f, 0.999076f, | |||
0.999243f, 0.999387f, 0.999511f, 0.999615f, 0.999702f, 0.999775f, 0.999834f, 0.999881f, | |||
0.999918f, 0.999946f, 0.999966f, 0.999981f, 0.999990f, 0.999996f, 0.999999f, 1.000000f | |||
}; | |||
float symmetry_table_8[256] = { | |||
-0.533333f, -0.533333f, -0.533333f, -0.533333f, -0.533332f, -0.533330f, -0.533328f, -0.533325f, | |||
-0.533320f, -0.533314f, -0.533307f, -0.533297f, -0.533286f, -0.533273f, -0.533257f, -0.533238f, | |||
-0.533216f, -0.533191f, -0.533162f, -0.533130f, -0.533093f, -0.533051f, -0.533005f, -0.532953f, | |||
-0.532896f, -0.532832f, -0.532762f, -0.532685f, -0.532601f, -0.532508f, -0.532407f, -0.532297f, | |||
-0.532178f, -0.532048f, -0.531908f, -0.531756f, -0.531591f, -0.531414f, -0.531223f, -0.531018f, | |||
-0.530797f, -0.530560f, -0.530306f, -0.530033f, -0.529741f, -0.529428f, -0.529094f, -0.528737f, | |||
-0.528356f, -0.527949f, -0.527515f, -0.527052f, -0.526559f, -0.526033f, -0.525474f, -0.524878f, | |||
-0.524244f, -0.523570f, -0.522852f, -0.522090f, -0.521279f, -0.520418f, -0.519502f, -0.518529f, | |||
-0.517495f, -0.516397f, -0.515230f, -0.513991f, -0.512674f, -0.511276f, -0.509790f, -0.508211f, | |||
-0.506534f, -0.504753f, -0.502859f, -0.500848f, -0.498710f, -0.496437f, -0.494022f, -0.491454f, | |||
-0.488725f, -0.485822f, -0.482737f, -0.479456f, -0.475967f, -0.472257f, -0.468313f, -0.464120f, | |||
-0.459664f, -0.454928f, -0.449897f, -0.444556f, -0.438887f, -0.432874f, -0.426502f, -0.419754f, | |||
-0.412616f, -0.405075f, -0.397117f, -0.388734f, -0.379917f, -0.370659f, -0.360958f, -0.350815f, | |||
-0.340232f, -0.329217f, -0.317779f, -0.305932f, -0.293690f, -0.281074f, -0.268103f, -0.254800f, | |||
-0.241189f, -0.227294f, -0.213139f, -0.198750f, -0.184152f, -0.169367f, -0.154418f, -0.139327f, | |||
-0.124115f, -0.108799f, -0.093398f, -0.077927f, -0.062402f, -0.046835f, -0.031238f, -0.015624f, | |||
0.000000f, 0.015625f, 0.031249f, 0.046871f, 0.062490f, 0.078106f, 0.093716f, 0.109321f, | |||
0.124918f, 0.140506f, 0.156084f, 0.171650f, 0.187202f, 0.202740f, 0.218261f, 0.233762f, | |||
0.249243f, 0.264701f, 0.280132f, 0.295536f, 0.310909f, 0.326249f, 0.341552f, 0.356815f, | |||
0.372035f, 0.387208f, 0.402331f, 0.417399f, 0.432408f, 0.447354f, 0.462232f, 0.477036f, | |||
0.491762f, 0.506403f, 0.520954f, 0.535409f, 0.549760f, 0.564001f, 0.578125f, 0.592123f, | |||
0.605989f, 0.619714f, 0.633288f, 0.646704f, 0.659952f, 0.673023f, 0.685907f, 0.698593f, | |||
0.711074f, 0.723337f, 0.735373f, 0.747171f, 0.758723f, 0.770018f, 0.781047f, 0.791801f, | |||
0.802270f, 0.812447f, 0.822325f, 0.831897f, 0.841157f, 0.850101f, 0.858724f, 0.867024f, | |||
0.875000f, 0.882649f, 0.889974f, 0.896976f, 0.903657f, 0.910022f, 0.916075f, 0.921822f, | |||
0.927270f, 0.932426f, 0.937297f, 0.941893f, 0.946223f, 0.950296f, 0.954123f, 0.957712f, | |||
0.961074f, 0.964218f, 0.967157f, 0.969898f, 0.972452f, 0.974829f, 0.977038f, 0.979089f, | |||
0.980989f, 0.982748f, 0.984375f, 0.985876f, 0.987260f, 0.988534f, 0.989704f, 0.990778f, | |||
0.991762f, 0.992661f, 0.993482f, 0.994229f, 0.994908f, 0.995524f, 0.996081f, 0.996583f, | |||
0.997035f, 0.997440f, 0.997802f, 0.998124f, 0.998409f, 0.998661f, 0.998882f, 0.999076f, | |||
0.999243f, 0.999387f, 0.999511f, 0.999615f, 0.999702f, 0.999775f, 0.999834f, 0.999881f, | |||
0.999918f, 0.999946f, 0.999966f, 0.999981f, 0.999990f, 0.999996f, 0.999999f, 1.000000f | |||
}; | |||
float symmetry_table_9[256] = { | |||
-0.600000f, -0.600000f, -0.600000f, -0.599999f, -0.599998f, -0.599996f, -0.599993f, -0.599989f, | |||
-0.599983f, -0.599975f, -0.599965f, -0.599953f, -0.599939f, -0.599921f, -0.599900f, -0.599875f, | |||
-0.599847f, -0.599814f, -0.599776f, -0.599734f, -0.599685f, -0.599631f, -0.599570f, -0.599502f, | |||
-0.599426f, -0.599342f, -0.599250f, -0.599148f, -0.599036f, -0.598914f, -0.598780f, -0.598634f, | |||
-0.598475f, -0.598303f, -0.598116f, -0.597913f, -0.597694f, -0.597458f, -0.597203f, -0.596928f, | |||
-0.596632f, -0.596314f, -0.595972f, -0.595606f, -0.595213f, -0.594792f, -0.594341f, -0.593859f, | |||
-0.593343f, -0.592792f, -0.592204f, -0.591576f, -0.590905f, -0.590191f, -0.589428f, -0.588616f, | |||
-0.587750f, -0.586829f, -0.585847f, -0.584802f, -0.583690f, -0.582507f, -0.581248f, -0.579909f, | |||
-0.578484f, -0.576969f, -0.575359f, -0.573646f, -0.571826f, -0.569890f, -0.567833f, -0.565647f, | |||
-0.563323f, -0.560853f, -0.558229f, -0.555441f, -0.552478f, -0.549332f, -0.545990f, -0.542441f, | |||
-0.538673f, -0.534675f, -0.530432f, -0.525932f, -0.521161f, -0.516106f, -0.510752f, -0.505087f, | |||
-0.499096f, -0.492766f, -0.486085f, -0.479041f, -0.471622f, -0.463820f, -0.455626f, -0.447034f, | |||
-0.438039f, -0.428640f, -0.418836f, -0.408630f, -0.398026f, -0.387032f, -0.375656f, -0.363911f, | |||
-0.351811f, -0.339370f, -0.326606f, -0.313536f, -0.300179f, -0.286555f, -0.272683f, -0.258583f, | |||
-0.244274f, -0.229776f, -0.215106f, -0.200282f, -0.185322f, -0.170242f, -0.155055f, -0.139777f, | |||
-0.124420f, -0.108997f, -0.093518f, -0.077994f, -0.062435f, -0.046848f, -0.031242f, -0.015624f, | |||
0.000000f, 0.015625f, 0.031249f, 0.046871f, 0.062490f, 0.078106f, 0.093716f, 0.109321f, | |||
0.124918f, 0.140506f, 0.156084f, 0.171650f, 0.187202f, 0.202740f, 0.218261f, 0.233762f, | |||
0.249243f, 0.264701f, 0.280132f, 0.295536f, 0.310909f, 0.326249f, 0.341552f, 0.356815f, | |||
0.372035f, 0.387208f, 0.402331f, 0.417399f, 0.432408f, 0.447354f, 0.462232f, 0.477036f, | |||
0.491762f, 0.506403f, 0.520954f, 0.535409f, 0.549760f, 0.564001f, 0.578125f, 0.592123f, | |||
0.605989f, 0.619714f, 0.633288f, 0.646704f, 0.659952f, 0.673023f, 0.685907f, 0.698593f, | |||
0.711074f, 0.723337f, 0.735373f, 0.747171f, 0.758723f, 0.770018f, 0.781047f, 0.791801f, | |||
0.802270f, 0.812447f, 0.822325f, 0.831897f, 0.841157f, 0.850101f, 0.858724f, 0.867024f, | |||
0.875000f, 0.882649f, 0.889974f, 0.896976f, 0.903657f, 0.910022f, 0.916075f, 0.921822f, | |||
0.927270f, 0.932426f, 0.937297f, 0.941893f, 0.946223f, 0.950296f, 0.954123f, 0.957712f, | |||
0.961074f, 0.964218f, 0.967157f, 0.969898f, 0.972452f, 0.974829f, 0.977038f, 0.979089f, | |||
0.980989f, 0.982748f, 0.984375f, 0.985876f, 0.987260f, 0.988534f, 0.989704f, 0.990778f, | |||
0.991762f, 0.992661f, 0.993482f, 0.994229f, 0.994908f, 0.995524f, 0.996081f, 0.996583f, | |||
0.997035f, 0.997440f, 0.997802f, 0.998124f, 0.998409f, 0.998661f, 0.998882f, 0.999076f, | |||
0.999243f, 0.999387f, 0.999511f, 0.999615f, 0.999702f, 0.999775f, 0.999834f, 0.999881f, | |||
0.999918f, 0.999946f, 0.999966f, 0.999981f, 0.999990f, 0.999996f, 0.999999f, 1.000000f | |||
}; | |||
float symmetry_table_10[256] = { | |||
-0.666667f, -0.666667f, -0.666666f, -0.666666f, -0.666664f, -0.666661f, -0.666657f, -0.666652f, | |||
-0.666644f, -0.666634f, -0.666622f, -0.666606f, -0.666587f, -0.666564f, -0.666537f, -0.666505f, | |||
-0.666468f, -0.666425f, -0.666376f, -0.666320f, -0.666257f, -0.666185f, -0.666105f, -0.666016f, | |||
-0.665917f, -0.665807f, -0.665685f, -0.665551f, -0.665404f, -0.665242f, -0.665065f, -0.664872f, | |||
-0.664662f, -0.664434f, -0.664186f, -0.663917f, -0.663625f, -0.663310f, -0.662970f, -0.662604f, | |||
-0.662208f, -0.661783f, -0.661326f, -0.660834f, -0.660307f, -0.659741f, -0.659135f, -0.658485f, | |||
-0.657789f, -0.657045f, -0.656250f, -0.655400f, -0.654491f, -0.653522f, -0.652486f, -0.651382f, | |||
-0.650204f, -0.648948f, -0.647610f, -0.646184f, -0.644665f, -0.643047f, -0.641325f, -0.639491f, | |||
-0.637540f, -0.635465f, -0.633257f, -0.630910f, -0.628414f, -0.625761f, -0.622943f, -0.619948f, | |||
-0.616769f, -0.613393f, -0.609811f, -0.606010f, -0.601981f, -0.597711f, -0.593187f, -0.588399f, | |||
-0.583333f, -0.577978f, -0.572322f, -0.566353f, -0.560059f, -0.553431f, -0.546459f, -0.539134f, | |||
-0.531447f, -0.523393f, -0.514967f, -0.506166f, -0.496987f, -0.487431f, -0.477501f, -0.467200f, | |||
-0.456535f, -0.445512f, -0.434141f, -0.422433f, -0.410401f, -0.398057f, -0.385417f, -0.372494f, | |||
-0.359306f, -0.345868f, -0.332196f, -0.318307f, -0.304217f, -0.289941f, -0.275495f, -0.260893f, | |||
-0.246151f, -0.231281f, -0.216296f, -0.201208f, -0.186029f, -0.170770f, -0.155440f, -0.140049f, | |||
-0.124605f, -0.109117f, -0.093591f, -0.078035f, -0.062455f, -0.046857f, -0.031245f, -0.015624f, | |||
0.000000f, 0.015625f, 0.031249f, 0.046871f, 0.062490f, 0.078106f, 0.093716f, 0.109321f, | |||
0.124918f, 0.140506f, 0.156084f, 0.171650f, 0.187202f, 0.202740f, 0.218261f, 0.233762f, | |||
0.249243f, 0.264701f, 0.280132f, 0.295536f, 0.310909f, 0.326249f, 0.341552f, 0.356815f, | |||
0.372035f, 0.387208f, 0.402331f, 0.417399f, 0.432408f, 0.447354f, 0.462232f, 0.477036f, | |||
0.491762f, 0.506403f, 0.520954f, 0.535409f, 0.549760f, 0.564001f, 0.578125f, 0.592123f, | |||
0.605989f, 0.619714f, 0.633288f, 0.646704f, 0.659952f, 0.673023f, 0.685907f, 0.698593f, | |||
0.711074f, 0.723337f, 0.735373f, 0.747171f, 0.758723f, 0.770018f, 0.781047f, 0.791801f, | |||
0.802270f, 0.812447f, 0.822325f, 0.831897f, 0.841157f, 0.850101f, 0.858724f, 0.867024f, | |||
0.875000f, 0.882649f, 0.889974f, 0.896976f, 0.903657f, 0.910022f, 0.916075f, 0.921822f, | |||
0.927270f, 0.932426f, 0.937297f, 0.941893f, 0.946223f, 0.950296f, 0.954123f, 0.957712f, | |||
0.961074f, 0.964218f, 0.967157f, 0.969898f, 0.972452f, 0.974829f, 0.977038f, 0.979089f, | |||
0.980989f, 0.982748f, 0.984375f, 0.985876f, 0.987260f, 0.988534f, 0.989704f, 0.990778f, | |||
0.991762f, 0.992661f, 0.993482f, 0.994229f, 0.994908f, 0.995524f, 0.996081f, 0.996583f, | |||
0.997035f, 0.997440f, 0.997802f, 0.998124f, 0.998409f, 0.998661f, 0.998882f, 0.999076f, | |||
0.999243f, 0.999387f, 0.999511f, 0.999615f, 0.999702f, 0.999775f, 0.999834f, 0.999881f, | |||
0.999918f, 0.999946f, 0.999966f, 0.999981f, 0.999990f, 0.999996f, 0.999999f, 1.000000f | |||
}; | |||
float symmetry_table_11[256] = { | |||
-0.733333f, -0.733333f, -0.733333f, -0.733332f, -0.733330f, -0.733327f, -0.733321f, -0.733314f, | |||
-0.733304f, -0.733292f, -0.733275f, -0.733255f, -0.733230f, -0.733201f, -0.733165f, -0.733124f, | |||
-0.733075f, -0.733020f, -0.732955f, -0.732882f, -0.732800f, -0.732706f, -0.732602f, -0.732485f, | |||
-0.732354f, -0.732210f, -0.732050f, -0.731873f, -0.731679f, -0.731466f, -0.731233f, -0.730978f, | |||
-0.730700f, -0.730397f, -0.730067f, -0.729710f, -0.729322f, -0.728903f, -0.728449f, -0.727960f, | |||
-0.727432f, -0.726862f, -0.726250f, -0.725591f, -0.724882f, -0.724122f, -0.723305f, -0.722430f, | |||
-0.721492f, -0.720487f, -0.719412f, -0.718261f, -0.717032f, -0.715717f, -0.714313f, -0.712813f, | |||
-0.711213f, -0.709506f, -0.707686f, -0.705745f, -0.703678f, -0.701476f, -0.699131f, -0.696636f, | |||
-0.693982f, -0.691160f, -0.688161f, -0.684974f, -0.681591f, -0.678000f, -0.674191f, -0.670154f, | |||
-0.665877f, -0.661350f, -0.656561f, -0.651499f, -0.646154f, -0.640515f, -0.634571f, -0.628313f, | |||
-0.621732f, -0.614820f, -0.607570f, -0.599974f, -0.592029f, -0.583730f, -0.575074f, -0.566062f, | |||
-0.556694f, -0.546972f, -0.536899f, -0.526482f, -0.515726f, -0.504639f, -0.493233f, -0.481515f, | |||
-0.469498f, -0.457195f, -0.444618f, -0.431780f, -0.418696f, -0.405378f, -0.391842f, -0.378100f, | |||
-0.364167f, -0.350056f, -0.335780f, -0.321352f, -0.306784f, -0.292087f, -0.277273f, -0.262352f, | |||
-0.247335f, -0.232229f, -0.217046f, -0.201792f, -0.186475f, -0.171104f, -0.155684f, -0.140221f, | |||
-0.124723f, -0.109194f, -0.093638f, -0.078062f, -0.062468f, -0.046862f, -0.031246f, -0.015625f, | |||
0.000000f, 0.015625f, 0.031249f, 0.046871f, 0.062490f, 0.078106f, 0.093716f, 0.109321f, | |||
0.124918f, 0.140506f, 0.156084f, 0.171650f, 0.187202f, 0.202740f, 0.218261f, 0.233762f, | |||
0.249243f, 0.264701f, 0.280132f, 0.295536f, 0.310909f, 0.326249f, 0.341552f, 0.356815f, | |||
0.372035f, 0.387208f, 0.402331f, 0.417399f, 0.432408f, 0.447354f, 0.462232f, 0.477036f, | |||
0.491762f, 0.506403f, 0.520954f, 0.535409f, 0.549760f, 0.564001f, 0.578125f, 0.592123f, | |||
0.605989f, 0.619714f, 0.633288f, 0.646704f, 0.659952f, 0.673023f, 0.685907f, 0.698593f, | |||
0.711074f, 0.723337f, 0.735373f, 0.747171f, 0.758723f, 0.770018f, 0.781047f, 0.791801f, | |||
0.802270f, 0.812447f, 0.822325f, 0.831897f, 0.841157f, 0.850101f, 0.858724f, 0.867024f, | |||
0.875000f, 0.882649f, 0.889974f, 0.896976f, 0.903657f, 0.910022f, 0.916075f, 0.921822f, | |||
0.927270f, 0.932426f, 0.937297f, 0.941893f, 0.946223f, 0.950296f, 0.954123f, 0.957712f, | |||
0.961074f, 0.964218f, 0.967157f, 0.969898f, 0.972452f, 0.974829f, 0.977038f, 0.979089f, | |||
0.980989f, 0.982748f, 0.984375f, 0.985876f, 0.987260f, 0.988534f, 0.989704f, 0.990778f, | |||
0.991762f, 0.992661f, 0.993482f, 0.994229f, 0.994908f, 0.995524f, 0.996081f, 0.996583f, | |||
0.997035f, 0.997440f, 0.997802f, 0.998124f, 0.998409f, 0.998661f, 0.998882f, 0.999076f, | |||
0.999243f, 0.999387f, 0.999511f, 0.999615f, 0.999702f, 0.999775f, 0.999834f, 0.999881f, | |||
0.999918f, 0.999946f, 0.999966f, 0.999981f, 0.999990f, 0.999996f, 0.999999f, 1.000000f | |||
}; | |||
float symmetry_table_12[256] = { | |||
-0.800000f, -0.800000f, -0.800000f, -0.799998f, -0.799996f, -0.799991f, -0.799985f, -0.799975f, | |||
-0.799963f, -0.799946f, -0.799925f, -0.799899f, -0.799867f, -0.799828f, -0.799782f, -0.799728f, | |||
-0.799665f, -0.799592f, -0.799509f, -0.799413f, -0.799305f, -0.799183f, -0.799045f, -0.798892f, | |||
-0.798720f, -0.798530f, -0.798319f, -0.798086f, -0.797830f, -0.797548f, -0.797238f, -0.796900f, | |||
-0.796531f, -0.796128f, -0.795690f, -0.795213f, -0.794696f, -0.794136f, -0.793530f, -0.792874f, | |||
-0.792166f, -0.791402f, -0.790579f, -0.789693f, -0.788739f, -0.787714f, -0.786613f, -0.785431f, | |||
-0.784164f, -0.782805f, -0.781350f, -0.779792f, -0.778126f, -0.776344f, -0.774441f, -0.772407f, | |||
-0.770238f, -0.767923f, -0.765456f, -0.762828f, -0.760029f, -0.757050f, -0.753883f, -0.750518f, | |||
-0.746943f, -0.743150f, -0.739128f, -0.734866f, -0.730354f, -0.725583f, -0.720540f, -0.715218f, | |||
-0.709606f, -0.703694f, -0.697475f, -0.690940f, -0.684083f, -0.676897f, -0.669377f, -0.661520f, | |||
-0.653321f, -0.644781f, -0.635898f, -0.626674f, -0.617111f, -0.607214f, -0.596986f, -0.586434f, | |||
-0.575567f, -0.564391f, -0.552918f, -0.541156f, -0.529117f, -0.516812f, -0.504253f, -0.491452f, | |||
-0.478422f, -0.465174f, -0.451722f, -0.438077f, -0.424251f, -0.410255f, -0.396103f, -0.381803f, | |||
-0.367368f, -0.352806f, -0.338128f, -0.323344f, -0.308461f, -0.293488f, -0.278434f, -0.263304f, | |||
-0.248107f, -0.232849f, -0.217536f, -0.202174f, -0.186768f, -0.171323f, -0.155844f, -0.140335f, | |||
-0.124801f, -0.109244f, -0.093669f, -0.078079f, -0.062477f, -0.046865f, -0.031247f, -0.015625f, | |||
0.000000f, 0.015625f, 0.031249f, 0.046871f, 0.062490f, 0.078106f, 0.093716f, 0.109321f, | |||
0.124918f, 0.140506f, 0.156084f, 0.171650f, 0.187202f, 0.202740f, 0.218261f, 0.233762f, | |||
0.249243f, 0.264701f, 0.280132f, 0.295536f, 0.310909f, 0.326249f, 0.341552f, 0.356815f, | |||
0.372035f, 0.387208f, 0.402331f, 0.417399f, 0.432408f, 0.447354f, 0.462232f, 0.477036f, | |||
0.491762f, 0.506403f, 0.520954f, 0.535409f, 0.549760f, 0.564001f, 0.578125f, 0.592123f, | |||
0.605989f, 0.619714f, 0.633288f, 0.646704f, 0.659952f, 0.673023f, 0.685907f, 0.698593f, | |||
0.711074f, 0.723337f, 0.735373f, 0.747171f, 0.758723f, 0.770018f, 0.781047f, 0.791801f, | |||
0.802270f, 0.812447f, 0.822325f, 0.831897f, 0.841157f, 0.850101f, 0.858724f, 0.867024f, | |||
0.875000f, 0.882649f, 0.889974f, 0.896976f, 0.903657f, 0.910022f, 0.916075f, 0.921822f, | |||
0.927270f, 0.932426f, 0.937297f, 0.941893f, 0.946223f, 0.950296f, 0.954123f, 0.957712f, | |||
0.961074f, 0.964218f, 0.967157f, 0.969898f, 0.972452f, 0.974829f, 0.977038f, 0.979089f, | |||
0.980989f, 0.982748f, 0.984375f, 0.985876f, 0.987260f, 0.988534f, 0.989704f, 0.990778f, | |||
0.991762f, 0.992661f, 0.993482f, 0.994229f, 0.994908f, 0.995524f, 0.996081f, 0.996583f, | |||
0.997035f, 0.997440f, 0.997802f, 0.998124f, 0.998409f, 0.998661f, 0.998882f, 0.999076f, | |||
0.999243f, 0.999387f, 0.999511f, 0.999615f, 0.999702f, 0.999775f, 0.999834f, 0.999881f, | |||
0.999918f, 0.999946f, 0.999966f, 0.999981f, 0.999990f, 0.999996f, 0.999999f, 1.000000f | |||
}; | |||
float symmetry_table_13[256] = { | |||
-0.866667f, -0.866667f, -0.866666f, -0.866664f, -0.866661f, -0.866655f, -0.866647f, -0.866635f, | |||
-0.866618f, -0.866597f, -0.866570f, -0.866535f, -0.866494f, -0.866443f, -0.866383f, -0.866313f, | |||
-0.866230f, -0.866135f, -0.866025f, -0.865900f, -0.865757f, -0.865596f, -0.865415f, -0.865212f, | |||
-0.864986f, -0.864734f, -0.864455f, -0.864146f, -0.863805f, -0.863430f, -0.863019f, -0.862568f, | |||
-0.862075f, -0.861536f, -0.860950f, -0.860312f, -0.859619f, -0.858866f, -0.858051f, -0.857169f, | |||
-0.856216f, -0.855186f, -0.854075f, -0.852878f, -0.851590f, -0.850204f, -0.848714f, -0.847114f, | |||
-0.845398f, -0.843558f, -0.841586f, -0.839477f, -0.837220f, -0.834808f, -0.832233f, -0.829485f, | |||
-0.826556f, -0.823435f, -0.820114f, -0.816581f, -0.812828f, -0.808845f, -0.804621f, -0.800146f, | |||
-0.795410f, -0.790404f, -0.785119f, -0.779546f, -0.773676f, -0.767502f, -0.761016f, -0.754213f, | |||
-0.747087f, -0.739635f, -0.731852f, -0.723737f, -0.715290f, -0.706510f, -0.697400f, -0.687961f, | |||
-0.678199f, -0.668118f, -0.657724f, -0.647026f, -0.636029f, -0.624744f, -0.613180f, -0.601348f, | |||
-0.589256f, -0.576917f, -0.564342f, -0.551541f, -0.538527f, -0.525309f, -0.511900f, -0.498310f, | |||
-0.484549f, -0.470629f, -0.456560f, -0.442350f, -0.428010f, -0.413549f, -0.398974f, -0.384295f, | |||
-0.369519f, -0.354653f, -0.339705f, -0.324681f, -0.309587f, -0.294429f, -0.279214f, -0.263945f, | |||
-0.248628f, -0.233267f, -0.217867f, -0.202433f, -0.186966f, -0.171472f, -0.155953f, -0.140413f, | |||
-0.124854f, -0.109279f, -0.093691f, -0.078091f, -0.062483f, -0.046868f, -0.031248f, -0.015625f, | |||
0.000000f, 0.015625f, 0.031249f, 0.046871f, 0.062490f, 0.078106f, 0.093716f, 0.109321f, | |||
0.124918f, 0.140506f, 0.156084f, 0.171650f, 0.187202f, 0.202740f, 0.218261f, 0.233762f, | |||
0.249243f, 0.264701f, 0.280132f, 0.295536f, 0.310909f, 0.326249f, 0.341552f, 0.356815f, | |||
0.372035f, 0.387208f, 0.402331f, 0.417399f, 0.432408f, 0.447354f, 0.462232f, 0.477036f, | |||
0.491762f, 0.506403f, 0.520954f, 0.535409f, 0.549760f, 0.564001f, 0.578125f, 0.592123f, | |||
0.605989f, 0.619714f, 0.633288f, 0.646704f, 0.659952f, 0.673023f, 0.685907f, 0.698593f, | |||
0.711074f, 0.723337f, 0.735373f, 0.747171f, 0.758723f, 0.770018f, 0.781047f, 0.791801f, | |||
0.802270f, 0.812447f, 0.822325f, 0.831897f, 0.841157f, 0.850101f, 0.858724f, 0.867024f, | |||
0.875000f, 0.882649f, 0.889974f, 0.896976f, 0.903657f, 0.910022f, 0.916075f, 0.921822f, | |||
0.927270f, 0.932426f, 0.937297f, 0.941893f, 0.946223f, 0.950296f, 0.954123f, 0.957712f, | |||
0.961074f, 0.964218f, 0.967157f, 0.969898f, 0.972452f, 0.974829f, 0.977038f, 0.979089f, | |||
0.980989f, 0.982748f, 0.984375f, 0.985876f, 0.987260f, 0.988534f, 0.989704f, 0.990778f, | |||
0.991762f, 0.992661f, 0.993482f, 0.994229f, 0.994908f, 0.995524f, 0.996081f, 0.996583f, | |||
0.997035f, 0.997440f, 0.997802f, 0.998124f, 0.998409f, 0.998661f, 0.998882f, 0.999076f, | |||
0.999243f, 0.999387f, 0.999511f, 0.999615f, 0.999702f, 0.999775f, 0.999834f, 0.999881f, | |||
0.999918f, 0.999946f, 0.999966f, 0.999981f, 0.999990f, 0.999996f, 0.999999f, 1.000000f | |||
}; | |||
float symmetry_table_14[256] = { | |||
-0.933333f, -0.933333f, -0.933332f, -0.933330f, -0.933326f, -0.933319f, -0.933308f, -0.933292f, | |||
-0.933271f, -0.933243f, -0.933207f, -0.933162f, -0.933107f, -0.933041f, -0.932963f, -0.932870f, | |||
-0.932761f, -0.932635f, -0.932490f, -0.932324f, -0.932136f, -0.931922f, -0.931682f, -0.931413f, | |||
-0.931111f, -0.930775f, -0.930403f, -0.929990f, -0.929534f, -0.929032f, -0.928480f, -0.927874f, | |||
-0.927211f, -0.926487f, -0.925697f, -0.924836f, -0.923900f, -0.922883f, -0.921781f, -0.920587f, | |||
-0.919295f, -0.917900f, -0.916394f, -0.914770f, -0.913022f, -0.911142f, -0.909121f, -0.906952f, | |||
-0.904625f, -0.902133f, -0.899466f, -0.896614f, -0.893569f, -0.890320f, -0.886857f, -0.883171f, | |||
-0.879252f, -0.875090f, -0.870674f, -0.865996f, -0.861046f, -0.855816f, -0.850296f, -0.844480f, | |||
-0.838360f, -0.831930f, -0.825184f, -0.818118f, -0.810729f, -0.803014f, -0.794973f, -0.786603f, | |||
-0.777908f, -0.768889f, -0.759549f, -0.749893f, -0.739926f, -0.729655f, -0.719086f, -0.708227f, | |||
-0.697088f, -0.685677f, -0.674004f, -0.662078f, -0.649911f, -0.637512f, -0.624893f, -0.612063f, | |||
-0.599033f, -0.585813f, -0.572414f, -0.558845f, -0.545117f, -0.531238f, -0.517218f, -0.503066f, | |||
-0.488789f, -0.474396f, -0.459895f, -0.445293f, -0.430597f, -0.415813f, -0.400948f, -0.386007f, | |||
-0.370997f, -0.355923f, -0.340789f, -0.325601f, -0.310363f, -0.295078f, -0.279752f, -0.264387f, | |||
-0.248988f, -0.233557f, -0.218097f, -0.202612f, -0.187104f, -0.171576f, -0.156029f, -0.140467f, | |||
-0.124891f, -0.109303f, -0.093706f, -0.078100f, -0.062487f, -0.046870f, -0.031248f, -0.015625f, | |||
0.000000f, 0.015625f, 0.031249f, 0.046871f, 0.062490f, 0.078106f, 0.093716f, 0.109321f, | |||
0.124918f, 0.140506f, 0.156084f, 0.171650f, 0.187202f, 0.202740f, 0.218261f, 0.233762f, | |||
0.249243f, 0.264701f, 0.280132f, 0.295536f, 0.310909f, 0.326249f, 0.341552f, 0.356815f, | |||
0.372035f, 0.387208f, 0.402331f, 0.417399f, 0.432408f, 0.447354f, 0.462232f, 0.477036f, | |||
0.491762f, 0.506403f, 0.520954f, 0.535409f, 0.549760f, 0.564001f, 0.578125f, 0.592123f, | |||
0.605989f, 0.619714f, 0.633288f, 0.646704f, 0.659952f, 0.673023f, 0.685907f, 0.698593f, | |||
0.711074f, 0.723337f, 0.735373f, 0.747171f, 0.758723f, 0.770018f, 0.781047f, 0.791801f, | |||
0.802270f, 0.812447f, 0.822325f, 0.831897f, 0.841157f, 0.850101f, 0.858724f, 0.867024f, | |||
0.875000f, 0.882649f, 0.889974f, 0.896976f, 0.903657f, 0.910022f, 0.916075f, 0.921822f, | |||
0.927270f, 0.932426f, 0.937297f, 0.941893f, 0.946223f, 0.950296f, 0.954123f, 0.957712f, | |||
0.961074f, 0.964218f, 0.967157f, 0.969898f, 0.972452f, 0.974829f, 0.977038f, 0.979089f, | |||
0.980989f, 0.982748f, 0.984375f, 0.985876f, 0.987260f, 0.988534f, 0.989704f, 0.990778f, | |||
0.991762f, 0.992661f, 0.993482f, 0.994229f, 0.994908f, 0.995524f, 0.996081f, 0.996583f, | |||
0.997035f, 0.997440f, 0.997802f, 0.998124f, 0.998409f, 0.998661f, 0.998882f, 0.999076f, | |||
0.999243f, 0.999387f, 0.999511f, 0.999615f, 0.999702f, 0.999775f, 0.999834f, 0.999881f, | |||
0.999918f, 0.999946f, 0.999966f, 0.999981f, 0.999990f, 0.999996f, 0.999999f, 1.000000f | |||
}; | |||
float symmetry_table_15[256] = { | |||
-1.000000f, -1.000000f, -0.999999f, -0.999996f, -0.999990f, -0.999981f, -0.999966f, -0.999946f, | |||
-0.999918f, -0.999881f, -0.999834f, -0.999775f, -0.999702f, -0.999615f, -0.999511f, -0.999387f, | |||
-0.999243f, -0.999076f, -0.998882f, -0.998661f, -0.998409f, -0.998124f, -0.997802f, -0.997440f, | |||
-0.997035f, -0.996583f, -0.996081f, -0.995524f, -0.994908f, -0.994229f, -0.993482f, -0.992661f, | |||
-0.991762f, -0.990778f, -0.989704f, -0.988534f, -0.987260f, -0.985876f, -0.984375f, -0.982748f, | |||
-0.980989f, -0.979089f, -0.977038f, -0.974829f, -0.972452f, -0.969898f, -0.967157f, -0.964218f, | |||
-0.961074f, -0.957712f, -0.954123f, -0.950296f, -0.946223f, -0.941893f, -0.937297f, -0.932426f, | |||
-0.927270f, -0.921822f, -0.916075f, -0.910022f, -0.903657f, -0.896976f, -0.889974f, -0.882649f, | |||
-0.875000f, -0.867024f, -0.858724f, -0.850101f, -0.841157f, -0.831897f, -0.822325f, -0.812447f, | |||
-0.802270f, -0.791801f, -0.781047f, -0.770018f, -0.758723f, -0.747171f, -0.735373f, -0.723337f, | |||
-0.711074f, -0.698593f, -0.685907f, -0.673023f, -0.659952f, -0.646704f, -0.633288f, -0.619714f, | |||
-0.605989f, -0.592123f, -0.578125f, -0.564001f, -0.549760f, -0.535409f, -0.520954f, -0.506403f, | |||
-0.491762f, -0.477036f, -0.462232f, -0.447354f, -0.432408f, -0.417399f, -0.402331f, -0.387208f, | |||
-0.372035f, -0.356815f, -0.341552f, -0.326249f, -0.310909f, -0.295536f, -0.280132f, -0.264701f, | |||
-0.249243f, -0.233762f, -0.218261f, -0.202740f, -0.187202f, -0.171650f, -0.156084f, -0.140506f, | |||
-0.124918f, -0.109321f, -0.093716f, -0.078106f, -0.062490f, -0.046871f, -0.031249f, -0.015625f, | |||
0.000000f, 0.015625f, 0.031249f, 0.046871f, 0.062490f, 0.078106f, 0.093716f, 0.109321f, | |||
0.124918f, 0.140506f, 0.156084f, 0.171650f, 0.187202f, 0.202740f, 0.218261f, 0.233762f, | |||
0.249243f, 0.264701f, 0.280132f, 0.295536f, 0.310909f, 0.326249f, 0.341552f, 0.356815f, | |||
0.372035f, 0.387208f, 0.402331f, 0.417399f, 0.432408f, 0.447354f, 0.462232f, 0.477036f, | |||
0.491762f, 0.506403f, 0.520954f, 0.535409f, 0.549760f, 0.564001f, 0.578125f, 0.592123f, | |||
0.605989f, 0.619714f, 0.633288f, 0.646704f, 0.659952f, 0.673023f, 0.685907f, 0.698593f, | |||
0.711074f, 0.723337f, 0.735373f, 0.747171f, 0.758723f, 0.770018f, 0.781047f, 0.791801f, | |||
0.802270f, 0.812447f, 0.822325f, 0.831897f, 0.841157f, 0.850101f, 0.858724f, 0.867024f, | |||
0.875000f, 0.882649f, 0.889974f, 0.896976f, 0.903657f, 0.910022f, 0.916075f, 0.921822f, | |||
0.927270f, 0.932426f, 0.937297f, 0.941893f, 0.946223f, 0.950296f, 0.954123f, 0.957712f, | |||
0.961074f, 0.964218f, 0.967157f, 0.969898f, 0.972452f, 0.974829f, 0.977038f, 0.979089f, | |||
0.980989f, 0.982748f, 0.984375f, 0.985876f, 0.987260f, 0.988534f, 0.989704f, 0.990778f, | |||
0.991762f, 0.992661f, 0.993482f, 0.994229f, 0.994908f, 0.995524f, 0.996081f, 0.996583f, | |||
0.997035f, 0.997440f, 0.997802f, 0.998124f, 0.998409f, 0.998661f, 0.998882f, 0.999076f, | |||
0.999243f, 0.999387f, 0.999511f, 0.999615f, 0.999702f, 0.999775f, 0.999834f, 0.999881f, | |||
0.999918f, 0.999946f, 0.999966f, 0.999981f, 0.999990f, 0.999996f, 0.999999f, 1.000000f | |||
}; |
@@ -0,0 +1,84 @@ | |||
#pragma once | |||
#include <assert.h> | |||
#include <map> | |||
#include <vector> | |||
#include "LookupTable.h" | |||
using Spline = std::vector< std::pair<double, double> >; | |||
class NonUniformLookup | |||
{ | |||
public: | |||
void add(double x, double y) | |||
{ | |||
data[x] = y; | |||
} | |||
double lookup(double x) | |||
{ | |||
// printf("lookup %f\n", x); | |||
auto l = data.lower_bound(x); | |||
assert(l != data.end()); | |||
// printf("lower = %f, %f\n", l->first, l->second); | |||
auto p = l; | |||
p--; | |||
if (p == data.end()) { | |||
assert(l->first == x); | |||
return l->second; | |||
} | |||
assert(p != data.end()); | |||
// printf("p = %f, %f\n", p->first, p->second); | |||
// construct line y = y0 + (y1 -y0)/(x1 - x0) * x-x0; | |||
// = b + a(x -b); | |||
const double b = p->second; | |||
const double a = (l->second - p->second) / (l->first - p->first); | |||
const double ret = b + a * (x - p->first); | |||
// printf("ret = %f\n", ret); | |||
return ret; | |||
} | |||
private: | |||
std::map<double, double> data; | |||
}; | |||
class AsymWaveShaper | |||
{ | |||
public: | |||
const static int iNumPoints = 256; | |||
const static int iSymmetryTables = 16; | |||
private: | |||
LookupTableParams<float> tables[iSymmetryTables]; | |||
public: | |||
AsymWaveShaper(); | |||
float lookup(float x, int index) const | |||
{ | |||
float x_scaled = 0; | |||
if (x >= 1) { | |||
x_scaled = iNumPoints - 1; | |||
} else if (x < -1) { | |||
x_scaled = 0; | |||
} else { | |||
x_scaled = (x + 1) * iNumPoints / 2; | |||
} | |||
assert(index >= 0 && index < iSymmetryTables); | |||
const LookupTableParams<float>& table = tables[index]; | |||
// TODO: we are going outside of domain!. | |||
const float y = LookupTable<float>::lookup(table, x_scaled, true); | |||
// printf("lookup %f -> %f ret %f\n", x, x_scaled, y); | |||
return y; | |||
} | |||
static void genTableValues(const Spline& spline, int numPoints); | |||
static void genTable(int index, double symmetry); | |||
static Spline makeSplineRight(double symmetry); | |||
static Spline makeSplineLeft(double symmetry); | |||
static std::pair<double, double> calcPoint(const Spline& spline, double t); | |||
}; | |||
@@ -24,6 +24,16 @@ public: | |||
return 20 * log(g) / Ln10; | |||
} | |||
static double cents(double f1, double f2) | |||
{ | |||
return 1200 * std::log2(f1 / f2); | |||
} | |||
static double acents(double f1, double f2) | |||
{ | |||
return std::abs(cents(f1, f2)); | |||
} | |||
static double gainFromDb(double db) | |||
{ | |||
return std::exp(Ln10 * db / 20.0); | |||
@@ -32,6 +42,9 @@ public: | |||
/** | |||
* Returns a function that generates one period of sin for x = {0..1}. | |||
* Range (output) is -1 to 1. | |||
* | |||
* All makeFunc_xxx functions return functions that are not optimized. | |||
* They do not use lookup tables. | |||
*/ | |||
static std::function<double(double)> makeFunc_Sin(); | |||
@@ -53,7 +66,8 @@ public: | |||
/** | |||
* ScaleFun is a function the combines CV, knob, and trim into a voltage. | |||
* Typically a ScaleFun is like an "attenuverter" | |||
* Typically a ScaleFun is like an "attenuverter", where the trim input | |||
* is the attenuverter. | |||
*/ | |||
template <typename T> | |||
using ScaleFun = std::function<T(T cv, T knob, T trim)>; | |||
@@ -66,6 +80,10 @@ public: | |||
* | |||
* This particular function is used when knobs are -5..5, | |||
* and CV range is -5..5. | |||
* | |||
* | |||
* Can easily be used to add, clip and scale just a knob an a CV by | |||
* passing 1 for the trim param. | |||
*/ | |||
template <typename T> | |||
static ScaleFun<T> makeLinearScaler(T y0, T y1) | |||
@@ -87,6 +105,67 @@ public: | |||
* Details the same as makeLinearScaler except that the CV | |||
* scaling will be exponential for most values, becoming | |||
* linear near zero. | |||
* | |||
* Note that the cv and knob will have linear taper, only the | |||
* attenuverter is audio taper. | |||
* | |||
* Implemented with a cached lookup table - | |||
* only the final scaling is done at run time | |||
*/ | |||
static ScaleFun<float> makeBipolarAudioScaler(float y0, float y1); | |||
static ScaleFun<float> makeScalerWithBipolarAudioTrim(float y0, float y1); | |||
/** | |||
* SimpleScaleFun is a function the combines CV, and knob, into a voltage. | |||
* Usually in a synth module that has knob and cv, but no trim. | |||
* | |||
* cv and knob are -5 to +5. They are summed, and the sum is limited to 5, -5. | |||
* Then the sum gets an audio taper | |||
*/ | |||
template <typename T> | |||
using SimpleScaleFun = std::function<T(T cv, T knob)>; | |||
/** | |||
* maps +/- 5 volts from cv and knob to y0, y1 | |||
* with an audio taper. | |||
*/ | |||
static SimpleScaleFun<float> makeSimpleScalerAudioTaper(float y0, float y1); | |||
template <typename T> | |||
static std::pair<T, T> getMinMax(const T* data, int numSamples) | |||
{ | |||
T min = 1, max = -1; | |||
for (int i = 0; i < numSamples; ++i) { | |||
const T x = data[i]; | |||
min = std::min(min, x); | |||
max = std::max(max, x); | |||
} | |||
return std::pair<T, T>(min, max); | |||
} | |||
/** | |||
* A random number generator function. uniform random 0..1 | |||
*/ | |||
using RandomUniformFunc = std::function<float(void)>; | |||
static RandomUniformFunc random(); | |||
/** | |||
* Folds numbers between +1 and -1 | |||
*/ | |||
static inline float fold(float x) | |||
{ | |||
float fold; | |||
const float bias = (x < 0) ? -1.f : 1.f; | |||
int phase = int((x + bias) / 2.f); | |||
bool isEven = !(phase & 1); | |||
// printf(" wrap(%f) phase=%d, isEven=%d", x, phase, isEven); | |||
if (isEven) { | |||
fold = x - 2.f * phase; | |||
} else { | |||
fold = -x + 2.f * phase; | |||
} | |||
// printf(" y=%f\n", wrap); | |||
return fold; | |||
} | |||
}; |
@@ -0,0 +1,46 @@ | |||
#pragma once | |||
/** | |||
* Outputs a stair-step decimation. | |||
* So maybe it's the integral or a decimation? | |||
*/ | |||
class Decimator | |||
{ | |||
public: | |||
/** | |||
* ret the next sample from the decimator. | |||
* if (needsInput), then next call must be acceptData. | |||
*/ | |||
float clock(bool& needsInput) | |||
{ | |||
--phaseAccumulator; // one more sample | |||
if (phaseAccumulator <= 0) { | |||
needsInput = true; | |||
phaseAccumulator += rate; | |||
} else { | |||
needsInput = false; | |||
} | |||
return memory; | |||
} | |||
void acceptData(float data) | |||
{ | |||
memory = data; | |||
} | |||
/** | |||
* Rate must be > 1. | |||
* Fractional rates are fine. | |||
*/ | |||
void setDecimationRate(float r) | |||
{ | |||
rate = r; | |||
phaseAccumulator = rate; | |||
} | |||
private: | |||
float rate=0; | |||
float memory=0; | |||
float phaseAccumulator = 0; | |||
}; |
@@ -0,0 +1,52 @@ | |||
#pragma once | |||
#include "BiquadParams.h" | |||
#include "BiquadState.h" | |||
#include "BiquadFilter.h" | |||
/** | |||
* A traditional decimator, using IIR filters for interpolation | |||
* | |||
* template parameter OVERSAMPLE is the | |||
* decimation rate. | |||
*/ | |||
class IIRDecimator | |||
{ | |||
public: | |||
float process(const float * input) | |||
{ | |||
float x = 0; | |||
for (int i = 0; i < oversample; ++i) { | |||
x = BiquadFilter<float>::run(input[i], state, *params); | |||
} | |||
return x; | |||
} | |||
/** | |||
* cutoff is normalized freq (.5 = nyquist). | |||
* typically cutoff will be < (.5 / OVERSAMPLE), | |||
* if not, the filters wouldn't work. | |||
*/ | |||
#if 0 | |||
void setCutoff(float cutoff) | |||
{ | |||
assert(cutoff > 0 && cutoff < .5f); | |||
params = ObjectCache<float>::get6PLPParams(cutoff); | |||
} | |||
#endif | |||
/** | |||
* will set the oversample factor, and set the filter cutoff. | |||
* NOrmalized cutoff is fixed at 1 / 4 * oversample, for a one | |||
* octave passband | |||
*/ | |||
void setup(int oversampleFactor) | |||
{ | |||
assert(oversampleFactor == 4 || oversampleFactor == 16); | |||
oversample = oversampleFactor; | |||
params = ObjectCache<float>::get6PLPParams(1.f / (4.0f * oversample)); | |||
} | |||
private: | |||
std::shared_ptr<BiquadParams<float, 3>> params; | |||
BiquadState<float, 3> state; | |||
int oversample = 16; | |||
}; |
@@ -0,0 +1,48 @@ | |||
#pragma once | |||
#include "BiquadParams.h" | |||
#include "BiquadState.h" | |||
#include "BiquadFilter.h" | |||
#include "ObjectCache.h" | |||
class IIRUpsampler | |||
{ | |||
public: | |||
void process(float * outputBuffer, float input) | |||
{ | |||
input *= oversample; | |||
for (int i = 0; i < oversample; ++i) { | |||
outputBuffer[i] = BiquadFilter<float>::run(input, state, *params); | |||
input = 0; // just filter a delta - don't average the whole signal | |||
} | |||
} | |||
/** | |||
* cutoff is normalized freq (.5 = nyquist). | |||
* typically cutoff will be < (.5 / FACTOR), | |||
* if not, the filters wouldn't work. | |||
*/ | |||
#if 0 | |||
void setCutoff(float cutoff) | |||
{ | |||
assert(cutoff > 0 && cutoff < .5f); | |||
// ButterworthFilterDesigner<float>::designSixPoleLowpass(params, cutoff); | |||
params = ObjectCache<float>::get6PLPParams(cutoff); | |||
} | |||
#endif | |||
/** | |||
* Will set the oversample factor, and set the filter cutoff. | |||
* NOrmalized cutoff is fixed at 1 / 4 * oversample, for a one | |||
* octave passband | |||
*/ | |||
void setup(int oversampleFactor) | |||
{ | |||
assert(oversampleFactor == 4 || oversampleFactor == 16); | |||
oversample = oversampleFactor; | |||
params = ObjectCache<float>::get6PLPParams(1.f / (4.0f * oversample)); | |||
} | |||
private: | |||
std::shared_ptr<BiquadParams<float, 3>> params; | |||
BiquadState<float, 3> state; | |||
int oversample = 16; | |||
}; |
@@ -1,5 +1,6 @@ | |||
#pragma once | |||
#include <algorithm> | |||
#include <cmath> | |||
#include <memory> | |||
#include <emmintrin.h> | |||
@@ -7,7 +7,16 @@ template<typename T> | |||
class LookupTableFactory | |||
{ | |||
public: | |||
/** | |||
* domain (x) = -1 .. +1 | |||
*/ | |||
static void makeBipolarAudioTaper(LookupTableParams<T>& params); | |||
/** | |||
* domain (x) = 0..1 | |||
*/ | |||
static void makeAudioTaper(LookupTableParams<T>& params); | |||
static double audioTaperKnee() | |||
{ | |||
return -24; | |||
@@ -18,32 +27,99 @@ public: | |||
* range = 20..20k (for now). but should be .001 to 1.0? | |||
*/ | |||
static void makeExp2(LookupTableParams<T>& params); | |||
static double expYMin() | |||
static double exp2YMin() | |||
{ | |||
return 4; | |||
} | |||
static double expYMax() | |||
static double exp2YMax() | |||
{ | |||
return 40000; | |||
} | |||
static double expXMin() | |||
static double exp2XMin() | |||
{ | |||
return std::log2(exp2YMin()); | |||
} | |||
static double exp2XMax() | |||
{ | |||
return std::log2(exp2YMax()); | |||
} | |||
static void makeExp2ExLow(LookupTableParams<T>& params); | |||
static double exp2ExLowYMin() | |||
{ | |||
return 2; | |||
} | |||
static double exp2ExLowYMax() | |||
{ | |||
return 400; | |||
} | |||
static double exp2ExLowXMin() | |||
{ | |||
return std::log2(expYMin()); | |||
return std::log2(exp2ExLowYMin()); | |||
} | |||
static double expXMax() | |||
static double exp2ExLowXMax() | |||
{ | |||
return std::log2(expYMax()); | |||
return std::log2(exp2ExLowYMax()); | |||
} | |||
static void makeExp2ExHigh(LookupTableParams<T>& params); | |||
static double exp2ExHighYMin() | |||
{ | |||
return 400; | |||
} | |||
static double exp2ExHighYMax() | |||
{ | |||
return 20000; | |||
} | |||
static double exp2ExHighXMin() | |||
{ | |||
return std::log2(exp2ExHighYMin()); | |||
} | |||
static double exp2ExHighXMax() | |||
{ | |||
return std::log2(exp2ExHighYMax()); | |||
} | |||
}; | |||
template<typename T> | |||
inline void LookupTableFactory<T>::makeExp2(LookupTableParams<T>& params) | |||
{ | |||
// 128 not enough for one cent | |||
// 256 enough for one cent | |||
const int bins = 256; | |||
const T xMin = (T) std::log2(expYMin()); | |||
const T xMax = (T) std::log2(expYMax()); | |||
const T xMin = (T) std::log2(exp2YMin()); | |||
const T xMax = (T) std::log2(exp2YMax()); | |||
assert(xMin < xMax); | |||
LookupTable<T>::init(params, bins, xMin, xMax, [](double x) { | |||
return std::pow(2, x); | |||
}); | |||
} | |||
// hit specs with 256 / 128 @200 crossover | |||
// try 800 ng - need 256/256 | |||
// 400 need 256 / 128 | |||
template<typename T> | |||
inline void LookupTableFactory<T>::makeExp2ExHigh(LookupTableParams<T>& params) | |||
{ | |||
const int bins = 512; | |||
const T xMin = (T) std::log2(exp2ExHighYMin()); | |||
const T xMax = (T) std::log2(exp2ExHighYMax()); | |||
assert(xMin < xMax); | |||
LookupTable<T>::init(params, bins, xMin, xMax, [](double x) { | |||
return std::pow(2, x); | |||
}); | |||
} | |||
template<typename T> | |||
inline void LookupTableFactory<T>::makeExp2ExLow(LookupTableParams<T>& params) | |||
{ | |||
const int bins = 256; | |||
const T xMin = (T) std::log2(exp2ExLowYMin()); | |||
const T xMax = (T) std::log2(exp2ExLowYMax()); | |||
assert(xMin < xMax); | |||
LookupTable<T>::init(params, bins, xMin, xMax, [](double x) { | |||
return std::pow(2, x); | |||
@@ -61,4 +137,17 @@ inline void LookupTableFactory<T>::makeBipolarAudioTaper(LookupTableParams<T>& p | |||
return (x >= 0) ? audioTaper(x) : -audioTaper(-x); | |||
}); | |||
} | |||
template<typename T> | |||
inline void LookupTableFactory<T>::makeAudioTaper(LookupTableParams<T>& params) | |||
{ | |||
const int bins = 32; | |||
std::function<double(double)> audioTaper = AudioMath::makeFunc_AudioTaper(audioTaperKnee()); | |||
const T xMin = 0; | |||
const T xMax = 1; | |||
LookupTable<T>::init(params, bins, xMin, xMax, [audioTaper](double x) { | |||
return audioTaper(x); | |||
}); | |||
} |
@@ -2,6 +2,7 @@ | |||
#include <assert.h> | |||
#include "AudioMath.h" | |||
#include "ButterworthFilterDesigner.h" | |||
#include "LookupTableFactory.h" | |||
#include "ObjectCache.h" | |||
@@ -17,6 +18,21 @@ std::shared_ptr<LookupTableParams<T>> ObjectCache<T>::getBipolarAudioTaper() | |||
return ret; | |||
} | |||
template <typename T> | |||
std::shared_ptr<LookupTableParams<T>> ObjectCache<T>::getAudioTaper() | |||
{ | |||
std::shared_ptr< LookupTableParams<T>> ret = audioTaper.lock(); | |||
if (!ret) { | |||
ret = std::make_shared<LookupTableParams<T>>(); | |||
LookupTableFactory<T>::makeAudioTaper(*ret); | |||
audioTaper = ret; | |||
} | |||
return ret; | |||
return nullptr; | |||
} | |||
template <typename T> | |||
std::shared_ptr<LookupTableParams<T>> ObjectCache<T>::getSinLookup() | |||
{ | |||
@@ -24,8 +40,8 @@ std::shared_ptr<LookupTableParams<T>> ObjectCache<T>::getSinLookup() | |||
if (!ret) { | |||
ret = std::make_shared<LookupTableParams<T>>(); | |||
std::function<double(double)> f = AudioMath::makeFunc_Sin(); | |||
// Used to use 4096, but 256 gives about 80db snr, so let's save memory | |||
LookupTable<T>::init(*ret, 256, 0, 1, f); | |||
// Used to use 4096, but 512 gives about 92db snr, so let's save memory | |||
LookupTable<T>::init(*ret, 512, 0, 1, f); | |||
sinLookupTable = ret; | |||
} | |||
return ret; | |||
@@ -43,6 +59,30 @@ std::shared_ptr<LookupTableParams<T>> ObjectCache<T>::getExp2() | |||
return ret; | |||
} | |||
template <typename T> | |||
std::shared_ptr<LookupTableParams<T>> ObjectCache<T>::getExp2ExtendedLow() | |||
{ | |||
std::shared_ptr< LookupTableParams<T>> ret = exp2ExLow.lock(); | |||
if (!ret) { | |||
ret = std::make_shared<LookupTableParams<T>>(); | |||
LookupTableFactory<T>::makeExp2ExLow(*ret); | |||
exp2ExLow = ret; | |||
} | |||
return ret; | |||
} | |||
template <typename T> | |||
std::shared_ptr<LookupTableParams<T>> ObjectCache<T>::getExp2ExtendedHigh() | |||
{ | |||
std::shared_ptr< LookupTableParams<T>> ret = exp2ExHigh.lock(); | |||
if (!ret) { | |||
ret = std::make_shared<LookupTableParams<T>>(); | |||
LookupTableFactory<T>::makeExp2ExHigh(*ret); | |||
exp2ExHigh = ret; | |||
} | |||
return ret; | |||
} | |||
template <typename T> | |||
@@ -53,7 +93,7 @@ std::shared_ptr<LookupTableParams<T>> ObjectCache<T>::getDb2Gain() | |||
ret = std::make_shared<LookupTableParams<T>>(); | |||
LookupTable<T>::init(*ret, 32, -80, 20, [](double x) { | |||
return AudioMath::gainFromDb(x); | |||
}); | |||
}); | |||
db2Gain = ret; | |||
} | |||
return ret; | |||
@@ -66,7 +106,7 @@ std::shared_ptr<LookupTableParams<T>> ObjectCache<T>::getTanh5() | |||
std::shared_ptr< LookupTableParams<T>> ret = tanh5.lock(); | |||
if (!ret) { | |||
ret = std::make_shared<LookupTableParams<T>>(); | |||
LookupTable<T>::init(*ret, 256, -5, 5, [](double x) { | |||
LookupTable<T>::init(*ret, 256, -5, 5, [](double x) { | |||
return std::tanh(x); | |||
}); | |||
tanh5 = ret; | |||
@@ -74,16 +114,71 @@ std::shared_ptr<LookupTableParams<T>> ObjectCache<T>::getTanh5() | |||
return ret; | |||
} | |||
// The weak pointer that hold our singletons. | |||
/** | |||
* Lambda capture two smart pointers to lookup table params, | |||
* so lifetime of the lambda control their reft. | |||
*/ | |||
template <typename T> | |||
std::function<T(T)> ObjectCache<T>::getExp2Ex() | |||
{ | |||
std::shared_ptr < LookupTableParams<T>> low = getExp2ExtendedLow(); | |||
std::shared_ptr < LookupTableParams<T>> high = getExp2ExtendedHigh(); | |||
const T xDivide = (T) LookupTableFactory<T>::exp2ExHighXMin(); | |||
return [low, high, xDivide](T x) { | |||
auto params = (x < xDivide) ? low : high; | |||
return LookupTable<T>::lookup(*params, x, true); | |||
}; | |||
} | |||
template <typename T> | |||
std::shared_ptr<BiquadParams<float, 3>> ObjectCache<T>::get6PLPParams(float normalizedFc) | |||
{ | |||
const int div = (int) std::round(1.0 / normalizedFc); | |||
if (div == 64) { | |||
std::shared_ptr < BiquadParams<float, 3>> ret = lowpass64.lock(); | |||
if (!ret) { | |||
ret = std::make_shared<BiquadParams<float, 3>>(); | |||
ButterworthFilterDesigner<float>::designSixPoleLowpass(*ret, normalizedFc); | |||
lowpass64 = ret; | |||
} | |||
return ret; | |||
} else if (div == 16) { | |||
std::shared_ptr < BiquadParams<float, 3>> ret = lowpass16.lock(); | |||
if (!ret) { | |||
ret = std::make_shared<BiquadParams<float, 3>>(); | |||
ButterworthFilterDesigner<float>::designSixPoleLowpass(*ret, normalizedFc); | |||
lowpass16 = ret; | |||
} | |||
return ret; | |||
} | |||
else assert(false); | |||
return nullptr; | |||
} | |||
// The weak pointers that hold our singletons. | |||
template <typename T> | |||
std::weak_ptr< BiquadParams<float, 3> > ObjectCache<T>::lowpass64; | |||
template <typename T> | |||
std::weak_ptr< BiquadParams<float, 3> > ObjectCache<T>::lowpass16; | |||
template <typename T> | |||
std::weak_ptr<LookupTableParams<T>> ObjectCache<T>::bipolarAudioTaper; | |||
template <typename T> | |||
std::weak_ptr<LookupTableParams<T>> ObjectCache<T>::audioTaper; | |||
template <typename T> | |||
std::weak_ptr<LookupTableParams<T>> ObjectCache<T>::sinLookupTable; | |||
template <typename T> | |||
std::weak_ptr<LookupTableParams<T>> ObjectCache<T>::exp2; | |||
template <typename T> | |||
std::weak_ptr<LookupTableParams<T>> ObjectCache<T>::exp2ExLow; | |||
template <typename T> | |||
std::weak_ptr<LookupTableParams<T>> ObjectCache<T>::exp2ExHigh; | |||
template <typename T> | |||
std::weak_ptr<LookupTableParams<T>> ObjectCache<T>::db2Gain; | |||
@@ -2,6 +2,7 @@ | |||
#include "LookupTable.h" | |||
#include "BiquadParams.h" | |||
/** | |||
* This class creates objects and caches them. | |||
@@ -18,6 +19,7 @@ class ObjectCache | |||
{ | |||
public: | |||
static std::shared_ptr<LookupTableParams<T>> getBipolarAudioTaper(); | |||
static std::shared_ptr<LookupTableParams<T>> getAudioTaper(); | |||
static std::shared_ptr<LookupTableParams<T>> getSinLookup(); | |||
/** | |||
@@ -30,6 +32,14 @@ public: | |||
* accuracy = 1 cent (1V/octave) | |||
*/ | |||
static std::shared_ptr<LookupTableParams<T>> getExp2(); | |||
static std::function<T(T)> getExp2Ex(); | |||
static std::shared_ptr<LookupTableParams<T>> getExp2ExtendedLow(); | |||
static std::shared_ptr<LookupTableParams<T>> getExp2ExtendedHigh(); | |||
static std::shared_ptr<LookupTableParams<T>> getDb2Gain(); | |||
/** | |||
@@ -37,14 +47,22 @@ public: | |||
*/ | |||
static std::shared_ptr<LookupTableParams<T>> getTanh5(); | |||
static std::shared_ptr<BiquadParams<float, 3>> get6PLPParams(float normalizedFc); | |||
private: | |||
/** | |||
* Cache uses weak pointers. This allows the cached objects to be | |||
* freed when the last client reference goes away. | |||
*/ | |||
static std::weak_ptr<LookupTableParams<T>> bipolarAudioTaper; | |||
static std::weak_ptr<LookupTableParams<T>> audioTaper; | |||
static std::weak_ptr<LookupTableParams<T>> sinLookupTable; | |||
static std::weak_ptr<LookupTableParams<T>> exp2; | |||
static std::weak_ptr<LookupTableParams<T>> exp2ExHigh; | |||
static std::weak_ptr<LookupTableParams<T>> exp2ExLow; | |||
static std::weak_ptr<LookupTableParams<T>> db2Gain; | |||
static std::weak_ptr<LookupTableParams<T>> tanh5; | |||
static std::weak_ptr< BiquadParams<float, 3>> lowpass64; | |||
static std::weak_ptr< BiquadParams<float, 3>> lowpass16; | |||
}; |
@@ -0,0 +1,64 @@ | |||
#pragma once | |||
template <typename T, int order> | |||
class Poly | |||
{ | |||
public: | |||
Poly(); | |||
float run(float x) | |||
{ | |||
fillPowers(x); | |||
return (float) doSum(); | |||
} | |||
void setGain(int index, float value) | |||
{ | |||
assert(index >= 0 && index < order); | |||
gains[index] = value; | |||
} | |||
private: | |||
T gains[order]; | |||
// powers[0] = x, powers[1] = x**2 | |||
T powers[order]; | |||
void fillPowers(T); | |||
T doSum(); | |||
}; | |||
template <typename T, int order> | |||
inline Poly<T, order>::Poly() | |||
{ | |||
assert(order == 11); | |||
for (int i = 0; i < order; ++i) { | |||
gains[i] = 0; | |||
powers[i] = 0; | |||
} | |||
} | |||
template <typename T, int order> | |||
inline void Poly<T, order>::fillPowers(T x) | |||
{ | |||
T value = x; | |||
for (int i = 0; i < order; ++i) { | |||
powers[i] = value; | |||
value *= x; | |||
} | |||
} | |||
template <typename T, int order> | |||
inline T Poly<T, order>::doSum() | |||
{ | |||
T ret = gains[0] * powers[0]; | |||
ret += gains[1] * (2 * powers[1] - 1); | |||
ret += gains[2] * (4 * powers[2] - 3 * powers[0]); | |||
ret += gains[3] * (8 * powers[3] - 8 * powers[1] + 1); | |||
ret += gains[4] * (16 * powers[4] - 20 * powers[2] + 5 * powers[0]); | |||
ret += gains[5] * (32 * powers[5] - 48 * powers[3] + 18 * powers[1] - 1); | |||
ret += gains[6] * (64 * powers[6] - 112 * powers[4] + 56 * powers[2] - 7 * powers[0]); | |||
ret += gains[7] * (128 * powers[7] - 256 * powers[5] + 160 * powers[3] - 32 * powers[1] + 1); | |||
ret += gains[8] * (256 * powers[8] - 576 * powers[6] + 432 * powers[4] - 120 * powers[2] + 9 * powers[0]); | |||
ret += gains[9] * (512 * powers[9] - 1280 * powers[7] + 1120 * powers[5] - 400 * powers[3] + 50 * powers[1] - 1); | |||
ret += gains[10] * (1024 * powers[10] - 2816 * powers[8] + 2816 * powers[6] - 1232 * powers[4] + 220 * powers[2] - 11 * powers[0]); | |||
return ret; | |||
} |
@@ -6,16 +6,28 @@ ALL_OBJ= \ | |||
dsp/filters/FormantTables2.o \ | |||
dsp/filters/HilbertFilterDesigner.o \ | |||
dsp/third-party/falco/DspFilter.o \ | |||
dsp/utils/AsymWaveShaper.o \ | |||
dsp/utils/AudioMath.o \ | |||
dsp/utils/ObjectCache.o \ | |||
sqsrc/clock/ClockMult.o \ | |||
sqsrc/thread/ThreadClient.o \ | |||
sqsrc/thread/ThreadServer.o \ | |||
sqsrc/thread/ThreadSharedState.o \ | |||
src/BlankModule.o \ | |||
src/BootyModule.o \ | |||
src/CHBModule.o \ | |||
src/CPU_Hog.o \ | |||
src/ColoredNoiseModule.o \ | |||
src/DGModule.o \ | |||
src/EV3Module.o \ | |||
src/EVModule.o \ | |||
src/FunVModule.o \ | |||
src/GMRModule.o \ | |||
src/GrayModule.o \ | |||
src/LFNModule.o \ | |||
src/ShaperModule.o \ | |||
src/Squinky.o \ | |||
src/SuperModule.o \ | |||
src/ThreadBoost.o \ | |||
src/TremoloModule.o \ | |||
src/VocalFilterModule.o \ | |||
@@ -2,7 +2,12 @@ SLUG=squinkylabs-plug1 | |||
include ../../../build_plugin_pre_linux.mk | |||
EXTRAFLAGS += -Icomposites/ -Idsp/utils/ -Idsp/third-party/kiss_fft130/ -Idsp/third-party/kiss_fft130/tools/ -Isqsrc/util/ -Isqsrc/thread/ -Idsp/third-party/falco/ -Idsp/generators/ -Idsp/filters/ -Idsp/fft/ -Isqsrc/clock/ | |||
EXTRAFLAGS += -Icomposites/ -Idsp/utils/ -Idsp/third-party/kiss_fft130/ -Idsp/third-party/kiss_fft130/tools/ -Idsp/third-party/src -Isqsrc/util/ -Isqsrc/thread/ -Idsp/third-party/falco/ -Idsp/generators/ -Idsp/filters/ -Idsp/fft/ -Isqsrc/clock/ -Isqsrc/grammar -Isqsrc/delay | |||
#EXTRAFLAGS += -D_CHB | |||
#EXTRAFLAGS += -D_DG | |||
#EXTRAFLAGS += -D_EV3 | |||
#EXTRAFLAGS += -D_EV | |||
#EXTRAFLAGS += -D_GMR | |||
include make.objects | |||
@@ -2,7 +2,12 @@ SLUG=squinkylabs-plug1 | |||
include ../../../build_plugin_pre_msvc.mk | |||
EXTRAFLAGS += -Icomposites/ -Idsp/utils/ -Idsp/third-party/kiss_fft130/ -Idsp/third-party/kiss_fft130/tools/ -Isqsrc/util/ -Isqsrc/thread/ -Idsp/third-party/falco/ -Idsp/generators/ -Idsp/filters/ -Idsp/fft/ -Isqsrc/clock/ | |||
EXTRAFLAGS += -Icomposites/ -Idsp/utils/ -Idsp/third-party/kiss_fft130/ -Idsp/third-party/kiss_fft130/tools/ -Idsp/third-party/src -Isqsrc/util/ -Isqsrc/thread/ -Idsp/third-party/falco/ -Idsp/generators/ -Idsp/filters/ -Idsp/fft/ -Isqsrc/clock/ -Isqsrc/grammar -Isqsrc/delay | |||
#EXTRAFLAGS += -D_CHB | |||
#EXTRAFLAGS += -D_DG | |||
#EXTRAFLAGS += -D_EV3 | |||
#EXTRAFLAGS += -D_EV | |||
#EXTRAFLAGS += -D_GMR | |||
include make.objects | |||
@@ -28,38 +28,67 @@ | |||
<ClCompile Include="..\..\dsp\third-party\falco\DspFilter.cpp" /> | |||
<ClCompile Include="..\..\dsp\third-party\kiss_fft130\kiss_fft.c" /> | |||
<ClCompile Include="..\..\dsp\third-party\kiss_fft130\tools\kiss_fftr.c" /> | |||
<ClCompile Include="..\..\dsp\third-party\src\minblep.cpp" /> | |||
<ClCompile Include="..\..\dsp\utils\AsymWaveShaper.cpp" /> | |||
<ClCompile Include="..\..\dsp\utils\AudioMath.cpp" /> | |||
<ClCompile Include="..\..\dsp\utils\ObjectCache.cpp" /> | |||
<ClCompile Include="..\..\sqsrc\clock\ClockMult.cpp" /> | |||
<ClCompile Include="..\..\sqsrc\delay\FractionalDelay.cpp" /> | |||
<ClCompile Include="..\..\sqsrc\grammar\StochasticGrammar.cpp" /> | |||
<ClCompile Include="..\..\sqsrc\thread\ThreadClient.cpp" /> | |||
<ClCompile Include="..\..\sqsrc\thread\ThreadServer.cpp" /> | |||
<ClCompile Include="..\..\sqsrc\thread\ThreadSharedState.cpp" /> | |||
<ClCompile Include="..\..\test\Analyzer.cpp" /> | |||
<ClCompile Include="..\..\test\main.cpp" /> | |||
<ClCompile Include="..\..\test\perfTest.cpp" /> | |||
<ClCompile Include="..\..\test\testAudioMath.cpp" /> | |||
<ClCompile Include="..\..\test\testBiquad.cpp" /> | |||
<ClCompile Include="..\..\test\testButterLookup.cpp" /> | |||
<ClCompile Include="..\..\test\testClockMult.cpp" /> | |||
<ClCompile Include="..\..\test\testColoredNoise.cpp" /> | |||
<ClCompile Include="..\..\test\testDelay.cpp" /> | |||
<ClCompile Include="..\..\test\testFFT.cpp" /> | |||
<ClCompile Include="..\..\test\testFFTCrossFader.cpp" /> | |||
<ClCompile Include="..\..\test\testAnalyzer.cpp" /> | |||
<ClCompile Include="..\..\test\testFilter.cpp" /> | |||
<ClCompile Include="..\..\test\testFilterDesign.cpp" /> | |||
<ClCompile Include="..\..\test\testFinalLeaks.cpp" /> | |||
<ClCompile Include="..\..\test\testFrequencyShifter.cpp" /> | |||
<ClCompile Include="..\..\test\testGateTrigger.cpp" /> | |||
<ClCompile Include="..\..\test\testGMR.cpp" /> | |||
<ClCompile Include="..\..\test\testHilbert.cpp" /> | |||
<ClCompile Include="..\..\test\testLookupTable.cpp" /> | |||
<ClCompile Include="..\..\test\testLowpassFilter.cpp" /> | |||
<ClCompile Include="..\..\test\testMinBLEPVCO.cpp" /> | |||
<ClCompile Include="..\..\test\testObjectCache.cpp" /> | |||
<ClCompile Include="..\..\test\testPoly.cpp" /> | |||
<ClCompile Include="..\..\test\testRateConversion.cpp" /> | |||
<ClCompile Include="..\..\test\testRingBuffer.cpp" /> | |||
<ClCompile Include="..\..\test\testSaw.cpp" /> | |||
<ClCompile Include="..\..\test\testSin.cpp" /> | |||
<ClCompile Include="..\..\test\testSinOscillator.cpp" /> | |||
<ClCompile Include="..\..\test\testSpline.cpp" /> | |||
<ClCompile Include="..\..\test\testStateVariable.cpp" /> | |||
<ClCompile Include="..\..\test\testStochasticGrammar.cpp" /> | |||
<ClCompile Include="..\..\test\testTestSignal.cpp" /> | |||
<ClCompile Include="..\..\test\testThread.cpp" /> | |||
<ClCompile Include="..\..\test\testTremolo.cpp" /> | |||
<ClCompile Include="..\..\test\testVCO.cpp" /> | |||
<ClCompile Include="..\..\test\testVCOAlias.cpp" /> | |||
<ClCompile Include="..\..\test\testVocalAnimator.cpp" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ClInclude Include="..\..\composites\CHB.h" /> | |||
<ClInclude Include="..\..\composites\ColoredNoise.h" /> | |||
<ClInclude Include="..\..\composites\daveguide.h" /> | |||
<ClInclude Include="..\..\composites\EV3.h" /> | |||
<ClInclude Include="..\..\composites\FrequencyShifter.h" /> | |||
<ClInclude Include="..\..\composites\FunVCOComposite.h" /> | |||
<ClInclude Include="..\..\composites\GMR.h" /> | |||
<ClInclude Include="..\..\composites\Gray.h" /> | |||
<ClInclude Include="..\..\composites\LFN.h" /> | |||
<ClInclude Include="..\..\composites\Shaper.h" /> | |||
<ClInclude Include="..\..\composites\Super.h" /> | |||
<ClInclude Include="..\..\composites\TestComposite.h" /> | |||
<ClInclude Include="..\..\composites\Tremolo.h" /> | |||
<ClInclude Include="..\..\composites\VocalAnimator.h" /> | |||
@@ -72,21 +101,37 @@ | |||
<ClInclude Include="..\..\dsp\filters\BiquadParams.h" /> | |||
<ClInclude Include="..\..\dsp\filters\BiquadState.h" /> | |||
<ClInclude Include="..\..\dsp\filters\ButterworthFilterDesigner.h" /> | |||
<ClInclude Include="..\..\dsp\filters\ButterworthLookup.h" /> | |||
<ClInclude Include="..\..\dsp\filters\FormantTables2.h" /> | |||
<ClInclude Include="..\..\dsp\filters\GraphicEq.h" /> | |||
<ClInclude Include="..\..\dsp\filters\HilbertFilterDesigner.h" /> | |||
<ClInclude Include="..\..\dsp\filters\LowPassFilter.h" /> | |||
<ClInclude Include="..\..\dsp\filters\StateVariableFilter.h" /> | |||
<ClInclude Include="..\..\dsp\generators\MinBLEPVCO.h" /> | |||
<ClInclude Include="..\..\dsp\generators\MultiModOsc.h" /> | |||
<ClInclude Include="..\..\dsp\generators\SawOscillator.h" /> | |||
<ClInclude Include="..\..\dsp\generators\SinOscillator.h" /> | |||
<ClInclude Include="..\..\dsp\third-party\falco\DspFilter.h" /> | |||
<ClInclude Include="..\..\dsp\third-party\kiss_fft130\kiss_fft.h" /> | |||
<ClInclude Include="..\..\dsp\third-party\kiss_fft130\tools\kiss_fftr.h" /> | |||
<ClInclude Include="..\..\dsp\third-party\src\EvenVCO.h" /> | |||
<ClInclude Include="..\..\dsp\third-party\src\EvenVCO_orig.h" /> | |||
<ClInclude Include="..\..\dsp\third-party\src\FunVCO.h" /> | |||
<ClInclude Include="..\..\dsp\utils\AsymRampShaper.h" /> | |||
<ClInclude Include="..\..\dsp\utils\AsymWaveShaper.h" /> | |||
<ClInclude Include="..\..\dsp\utils\AudioMath.h" /> | |||
<ClInclude Include="..\..\dsp\utils\Decimator.h" /> | |||
<ClInclude Include="..\..\dsp\utils\IIRDecimator.h" /> | |||
<ClInclude Include="..\..\dsp\utils\IIRUpsampler.h" /> | |||
<ClInclude Include="..\..\dsp\utils\LookupTable.h" /> | |||
<ClInclude Include="..\..\dsp\utils\LookupTableFactory.h" /> | |||
<ClInclude Include="..\..\dsp\utils\ObjectCache.h" /> | |||
<ClInclude Include="..\..\dsp\utils\poly.h" /> | |||
<ClInclude Include="..\..\sqsrc\clock\ClockMult.h" /> | |||
<ClInclude Include="..\..\sqsrc\clock\GenerativeTriggerGenerator.h" /> | |||
<ClInclude Include="..\..\sqsrc\clock\TriggerSequencer.h" /> | |||
<ClInclude Include="..\..\sqsrc\delay\FractionalDelay.h" /> | |||
<ClInclude Include="..\..\sqsrc\grammar\StochasticGrammar.h" /> | |||
<ClInclude Include="..\..\sqsrc\thread\ThreadClient.h" /> | |||
<ClInclude Include="..\..\sqsrc\thread\ThreadPriority.h" /> | |||
<ClInclude Include="..\..\sqsrc\thread\ThreadServer.h" /> | |||
@@ -96,6 +141,8 @@ | |||
<ClInclude Include="..\..\sqsrc\util\ManagedPool.h" /> | |||
<ClInclude Include="..\..\sqsrc\util\RingBuffer.h" /> | |||
<ClInclude Include="..\..\sqsrc\util\SchmidtTrigger.h" /> | |||
<ClInclude Include="..\..\sqsrc\util\TriggerOutput.h" /> | |||
<ClInclude Include="..\..\test\Analyzer.h" /> | |||
<ClInclude Include="..\..\test\asserts.h" /> | |||
<ClInclude Include="..\..\test\ExtremeTester.h" /> | |||
<ClInclude Include="..\..\test\MeasureTime.h" /> | |||
@@ -164,7 +211,7 @@ | |||
<WarningLevel>Level3</WarningLevel> | |||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> | |||
<Optimization>Disabled</Optimization> | |||
<AdditionalIncludeDirectories>..\..\dsp\third-party\falco;..\..\dsp\utils;..\..\dsp\generators;..\..\dsp\filters;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||
<AdditionalIncludeDirectories>..\..\dsp\utils;..\..\dsp\fft;..\..\dsp\filters;..\..\dsp\generators;..\..\dsp\third-party\src;..\..\dsp\third-party\falco;..\..\dsp\third-party\kiss_fft130;..\..\dsp\third-party\kiss_fft130\tools;..\..\composites;..\..\sqsrc\util;..\..\sqsrc\thread;..\..\sqsrc\grammar;..\..\sqsrc\clock;..\..\..\..\include;..\..\..\..\dep\speexdsp-SpeexDSP-1.2rc3\include;D:\VCVRack\x6\dep\jpommier-pffft-29e4f76ac53b;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||
<LanguageStandard>stdcpp14</LanguageStandard> | |||
</ClCompile> | |||
<Link> | |||
@@ -180,7 +227,7 @@ | |||
<WarningLevel>Level3</WarningLevel> | |||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> | |||
<Optimization>Disabled</Optimization> | |||
<AdditionalIncludeDirectories>..\..\dsp\fft;..\..\dsp\generators;..\..\dsp\filters;..\..\dsp\third-party\falco;..\..\composites;..\..\sqsrc\clock;..\..\sqsrc\util;..\..\sqsrc\thread;..\..\dsp\utils;..\..\dsp\third-party\kiss_fft130;..\..\dsp\third-party\kiss_fft130\tools;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||
<AdditionalIncludeDirectories>..\..\sqsrc\grammar;..\..\dsp\fft;..\..\dsp\generators;..\..\dsp\filters;..\..\dsp\third-party\src;..\..\dsp\third-party\falco;..\..\composites;..\..\sqsrc\clock;..\..\sqsrc\delay;..\..\sqsrc\util;..\..\sqsrc\thread;..\..\dsp\utils;..\..\dsp\third-party\kiss_fft130;..\..\dsp\third-party\kiss_fft130\tools;..\..\..\..\include;..\..\..\..\dep\speexdsp-SpeexDSP-1.2rc3\include;..\..\..\..\dep\jpommier-pffft-29e4f76ac53b;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||
<LanguageStandard>stdcpp14</LanguageStandard> | |||
</ClCompile> | |||
<Link> | |||
@@ -195,6 +242,7 @@ | |||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> | |||
<WarningLevel>Level3</WarningLevel> | |||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> | |||
<AdditionalIncludeDirectories>..\..\dsp\utils;..\..\dsp\fft;..\..\dsp\filters;..\..\dsp\generators;..\..\dsp\third-party\src;..\..\dsp\third-party\falco;..\..\dsp\third-party\kiss_fft130;..\..\dsp\third-party\kiss_fft130\tools;..\..\composites;..\..\sqsrc\util;..\..\sqsrc\thread;..\..\sqsrc\grammar;..\..\sqsrc\clock;..\..\..\..\include;..\..\..\..\dep\speexdsp-SpeexDSP-1.2rc3\include;D:\VCVRack\x6\dep\jpommier-pffft-29e4f76ac53b;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||
</ClCompile> | |||
<Link> | |||
<TargetMachine>MachineX86</TargetMachine> | |||
@@ -210,7 +258,7 @@ | |||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> | |||
<WarningLevel>Level3</WarningLevel> | |||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> | |||
<AdditionalIncludeDirectories>..\..\dsp\fft;..\..\dsp\generators;..\..\dsp\filters;..\..\dsp\third-party\falco;..\..\composites;..\..\sqsrc\clock;..\..\sqsrc\util;..\..\sqsrc\thread;..\..\dsp\utils;..\..\dsp\third-party\kiss_fft130;..\..\dsp\third-party\kiss_fft130\tools;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||
<AdditionalIncludeDirectories>..\..\sqsrc\grammar;..\..\dsp\fft;..\..\dsp\generators;..\..\dsp\filters;..\..\dsp\third-party\src;..\..\dsp\third-party\falco;..\..\composites;..\..\sqsrc\clock;..\..\sqsrc\delay;..\..\sqsrc\util;..\..\sqsrc\thread;..\..\dsp\utils;..\..\dsp\third-party\kiss_fft130;..\..\dsp\third-party\kiss_fft130\tools;..\..\..\..\include;..\..\..\..\dep\speexdsp-SpeexDSP-1.2rc3\include;..\..\..\..\dep\jpommier-pffft-29e4f76ac53b;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | |||
</ClCompile> | |||
<Link> | |||
<GenerateDebugInformation>true</GenerateDebugInformation> | |||