diff --git a/.gitignore b/.gitignore index 62cb4c9b..073a4152 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ *.exp *.lib *.map +*.o + diff --git a/README.md b/README.md index 9b0d2324..3b2f1c01 100644 --- a/README.md +++ b/README.md @@ -233,6 +233,13 @@ The following add-on modules are statically linked with the VST plugin: - SonusModular.Scramblase - SonusModular.Twoff - SonusModular.Yabp + - squinkylabs-plug1.Booty + - squinkylabs-plug1.Vocal + - squinkylabs-plug1.VocalFilter + - squinkylabs-plug1.ColoredNoise + - squinkylabs-plug1.Tremolo + - squinkylabs-plug1.CPU_Hog + - squinkylabs-plug1.ThreadBoost - SubmarineFree.AG106 - SubmarineFree.BB120 - SubmarineFree.FF110 diff --git a/plugins/community/repos/squinkylabs-plug1/.vscode/c_cpp_properties.json b/plugins/community/repos/squinkylabs-plug1/.vscode/c_cpp_properties.json new file mode 100644 index 00000000..85b7d91d --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/.vscode/c_cpp_properties.json @@ -0,0 +1,92 @@ +{ + "configurations": [ + { + "name": "Mac", + "includePath": [ + "/usr/include", + "/usr/local/include", + "${workspaceRoot}" + ], + "defines": [], + "intelliSenseMode": "clang-x64", + "browse": { + "path": [ + "/usr/include", + "/usr/local/include", + "${workspaceRoot}" + ], + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "" + }, + "macFrameworkPath": [ + "/System/Library/Frameworks", + "/Library/Frameworks" + ], + "cStandard": "c11", + "cppStandard": "c++17" + }, + { + "name": "Linux", + "includePath": [ + "/usr/include", + "/usr/local/include", + "${workspaceRoot}" + ], + "defines": [], + "intelliSenseMode": "clang-x64", + "browse": { + "path": [ + "/usr/include", + "/usr/local/include", + "${workspaceRoot}" + ], + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "" + }, + "cStandard": "c11", + "cppStandard": "c++17" + }, + { + "name": "Win32", + "includePath": [ + "D:/VisualStudio2017/VC/Tools/MSVC/14.12.25827/include/*", + "D:/VisualStudio2017/VC/Tools/MSVC/14.12.25827/atlmfc/include/*", + "C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/um", + "C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/ucrt", + "C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/shared", + "C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/winrt", + "${workspaceRoot}", + "${workspaceRoot}/dsp/generators", + "${workspaceRoot}/dsp/filters", + "${workspaceRoot}/dsp/utils", + "${workspaceRoot}/src", + "${workspaceRoot}/../../include", + "${workspaceRoot}/dsp/third-party/falco" + ], + "defines": [ + "_EXP", + "_DEBUG", + "UNICODE", + "_UNICODE", + "_VSCODE" + ], + "intelliSenseMode": "msvc-x64", + "browse": { + "path": [ + "D:/VisualStudio2017/VC/Tools/MSVC/14.12.25827/include/*", + "D:/VisualStudio2017/VC/Tools/MSVC/14.12.25827/atlmfc/include/*", + "C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/um", + "C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/ucrt", + "C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/shared", + "C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/winrt", + "${workspaceRoot}" + ], + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "" + }, + "cStandard": "c11", + "cppStandard": "c++17" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/LICENSE b/plugins/community/repos/squinkylabs-plug1/LICENSE new file mode 100644 index 00000000..30d73b3d --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 squinkylabs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/community/repos/squinkylabs-plug1/LICENSE-dist.txt b/plugins/community/repos/squinkylabs-plug1/LICENSE-dist.txt new file mode 100644 index 00000000..636c5ee5 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/LICENSE-dist.txt @@ -0,0 +1,67 @@ +# kiss FFT + +Copyright (c) 2003-2010 Mark Borgerding + +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. + * 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. + + +# Vincent Falco + +"A Collection of Useful C++ Classes for Digital Signal Processing" +By Vincent Falco + +Official project location: +http://code.google.com/p/dspfilterscpp/ + +See DspFilter.cpp for notes and bibliography. + +-------------------------------------------------------------------------------- + +License: MIT License (http://www.opensource.org/licenses/mit-license.php) +Copyright (c) 2009 by Vincent Falco + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +# 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: + + +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 Rack Component Library + +Component Library graphics by Grayscale (http://grayscale.info/) +Licensed under CC BY-NC 4.0 (https://creativecommons.org/licenses/by-nc/4.0/) diff --git a/plugins/community/repos/squinkylabs-plug1/Makefile b/plugins/community/repos/squinkylabs-plug1/Makefile new file mode 100644 index 00000000..184d302f --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/Makefile @@ -0,0 +1,56 @@ +# Must follow the format in the Naming section of https://vcvrack.com/manual/PluginDevelopmentTutorial.html +SLUG = squinkylabs-plug1 + +# Must follow the format in the Versioning section of https://vcvrack.com/manual/PluginDevelopmentTutorial.html +VERSION = 0.6.5 + +# 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./sqsrc/thread -I./dsp/fft -I./composites +FLAGS += -I./sqsrc/noise -I./sqsrc/util -I./sqsrc/clock +CFLAGS += +CXXFLAGS += + +# Command line variable to turn on "experimental" modules +ifdef _EXP + FLAGS += -D _EXP +endif +ifdef _CPU_HOG + FLAGS += -D _CPU_HOG +endif +# Macro to use on any target where we don't normally want asserts +ASSERTOFF = -D NDEBUG + +# Make _ASSERT=true will nullify our ASSERTOFF flag, thus allowing them +ifdef _ASSERT + ASSERTOFF = +endif + +# Careful about linking to shared libraries, since you can't assume much about the user's environment and library search path. +# Static libraries are fine. +LDFLAGS += -lpthread + +# Add .cpp and .c files to the build +SOURCES += $(wildcard src/*.cpp) +SOURCES += $(wildcard dsp/**/*.cpp) +SOURCES += $(wildcard dsp/third-party/falco/*.cpp) +SOURCES += dsp/third-party/kiss_fft130/kiss_fft.c +SOURCES += dsp/third-party/kiss_fft130/tools/kiss_fftr.c +SOURCES += $(wildcard sqsrc/**/*.cpp) + +# Add files to the ZIP package when running `make dist` +# The compiled plugin is automatically added. +DISTRIBUTABLES += $(wildcard LICENSE*) res + +# If RACK_DIR is not defined when calling the Makefile, default to two levels above +RACK_DIR ?= ../.. + +# Include the VCV Rack plugin Makefile framework +include $(RACK_DIR)/plugin.mk + +# This turns asserts off for make (plugin), not for test or perf +$(TARGET) : FLAGS += $(ASSERTOFF) + +include test.mk + diff --git a/plugins/community/repos/squinkylabs-plug1/README.md b/plugins/community/repos/squinkylabs-plug1/README.md new file mode 100644 index 00000000..13a7bbcd --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/README.md @@ -0,0 +1,21 @@ +# About SquinkyVCV + +This project is a growing collection of modules for the VCV Rack vritual modular synthesizer. You can find more information about VCV Rack [here](https://vcvrack.com/). + +You can find us on Facebook [here](https://www.facebook.com/SquinkyLabs). + +## Manuals + +Here is the user's manual for our modules: [instruction manual](./docs/booty-shifter.md). It contains descriptions of all of them. + +## 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. + +For general communications, you may use our [Facebook Page](https://www.facebook.com/SquinkyLabs). + +We are not currently accepting pull requests. + +## More information for programmers, builders, and experimenters + +There are "secret" modules, extra code and other things scattered around this repo. Pointers to some of it can be found [in the docs folder](./docs/README.md). diff --git a/plugins/community/repos/squinkylabs-plug1/composites/ColoredNoise.h b/plugins/community/repos/squinkylabs-plug1/composites/ColoredNoise.h new file mode 100644 index 00000000..087daff3 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/composites/ColoredNoise.h @@ -0,0 +1,309 @@ +#pragma once + +#include +#include "assert.h" + +#include "AudioMath.h" +#include "ManagedPool.h" +#include "ThreadClient.h" +#include "ThreadServer.h" +#include "ThreadSharedState.h" + +#include "FFTData.h" +#include "FFT.h" +#include "FFTCrossFader.h" + +class NoiseMessage; + +const int crossfadeSamples = 4 * 1024; +template + +/** + * Implementation of the "Colors" noises generator + */ +class ColoredNoise : public TBase +{ +public: + ColoredNoise(struct Module * module) : TBase(module), crossFader(crossfadeSamples) + { + commonConstruct(); + } + ColoredNoise() : TBase(), crossFader(crossfadeSamples) + { + commonConstruct(); + } + ~ColoredNoise() + { + thread.reset(); // kill the threads before deleting other things + } + + void setSampleRate(float rate) + { + } + + // must be called after setSampleRate + void init() + { + cv_scaler = AudioMath::makeLinearScaler(-8, 8); + } + + // Define all the enums here. This will let the tests and the widget access them. + enum ParamIds + { + SLOPE_PARAM, + SLOPE_TRIM, + NUM_PARAMS + }; + + enum InputIds + { + SLOPE_CV, + NUM_INPUTS + }; + + enum OutputIds + { + AUDIO_OUTPUT, + NUM_OUTPUTS + }; + + enum LightIds + { + NUM_LIGHTS + }; + + /** + * Main processing entry point. Called every sample + */ + void step(); + + float getSlope() const; + + int _msgCount() const; // just for debugging + + typedef float T; // use floats for all signals +private: + + AudioMath::ScaleFun cv_scaler; + bool isRequestPending = false; + + /** + * crossFader generates the audio, but we must + * feed it with NoiseMessage data from the ThreadServer + */ + FFTCrossFader crossFader; + + // just for debugging + int messageCount = 0; + + std::unique_ptr thread; + + /** + * Messages moved between thread, messagePool, and crossFader + * as new noise slopes are requested in response to CV/knob changes. + */ + ManagedPool messagePool; + + void serviceFFTServer(); + void serviceAudio(); + void serviceInputs(); + void commonConstruct(); +}; + +class NoiseMessage : public ThreadMessage +{ +public: + + NoiseMessage() : ThreadMessage(Type::NOISE), + dataBuffer(new FFTDataReal(defaultNumBins)) + { + } + + NoiseMessage(int numBins) : ThreadMessage(Type::NOISE), + dataBuffer(new FFTDataReal(numBins)) + { + } + ~NoiseMessage() + { + } + const int defaultNumBins = 64 * 1024; + + ColoredNoiseSpec noiseSpec; + + /** Server is going to fill this buffer up with time-domain data + */ + std::unique_ptr dataBuffer; +}; + +class NoiseServer : public ThreadServer +{ +public: + NoiseServer(std::shared_ptr state) : ThreadServer(state) + { + } +protected: + /** + * This is called on the server thread, not the audio thread. + * We have plenty of time to do some heavy lifting here. + */ + virtual void handleMessage(ThreadMessage* msg) override + { + if (msg->type != ThreadMessage::Type::NOISE) { + assert(false); + return; + } + + // Unpack the parameters, convert to frequency domain "noise" recipe + NoiseMessage* noiseMessage = static_cast(msg); + reallocSpectrum(noiseMessage); + FFT::makeNoiseSpectrum(noiseSpectrum.get(), + noiseMessage->noiseSpec); + +// Now inverse FFT to time domain noise in client's buffer + FFT::inverse(noiseMessage->dataBuffer.get(), *noiseSpectrum.get()); + FFT::normalize(noiseMessage->dataBuffer.get()); + sendMessageToClient(noiseMessage); + } +private: + std::unique_ptr noiseSpectrum; + + // may do nothing, may create the first buffer, + // may delete the old buffer and make a new one. + void reallocSpectrum(const NoiseMessage* msg) + { + if (noiseSpectrum && ((int) noiseSpectrum->size() == msg->dataBuffer->size())) { + return; + } + + noiseSpectrum.reset(new FFTDataCpx(msg->dataBuffer->size())); + } +}; + +template +float ColoredNoise::getSlope() const +{ + const NoiseMessage* curMsg = crossFader.playingMessage(); + return curMsg ? curMsg->noiseSpec.slope : 0; +} + +template +void ColoredNoise::commonConstruct() +{ + crossFader.enableMakeupGain(true); + std::shared_ptr threadState = std::make_shared(); + std::unique_ptr server(new NoiseServer(threadState)); + + std::unique_ptr client(new ThreadClient(threadState, std::move(server))); + this->thread = std::move(client); +} + +template +int ColoredNoise::_msgCount() const +{ + return messageCount; +} + + +template +void ColoredNoise::serviceFFTServer() +{ + // see if we need to request first frame of sample data + // first request will be white noise. Is that ok? + if (!isRequestPending && crossFader.empty()) { + assert(!messagePool.empty()); + NoiseMessage* msg = messagePool.pop(); + + bool sent = thread->sendMessage(msg); + if (sent) { + isRequestPending = true; + } else { + messagePool.push(msg); + } + } + + // see if any messages came back for us + ThreadMessage* newMsg = thread->getMessage(); + if (newMsg) { + ++messageCount; + assert(newMsg->type == ThreadMessage::Type::NOISE); + NoiseMessage* noise = static_cast(newMsg); + + isRequestPending = false; + + // put it in the cross fader for playback + // give the last one back + NoiseMessage* oldMsg = crossFader.acceptData(noise); + if (oldMsg) { + messagePool.push(oldMsg); + } + } +} + +template +void ColoredNoise::serviceAudio() +{ + float output = 0; + NoiseMessage* oldMessage = crossFader.step(&output); + if (oldMessage) { + // One frame may be done fading - we can take it back. + messagePool.push(oldMessage); + } + + TBase::outputs[AUDIO_OUTPUT].value = output; +} + + +template +void ColoredNoise::serviceInputs() +{ + if (isRequestPending) { + return; // can't do anything until server is free. + } + if (crossFader.empty()) { + return; // if we don't have data, we will be asking anyway + } + if (messagePool.empty()) { + return; // all our buffers are in use + } + + T combinedSlope = cv_scaler( + TBase::inputs[SLOPE_CV].value, + TBase::params[SLOPE_PARAM].value, + TBase::params[SLOPE_TRIM].value); + + // get slope input to one decimal place + int i = int(combinedSlope * 10); + combinedSlope = i / 10.f; + ColoredNoiseSpec sp; + sp.slope = combinedSlope; + sp.highFreqCorner = 6000; + const NoiseMessage* playingData = crossFader.playingMessage(); + if (!playingData || !(sp != playingData->noiseSpec)) { + // If we aren't playing yet, or no change in slope, + // the don't do anything + return; + } + + assert(!messagePool.empty()); + NoiseMessage* msg = messagePool.pop(); + assert(msg); + if (!msg) { + return; + } + msg->noiseSpec = sp; + // TODO: put this logic in one place + bool sent = thread->sendMessage(msg); + if (sent) { + isRequestPending = true; + } else { + messagePool.push(msg); + } +} + +template +void ColoredNoise::step() +{ + serviceFFTServer(); + serviceAudio(); + serviceInputs(); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/composites/FrequencyShifter.h b/plugins/community/repos/squinkylabs-plug1/composites/FrequencyShifter.h new file mode 100644 index 00000000..67456eb1 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/composites/FrequencyShifter.h @@ -0,0 +1,129 @@ +#pragma once + +#include "LookupTable.h" +#include "SinOscillator.h" +#include "BiquadFilter.h" +#include "BiquadParams.h" +#include "BiquadState.h" +#include "HilbertFilterDesigner.h" + +/** + * Complete Frequency Shifter composite + * + * If TBase is WidgetComposite, this class is used as the implementation part of the Booty Shifter module. + * If TBase is TestComposite, this class may stand alone for unit tests. + */ +template +class FrequencyShifter : public TBase +{ +public: + FrequencyShifter(struct Module * module) : TBase(module) + { + } + FrequencyShifter() : TBase() + { + } + void setSampleRate(float rate) + { + reciprocalSampleRate = 1 / rate; + HilbertFilterDesigner::design(rate, hilbertFilterParamsSin, hilbertFilterParamsCos); + } + + // must be called after setSampleRate + void init() + { + SinOscillator::setFrequency(oscParams, T(.01)); + exponential2 = ObjectCache::getExp2(); // Get a shared copy of the 2**x lookup. + // This will enable exp mode to track at + // 1V/ octave. + } + + // Define all the enums here. This will let the tests and the widget access them. + enum ParamIds + { + PITCH_PARAM, // the big pitch knob + NUM_PARAMS + }; + + enum InputIds + { + AUDIO_INPUT, + CV_INPUT, + NUM_INPUTS + }; + + enum OutputIds + { + SIN_OUTPUT, + COS_OUTPUT, + NUM_OUTPUTS + }; + + enum LightIds + { + NUM_LIGHTS + }; + + /** + * Main processing entry point. Called every sample + */ + void step(); + + typedef float T; // use floats for all signals + T freqRange = 5; // the freq range switch +private: + SinOscillatorParams oscParams; + SinOscillatorState oscState; + BiquadParams hilbertFilterParamsSin; + BiquadParams hilbertFilterParamsCos; + BiquadState hilbertFilterStateSin; + BiquadState hilbertFilterStateCos; + + std::shared_ptr> exponential2; + + float reciprocalSampleRate; +}; + +template +inline void FrequencyShifter::step() +{ + assert(exponential2->isValid()); + + // Add the knob and the CV value. + T freqHz; + T cvTotal = TBase::params[PITCH_PARAM].value + TBase::inputs[CV_INPUT].value; + if (cvTotal > 5) { + cvTotal = 5; + } + if (cvTotal < -5) { + cvTotal = -5; + } + if (freqRange > .2) { + cvTotal *= freqRange; + cvTotal *= T(1. / 5.); + freqHz = cvTotal; + } else { + cvTotal += 7; // shift up to GE 2 (min value for out 1v/oct lookup) + freqHz = LookupTable::lookup(*exponential2, cvTotal); + freqHz /= 2; // down to 2..2k range that we want. + } + + SinOscillator::setFrequency(oscParams, freqHz * reciprocalSampleRate); + + // Generate the quadrature sin oscillators. + T x, y; + SinOscillator::runQuadrature(x, y, oscState, oscParams); + + // Filter the input through th quadrature filter + const T input = TBase::inputs[AUDIO_INPUT].value; + const T hilbertSin = BiquadFilter::run(input, hilbertFilterStateSin, hilbertFilterParamsSin); + const T hilbertCos = BiquadFilter::run(input, hilbertFilterStateCos, hilbertFilterParamsCos); + + // Cross modulate the two sections. + x *= hilbertSin; + y *= hilbertCos; + + // And combine for final SSB output. + TBase::outputs[SIN_OUTPUT].value = x + y; + TBase::outputs[COS_OUTPUT].value = x - y; +} diff --git a/plugins/community/repos/squinkylabs-plug1/composites/TestComposite.h b/plugins/community/repos/squinkylabs-plug1/composites/TestComposite.h new file mode 100644 index 00000000..3f68162a --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/composites/TestComposite.h @@ -0,0 +1,65 @@ +#pragma once + +#include + +/** +* Base class for composites embeddable in a unit test +* Isolates test from VCV. +*/ + +class TestComposite +{ +public: + TestComposite() : + inputs(20), + outputs(20), + params(20), + lights(20) + { + + } + struct Param + { + float value = 0.0; + }; + + struct Light + { + /** The square of the brightness value */ + float value = 0.0; + float getBrightness(); + void setBrightness(float brightness) + { + value = (brightness > 0.f) ? brightness * brightness : 0.f; + } + void setBrightnessSmooth(float brightness); + }; + + struct Input + { + /** Voltage of the port, zero if not plugged in. Read-only by Module */ + float value = 0.0; + /** Whether a wire is plugged in */ + bool active = false; + Light plugLights[2]; + /** Returns the value if a wire is plugged in, otherwise returns the given default value */ + float normalize(float normalValue) + { + return active ? value : normalValue; + } + }; + + struct Output + { + /** Voltage of the port. Write-only by Module */ + float value = 0.0; + /** Whether a wire is plugged in */ + bool active = false; + Light plugLights[2]; + }; + + std::vector inputs; + std::vector outputs; + std::vector params; + std::vector lights; +}; diff --git a/plugins/community/repos/squinkylabs-plug1/composites/Tremolo.h b/plugins/community/repos/squinkylabs-plug1/composites/Tremolo.h new file mode 100644 index 00000000..57fd03af --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/composites/Tremolo.h @@ -0,0 +1,230 @@ + +#pragma once + +#include + +#include "ClockMult.h" +#include "ObjectCache.h" +#include "AsymRampShaper.h" +#include "GateTrigger.h" + +/** + */ +template +class Tremolo : public TBase +{ +public: + Tremolo(struct Module * module) : TBase(module) + { + } + Tremolo() : TBase() + { + } + void setSampleRate(float rate) + { + reciprocalSampleRate = 1 / rate; + } + + // must be called after setSampleRate + void init(); + + enum ParamIds + { + LFO_RATE_PARAM, + LFO_SHAPE_PARAM, + LFO_SKEW_PARAM, + LFO_PHASE_PARAM, + MOD_DEPTH_PARAM, + CLOCK_MULT_PARAM, + + LFO_SHAPE_TRIM_PARAM, + LFO_SKEW_TRIM_PARAM, + LFO_PHASE_TRIM_PARAM, + MOD_DEPTH_TRIM_PARAM, + NUM_PARAMS + }; + + enum InputIds + { + AUDIO_INPUT, + CLOCK_INPUT, + LFO_SHAPE_INPUT, + LFO_SKEW_INPUT, + LFO_PHASE_INPUT, + MOD_DEPTH_INPUT, + NUM_INPUTS + }; + + enum OutputIds + { + AUDIO_OUTPUT, + SAW_OUTPUT, + LFO_OUTPUT, + NUM_OUTPUTS + }; + + enum LightIds + { + NUM_LIGHTS + }; + + /** + * Main processing entry point. Called every sample + */ + void step(); + +private: + + ClockMult clock; + std::shared_ptr> tanhLookup; + float reciprocalSampleRate = 0; + + AsymRampShaperParams rampShaper; + std::shared_ptr> exp2 = ObjectCache::getExp2(); + + // make some bootstrap scalers + AudioMath::ScaleFun scale_rate; + AudioMath::ScaleFun scale_skew; + AudioMath::ScaleFun scale_shape; + AudioMath::ScaleFun scale_depth; + AudioMath::ScaleFun scale_phase; + + GateTrigger gateTrigger; +}; + + + +template +inline void Tremolo::init() +{ + tanhLookup = ObjectCache::getTanh5(); + clock.setMultiplier(0); + + scale_rate = AudioMath::makeLinearScaler(4.f, 9.f); // log domain, 16 range + scale_skew = AudioMath::makeLinearScaler(-.99f, .99f); + scale_shape = AudioMath::makeLinearScaler(0.f, 1.f); + scale_depth = AudioMath::makeLinearScaler(0.f, 1.f); + scale_phase = AudioMath::makeLinearScaler(-1.f, 1.f); +} + +template +inline void Tremolo::step() +{ + gateTrigger.go(TBase::inputs[CLOCK_INPUT].value); + if (gateTrigger.trigger()) { + clock.refClock(); + } + + int clockMul = (int) round(TBase::params[CLOCK_MULT_PARAM].value); + + // UI is shifted + clockMul++; + if (clockMul > 4) { + clockMul = 0; + } + + + + clock.setMultiplier(clockMul); + + + const float shape = scale_shape( + TBase::inputs[LFO_SHAPE_INPUT].value, + TBase::params[LFO_SHAPE_PARAM].value, + TBase::params[LFO_SHAPE_TRIM_PARAM].value); + + const float skew = scale_skew( + TBase::inputs[LFO_SKEW_INPUT].value, + TBase::params[LFO_SKEW_PARAM].value, + TBase::params[LFO_SKEW_TRIM_PARAM].value); + + const float phase = scale_phase( + TBase::inputs[LFO_PHASE_INPUT].value, + TBase::params[LFO_PHASE_PARAM].value, + TBase::params[LFO_PHASE_TRIM_PARAM].value); + + const float modDepth = scale_depth( + TBase::inputs[MOD_DEPTH_INPUT].value, + TBase::params[MOD_DEPTH_PARAM].value, + TBase::params[MOD_DEPTH_TRIM_PARAM].value); + + if (clockMul == 0) // only calc rate for internal + { + const float logRate = scale_rate( + 0, + TBase::params[LFO_RATE_PARAM].value, + 1); + + float rate = LookupTable::lookup(*exp2, logRate); + float scaledRate = rate * .06f; + clock.setFreeRunFreq(scaledRate * reciprocalSampleRate); + } + + + + // For now, call setup every sample. will eat a lot of cpu + AsymRampShaper::setup(rampShaper, skew, phase); + + // ------------ now generate the lfo waveform + clock.sampleClock(); + float mod = clock.getSaw(); + mod = AsymRampShaper::proc_1(rampShaper, mod); + mod -= 0.5f; + // now we have a skewed saw -.5 to .5 + TBase::outputs[SAW_OUTPUT].value = mod; + + // TODO: don't scale twice - just get it right the first tme + const float shapeMul = std::max(.25f, 10 * shape); + mod *= shapeMul; + + mod = LookupTable::lookup(*tanhLookup.get(), mod); + TBase::outputs[LFO_OUTPUT].value = mod; + + const float gain = modDepth / + LookupTable::lookup(*tanhLookup.get(), (shapeMul / 2)); + const float finalMod = gain * mod + 1; // TODO: this offset by 1 is pretty good, but we + // could add an offset control to make it really "chop" off + + TBase::outputs[AUDIO_OUTPUT].value = TBase::inputs[AUDIO_INPUT].value * finalMod; +} + +/* + + +old plug proc loop. + +// Step 1: generate a saw +// range is 0..1 +SawOsc::gen_v(*sawState, *sawParams, tempBuffer, sampleFrames); + +// step 2: apply skew and phase shift +// range still 0..1 +AsymRampShaper::proc_v(*shaperParams, tempBuffer, tempBuffer, sampleFrames); + +// step 3: shift down to be centered at zero, +// max excursion +-5 at shape "most square" +// min is +/- .25 TODO: put the .25 into the control range itself + +// range = +/- (5 * shape) +// +f_t shapeMul = std::max(.25, 10 * controlValues.lfoShape); +VecBasic::add_mul_c_imp(tempBuffer, sampleFrames, shapeMul, -.5f); + +// now tanh, +// output contered around zero, +// max is tanh(.25) to tanh(5), depending on shape value +// rang = +/- tanh(5 * shape) +LookupUniform::lookup_clip_v(*tanhParams, tempBuffer, tempBuffer, sampleFrames); + + // so: makeup gain of 1/tanh(shapeMul) will get us to +1/-1 + // then multiply by depth to get contered around zero with correct depth + // the add one to get back to trem range! + f_t gain = controlValues.modDepth / tanh(shapeMul/2); + VecBasic::mul_add_c_imp(tempBuffer, sampleFrames, gain, 1); +// scale then add constant + // input = a * input + b + static void mul_add_c_imp(f_t * inout, int size, f_t a, f_t b) { + assert_size(size); + + // now range = +/- tanh(5*shape) * depth / tanh(10 * shape) +*/ diff --git a/plugins/community/repos/squinkylabs-plug1/composites/VocalAnimator.h b/plugins/community/repos/squinkylabs-plug1/composites/VocalAnimator.h new file mode 100644 index 00000000..1a18c209 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/composites/VocalAnimator.h @@ -0,0 +1,307 @@ +#pragma once +#include + +#include "AudioMath.h" +#include "LookupTable.h" +#include "LookupTableFactory.h" +#include "MultiModOsc.h" +#include "ObjectCache.h" +#include "StateVariableFilter.h" + +#define _ANORM + +/** + * Version 2 - make the math sane. + */ +template +class VocalAnimator : public TBase +{ +public: + typedef float T; + static const int numTriangle = 4; + static const int numModOutputs = 3; + static const int numFilters = 4; + + VocalAnimator(struct Module * module) : TBase(module) + { + } + VocalAnimator() : TBase() + { + } + + void setSampleRate(float rate) + { + reciprocalSampleRate = 1 / rate; + modulatorParams.setRateAndSpread(.5, .5, 0, reciprocalSampleRate); + } + + enum ParamIds + { + LFO_RATE_PARAM, + FILTER_Q_PARAM, + FILTER_FC_PARAM, + FILTER_MOD_DEPTH_PARAM, + LFO_RATE_TRIM_PARAM, + FILTER_Q_TRIM_PARAM, + FILTER_FC_TRIM_PARAM, + FILTER_MOD_DEPTH_TRIM_PARAM, + BASS_EXP_PARAM, + + // tracking: + // 0 = all 1v/octave, mod scaled, no on top + // 1 = mod and cv scaled + // 2 = 1, + top filter gets some mod + TRACK_EXP_PARAM, + + // LFO mixing options + // 0 = classic + // 1 = option + // 2 = lf sub + LFO_MIX_PARAM, + + NUM_PARAMS + }; + + enum InputIds + { + AUDIO_INPUT, + LFO_RATE_CV_INPUT, + FILTER_Q_CV_INPUT, + FILTER_FC_CV_INPUT, + FILTER_MOD_DEPTH_CV_INPUT, + NUM_INPUTS + }; + + enum OutputIds + { + AUDIO_OUTPUT, + LFO0_OUTPUT, + LFO1_OUTPUT, + LFO2_OUTPUT, + NUM_OUTPUTS + }; + + enum LightIds + { + LFO0_LIGHT, + LFO1_LIGHT, + LFO2_LIGHT, + BASS_LIGHT, + NUM_LIGHTS + }; + + void init(); + void step(); + T modulatorOutput[numModOutputs]; + + // The frequency inputs to the filters, exposed for testing. + + T filterFrequencyLog[numFilters]; + + const T nominalFilterCenterHz[numFilters] = {522, 1340, 2570, 3700}; + const T nominalFilterCenterLog2[numFilters] = { + std::log2(T(522)), + std::log2(T(1340)), + std::log2(T(2570)), + std::log2(T(3700)) + }; + // 1, .937 .3125 + const T nominalModSensitivity[numFilters] = {T(1), T(.937), T(.3125), 0}; + + // Following are for unit tests. + T normalizedFilterFreq[numFilters]; + bool jamModForTest = false; + T modValueForTest = 0; + + float reciprocalSampleRate; + + using osc = MultiModOsc; + typename osc::State modulatorState; + typename osc::Params modulatorParams; + + StateVariableFilterState filterStates[numFilters]; + StateVariableFilterParams filterParams[numFilters]; + + std::shared_ptr> expLookup; + + // We need a bunch of scalers to convert knob, CV, trim into the voltage + // range each parameter needs. + AudioMath::ScaleFun scale0_1; + AudioMath::ScaleFun scalem2_2; + AudioMath::ScaleFun scaleQ; + AudioMath::ScaleFun scalen5_5; +}; + +template +inline void VocalAnimator::init() +{ + for (int i = 0; i < numFilters; ++i) { + filterParams[i].setMode(StateVariableFilterParams::Mode::BandPass); + filterParams[i].setQ(15); // or should it be 5? + + filterParams[i].setFreq(nominalFilterCenterHz[i] * reciprocalSampleRate); + filterFrequencyLog[i] = nominalFilterCenterLog2[i]; + + 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); + + // make table of 2 ** x + expLookup = ObjectCache::getExp2(); +} + +template +inline void VocalAnimator::step() +{ + const bool bass = TBase::params[BASS_EXP_PARAM].value > .5; + const auto mode = bass ? + StateVariableFilterParams::Mode::LowPass : + StateVariableFilterParams::Mode::BandPass; + + for (int i = 0; i < numFilters +1 - 1; ++i) { + filterParams[i].setMode(mode); + } + + // Run the modulators, hold onto their output. + // Raw Modulator outputs put in modulatorOutputs[]. + osc::run(modulatorOutput, modulatorState, modulatorParams); + + static const OutputIds LEDOutputs[] = { + LFO0_OUTPUT, + LFO1_OUTPUT, + LFO2_OUTPUT, + }; + // Light up the LEDs with the unscaled Modulator outputs. + for (int i = LFO0_LIGHT; i <= LFO2_LIGHT; ++i) { + TBase::outputs[LEDOutputs[i]].value = modulatorOutput[i]; + TBase::lights[i].value = (modulatorOutput[i] ) * .3f; + TBase::outputs[LEDOutputs[i]].value = modulatorOutput[i]; + } + + // Normalize all the parameters out here + const T qFinal = scaleQ( + TBase::inputs[FILTER_Q_CV_INPUT].value, + TBase::params[FILTER_Q_PARAM].value, + TBase::params[FILTER_Q_TRIM_PARAM].value); + + + const T fc = scalen5_5( + TBase::inputs[FILTER_FC_CV_INPUT].value, + TBase::params[FILTER_FC_PARAM].value, + TBase::params[FILTER_FC_TRIM_PARAM].value); + + + // put together a mod depth parameter from all the inputs + // range is 0..1 + + // cv, knob, trim + const T baseModDepth = scale0_1( + TBase::inputs[FILTER_MOD_DEPTH_CV_INPUT].value, + TBase::params[FILTER_MOD_DEPTH_PARAM].value, + TBase::params[FILTER_MOD_DEPTH_TRIM_PARAM].value); + + // tracking: + // 0 = all 1v/octave, mod scaled, no on top + // 1 = mod and cv scaled + // 2 = 1, + top filter gets some mod + int cvScaleMode = 0; + const float cvScaleParam = TBase::params[TRACK_EXP_PARAM].value; + if (cvScaleParam < .5) { + cvScaleMode = 0; + } else if (cvScaleParam < 1.5) { + cvScaleMode = 1; + } else { + cvScaleMode = 2; + assert(cvScaleParam < 2.5); + } + + // Just do the Q division once, in the outer loop + const T filterNormalizedBandwidth = T(1) / qFinal; + const T input = TBase::inputs[AUDIO_INPUT].value; + T filterMix = 0; // Sum the folder outputs here + + for (int i = 0; i < numFilters; ++i) { + T logFreq = nominalFilterCenterLog2[i]; + + switch (cvScaleMode) { + case 1: + // In this mode (1) CV comes straight through at 1V/8 + // Even on the top (fixed) filter + logFreq += fc; // add without attenuation for 1V/octave + break; + case 0: + // In mode (0) CV gets scaled per filter, as in the original design. + // Since nominalModSensitivity[3] == 0, top doesn't track + logFreq += fc * nominalModSensitivity[i]; + break; + case 2: + if (fc < 0) { + // Normal scaling for Fc less than nominal + logFreq += fc * nominalModSensitivity[i]; + } else { + // above nominal, they all track the high one (so they don't cross) + logFreq += fc * nominalModSensitivity[2]; + } + + break; + default: + assert(false); + } + + // First three filters always modulated, + // (wanted to try modulating 4, but don't have an LFO (yet + const bool modulateThisFilter = (i < 3); + if (modulateThisFilter) { + logFreq += modulatorOutput[i] * + baseModDepth * + nominalModSensitivity[i]; + } + + logFreq += ((i < 3) ? modulatorOutput[i] : 0) * + baseModDepth * + nominalModSensitivity[i]; + + filterFrequencyLog[i] = logFreq; + + // tell lookup not to assert - we know we can go slightly out of range. + T normFreq = LookupTable::lookup(*expLookup, logFreq, true) * reciprocalSampleRate; + normFreq = std::min(normFreq, T(.2)); + + normalizedFilterFreq[i] = normFreq; + filterParams[i].setFreq(normFreq); + + filterParams[i].setNormalizedBandwidth(filterNormalizedBandwidth); + filterMix += StateVariableFilter::run(input, filterStates[i], filterParams[i]); + } +#ifdef _ANORM + filterMix *= filterNormalizedBandwidth * 2; +#else + filterMix *= T(.3); // attenuate to avoid clip +#endif + TBase::outputs[AUDIO_OUTPUT].value = filterMix; + + + int matrixMode; + float mmParam = TBase::params[LFO_MIX_PARAM].value; + if (mmParam < .5) { + matrixMode = 0; + } else if (mmParam < 1.5) { + matrixMode = 1; + } else { + matrixMode = 2; + assert(mmParam < 2.5); + } + + const T spread = T(1.0); + modulatorParams.setRateAndSpread( + scalem2_2( + TBase::inputs[LFO_RATE_CV_INPUT].value, + TBase::params[LFO_RATE_PARAM].value, + TBase::params[LFO_RATE_TRIM_PARAM].value), + spread, + matrixMode, + reciprocalSampleRate); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/composites/VocalFilter.h b/plugins/community/repos/squinkylabs-plug1/composites/VocalFilter.h new file mode 100644 index 00000000..4e0b1d11 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/composites/VocalFilter.h @@ -0,0 +1,212 @@ +#pragma once +#include +#include + +#include "AudioMath.h" +#include "FormantTables2.h" +#include "LookupTable.h" +#include "LookupTableFactory.h" +#include "ObjectCache.h" +#include "StateVariableFilter.h" + +/** + * + */ +template +class VocalFilter : public TBase +{ +public: + typedef float T; + static const int numFilters = FormantTables2::numFormantBands; + + VocalFilter(struct Module * module) : TBase(module) + { + } + VocalFilter() : TBase() + { + } + + void setSampleRate(float rate) + { + reciprocalSampleRate = 1 / rate; + } + + enum ParamIds + { + FILTER_Q_PARAM, + FILTER_Q_TRIM_PARAM, + FILTER_FC_PARAM, + FILTER_FC_TRIM_PARAM, + FILTER_VOWEL_PARAM, + FILTER_VOWEL_TRIM_PARAM, + FILTER_MODEL_SELECT_PARAM, + FILTER_BRIGHTNESS_PARAM, + FILTER_BRIGHTNESS_TRIM_PARAM, + NUM_PARAMS + }; + + enum InputIds + { + AUDIO_INPUT, + FILTER_Q_CV_INPUT, + FILTER_FC_CV_INPUT, + FILTER_VOWEL_CV_INPUT, + FILTER_BRIGHTNESS_INPUT, + NUM_INPUTS + }; + + enum OutputIds + { + AUDIO_OUTPUT, + NUM_OUTPUTS + }; + + enum LightIds + { + LED_A, + LED_E, + LED_I, + LED_O, + LED_U, + NUM_LIGHTS + }; + + void init(); + void step(); + + float reciprocalSampleRate; + + // The frequency inputs to the filters, exposed for testing. + + T filterFrequencyLog[numFilters]; + + StateVariableFilterState filterStates[numFilters]; + StateVariableFilterParams filterParams[numFilters]; + + FormantTables2 formantTables; + std::shared_ptr> expLookup; + std::shared_ptr> db2GainLookup; + + AudioMath::ScaleFun scaleCV_to_formant; + AudioMath::ScaleFun scaleQ; + AudioMath::ScaleFun scaleFc; + AudioMath::ScaleFun scaleBrightness; +}; + +template +inline void VocalFilter::init() +{ + for (int i = 0; i < numFilters; ++i) { + filterParams[i].setMode(StateVariableFilterParams::Mode::BandPass); + filterParams[i].setQ(15); // or should it be 5? + + filterParams[i].setFreq(T(.1)); + } + scaleCV_to_formant = AudioMath::makeLinearScaler(0, formantTables.numVowels - 1); + scaleFc = AudioMath::makeLinearScaler(-2, 2); + scaleBrightness = AudioMath::makeLinearScaler(0, 1); + + AudioMath::ScaleFun rawQKnob = AudioMath::makeLinearScaler(-1, 1); + scaleQ = [rawQKnob](T cv, T param, T trim) { + T temp = rawQKnob(cv, param, trim); + return (temp >= 0) ? + 1 - 3 * temp / 4 : + 1 - temp; + }; + + // get reference to table of 2 ** x + expLookup = ObjectCache::getExp2(); + db2GainLookup = ObjectCache::getDb2Gain(); +} + +template +inline void VocalFilter::step() +{ + int model = 0; + const T switchVal = TBase::params[FILTER_MODEL_SELECT_PARAM].value; + if (switchVal < .5) { + model = 0; + assert(switchVal > -.5); + } else if (switchVal < 1.5) { + model = 1; + } else if (switchVal < 2.5) { + model = 2; + } else if (switchVal < 3.5) { + model = 3; + } else { + model = 4; + assert(switchVal < 4.5); + } + + const T fVowel = scaleCV_to_formant( + TBase::inputs[FILTER_VOWEL_CV_INPUT].value, + TBase::params[FILTER_VOWEL_PARAM].value, + TBase::params[FILTER_VOWEL_TRIM_PARAM].value); + + + int iVowel = (int) std::floor(fVowel); + + assert(iVowel >= 0); + if (iVowel >= formantTables.numVowels) { + printf("formant overflow %f\n", fVowel); + iVowel = formantTables.numVowels - 1; + } + +#if 1 + 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; + } else if (i != (iVowel + 1)) { + TBase::lights[i].value = 0; + } + } +#else + for (int i = LED_A; i <= LED_U; ++i) { + TBase::lights[i].value = (i == iVowel) ? T(10) : T(0); + } +#endif + + const T bwMultiplier = scaleQ( + TBase::inputs[FILTER_Q_CV_INPUT].value, + TBase::params[FILTER_Q_PARAM].value, + TBase::params[FILTER_Q_TRIM_PARAM].value); + // printf("bwMultiplier = %f\n", bwMultiplier); + + + const T fPara = scaleFc( + TBase::inputs[FILTER_FC_CV_INPUT].value, + TBase::params[FILTER_FC_PARAM].value, + TBase::params[FILTER_FC_TRIM_PARAM].value); + // fNow -5..5, log + + const T brightness = scaleBrightness( + TBase::inputs[FILTER_BRIGHTNESS_INPUT].value, + TBase::params[FILTER_BRIGHTNESS_PARAM].value, + TBase::params[FILTER_BRIGHTNESS_TRIM_PARAM].value); + + T input = TBase::inputs[AUDIO_INPUT].value; + T filterMix = 0; + for (int i = 0; i < numFilters; ++i) { + const T fcLog = formantTables.getLogFrequency(model, i, fVowel); + const T normalizedBw = bwMultiplier * formantTables.getNormalizedBandwidth(model, i, fVowel); + + // Get the filter gain from the table, but scale by BW to counteract the filters + // gain that tracks Q + T gainDB = formantTables.getGain(model, i, fVowel); + + // blend the table with full gain depending on brightness + T modifiedGainDB = (1 - gainDB) * brightness + gainDB; + + // TODO: why is normalizedBW in this equation? + const T gain =LookupTable::lookup(*db2GainLookup, modifiedGainDB) * normalizedBw; + + T fcFinalLog = fcLog + fPara; + T fcFinal = LookupTable::lookup(*expLookup, fcFinalLog); + + filterParams[i].setFreq(fcFinal * reciprocalSampleRate); + filterParams[i].setNormalizedBandwidth(normalizedBw); + filterMix += gain * StateVariableFilter::run(input, filterStates[i], filterParams[i]); + } + TBase::outputs[AUDIO_OUTPUT].value = 3 * filterMix; +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/composites/WidgetComposite.h b/plugins/community/repos/squinkylabs-plug1/composites/WidgetComposite.h new file mode 100644 index 00000000..b1f3bfc7 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/composites/WidgetComposite.h @@ -0,0 +1,22 @@ +#pragma once + +/** + * Base class for composites embeddable in a VCV Widget + * This is used for "real" implementations + */ +class WidgetComposite +{ +public: + WidgetComposite(Module * parent) : + inputs(parent->inputs), + outputs(parent->outputs), + params(parent->params), + lights(parent->lights) + { + } +protected: + std::vector& inputs; + std::vector& outputs; + std::vector& params; + std::vector& lights; +}; diff --git a/plugins/community/repos/squinkylabs-plug1/docs/README.md b/plugins/community/repos/squinkylabs-plug1/docs/README.md new file mode 100644 index 00000000..71d640ab --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/docs/README.md @@ -0,0 +1,31 @@ +# Squinky Labs modules for VCV Rack + +All of our plugins are free and open source. The [instruction manual](booty-shifter.md) describes all of the released modules. + +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). + +## Information for developer and experimenters + +There are various test modules, test code, and other good things hidden away in this repo. We will try to point you to some that may be of interest. + +Most of the documentation may be found in the [docs folder](../docs/.). + +## Building source + +As with all third-party modules for VCV, you must: + +* Clone the VCV Rack repo. +* Build Rack from source. +* Clone SquinkyVCV in Rack’s plugins folder. +* `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). \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/docs/booty-shifter.md b/plugins/community/repos/squinkylabs-plug1/docs/booty-shifter.md new file mode 100644 index 00000000..44c078a4 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/docs/booty-shifter.md @@ -0,0 +1,207 @@ +# Table of contents + +[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) + +# Chopper tremolo / programmable LFO + +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 + +![chopper image](../docs/lfo-waveforms.png) + +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 + +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 + +![noise image](../docs/colors.png) + +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 + +![vocal formant filter image](./growler.jpg) + +**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 +![growler scope](./growler.png) + +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 + +**Booty Shifter** is a frequency shifter inspired by the Moog/Bode frequency shifter module. + +![booty shifter image](./booty-shifter.png) + +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 + +![formants image](./formants.png) + +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 + +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 + +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. diff --git a/plugins/community/repos/squinkylabs-plug1/docs/booty-shifter.png b/plugins/community/repos/squinkylabs-plug1/docs/booty-shifter.png new file mode 100644 index 00000000..1d5c6553 Binary files /dev/null and b/plugins/community/repos/squinkylabs-plug1/docs/booty-shifter.png differ diff --git a/plugins/community/repos/squinkylabs-plug1/docs/colors.png b/plugins/community/repos/squinkylabs-plug1/docs/colors.png new file mode 100644 index 00000000..81660f76 Binary files /dev/null and b/plugins/community/repos/squinkylabs-plug1/docs/colors.png differ diff --git a/plugins/community/repos/squinkylabs-plug1/docs/composites.md b/plugins/community/repos/squinkylabs-plug1/docs/composites.md new file mode 100644 index 00000000..6a6a2e0d --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/docs/composites.md @@ -0,0 +1,67 @@ +# About composites + +This is a somewhat ugly "architecture" that lets the same module "guts" run inside a VCV plugin, and also turn inside our unit test framework (which does not link against VCV Rack). + +While this is slightly ugly, it is incredibly useful to us to be able to test and benchmark our plugins from a simple command line program. + +You may look at the [FrequencyShifter composite](../composites/FrequencyShifter.h) as an example. + +Here is [more on our tests](unit-test.md). + +## To make a composite + +Make a new class in the composites folder. This class is your composite class. This class must work in the test environment and the VCV Widget environment. + +Templatize the class so that its base class is the template parameter. For example, in the FrequencyShifter class: +``` +template +class FrequencyShifter : public TBase +{ +... +} +``` +Follow the link to look at [FrequencyShifter](composites/FrequencyShifter.h) + +Create two constructors. One with no arguments for the tests to use, and the other with a `Module *` to use in the VCV plugin. + +Put all the Input, Output, Param, Light enums into the composite class, rather than the normal Widget class. + +To use this composite in a test, derive concrete class from TestComposite +```c++ +using Shifter = FrequencyShifter; +``` +Now you may use this class in a test. The TestComposite base class give your tests access to the inputs and outputs of your "widget" +```c++ + Shifter fs; + + fs.setSampleRate(44100); + fs.init(); + fs.inputs[Shifter::AUDIO_INPUT].value = 0; +``` + +## How it works + +The class hierarchy is something like these crudely drawn UML diagrams +``` +TestWidget -> I/O + | + ^ + / \ + --- + | + FrequencyShifter -> enums + ``` + + ``` + ModuleWidget -> I/O -> Module + | + ^ + / \ + --- + | + FrequencyShifter -> enums +``` + +The purpose of the composite's base class is to provide the inputs/outputs/etc... to the composite. The TestComposite base class has vectors of I/O exposed as public members that the tests may directly manipulate. The WidgetComposite base class just marshals references to the I/O that is already in the VCV provided Widget base class. + +All of the enums for module I/O goes into the composite. So they are available to tests as composite.ENUM_NAME. They are also available to Modules as this->composite.ENUM_NAME. diff --git a/plugins/community/repos/squinkylabs-plug1/docs/experimental.md b/plugins/community/repos/squinkylabs-plug1/docs/experimental.md new file mode 100644 index 00000000..b5fbbbd5 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/docs/experimental.md @@ -0,0 +1,15 @@ +# Experimental Modules + +Usually we have one or two modules that work, but are not ready for release. Often they have very ugly panels, are missing features, aren't tuned yet for performance or playability. + +I most cases there are no pre-built binaries for these plugins, you must build them yourself. + +Never the less, we encourage the curious to build these, try them out, and report back to us on any bugs or desired features. + +## Building experiments + +To build all the modules, including the experimental ones, simply run `make _EXP=1`. + +## Current experiments + +The Thread Booster is quite experimental. It is described [here](thread-booster.md). This one does have a pre-built binary. diff --git a/plugins/community/repos/squinkylabs-plug1/docs/formants.png b/plugins/community/repos/squinkylabs-plug1/docs/formants.png new file mode 100644 index 00000000..c96ca7f2 Binary files /dev/null and b/plugins/community/repos/squinkylabs-plug1/docs/formants.png differ diff --git a/plugins/community/repos/squinkylabs-plug1/docs/growler.jpg b/plugins/community/repos/squinkylabs-plug1/docs/growler.jpg new file mode 100644 index 00000000..b8fef84b Binary files /dev/null and b/plugins/community/repos/squinkylabs-plug1/docs/growler.jpg differ diff --git a/plugins/community/repos/squinkylabs-plug1/docs/growler.png b/plugins/community/repos/squinkylabs-plug1/docs/growler.png new file mode 100644 index 00000000..89609c72 Binary files /dev/null and b/plugins/community/repos/squinkylabs-plug1/docs/growler.png differ diff --git a/plugins/community/repos/squinkylabs-plug1/docs/installing-binaries.md b/plugins/community/repos/squinkylabs-plug1/docs/installing-binaries.md new file mode 100644 index 00000000..064f0b2b --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/docs/installing-binaries.md @@ -0,0 +1,32 @@ +# Installing binaries + +This should rarely be needed, as the current versions of our modules are in the plugin manager. + +Download the current release from our [releases page](https://github.com/squinkylabs/SquinkyVCV/releases), or get them from someone who built them for you. Warning: often we do not post all the current releases. + +Follow the standard instructions for installing third-party plugins in rack: [here](https://vcvrack.com/manual/Installing.html). These instructions will tell you how to put the zip file in a location where VCV Rack will unzip it for you. + +## Unzipping yourself + +Usually letting VCV Rack unzip your plugins is easiest, so you shouldn't need this information. + +Note that the built-in unzip in windows explorer will create an extra folder called with the same name as the zip file. But when everything is unzipped you want your folder structure to look like this: + +``` +plugins/ + + squinkylabs-plug1/ + plugin.dll + plugin.dylib (optional, unused) + plugin.so (optional, unused) + LICENSE + res/ + + +``` + +The plugins will be under the **Rack** folder. Rack folders are here: + +* MacOS: Documents/Rack/ +* Windows: My Documents/Rack/ +* Linux: ~/.Rack/ diff --git a/plugins/community/repos/squinkylabs-plug1/docs/lfo-waveforms.png b/plugins/community/repos/squinkylabs-plug1/docs/lfo-waveforms.png new file mode 100644 index 00000000..e9cd9539 Binary files /dev/null and b/plugins/community/repos/squinkylabs-plug1/docs/lfo-waveforms.png differ diff --git a/plugins/community/repos/squinkylabs-plug1/docs/thread-booster.md b/plugins/community/repos/squinkylabs-plug1/docs/thread-booster.md new file mode 100644 index 00000000..89ebebc0 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/docs/thread-booster.md @@ -0,0 +1,66 @@ +# Thread Booster + +Thread booster boosts the thread priority of the audio streaming thread in VCV Rack. The intention is to make sure that if your computer strains to produce all that audio, audio will get priority in the competition for the CPU over other things like drawing the screen, or showing ads in your web browser. + +If you aren't a computer programmer, this may sound like some hocus-pocus. But it is a very basic technique used in many audio applications. Google "audio thread priority" for some random instances. + +If some other task is competing for CPU and the audio loses out, it may be unable to keep up with the audio interface. This will cause occasional data underruns, which often sound like pops and clicks. + +Now, while raising the audio thread priority is a fundamentally sound thing to do, it is not going to solve all audio problems. Some users report that Thread Booster does not help at all. Others report significant improvement. + +On all three operating systems we have been able to get the "real-time" setting to work (although read below - it's not always technically "real time"). + +## Notes for Linux + +Linux has two challenges for thread booster: + +* The non-real-time settings set by the POSIX Pthread API don't work. +* Raising thread priority to real-time in Linux is a privileged operation. + +The impact of the first challenge is that the middle "boosted" setting might not do much in Linux. We recommend the real-time setting. + +You can always set the real time priority of Rack if run as root, but we do not recommend that. Instead you may use `setcap` to give VCV Rack permission to use real-time priority. + +```bash +sudo setcap cap_sys_nice=ep +``` + +After giving Rack this ability, it will stay set until the Rack executable file is changed, either by downloading a new one, or building a new one on top of it. + +## Notes on Windows + +Unlike Linux, the middle boost setting works well on Windows. The realtime setting works well also, although it is not the very highest setting that windows calls realtime. Instead it sets the Rack process as a whole to HIGH_PRIORITY_CLASS, and the sets the audio thread to THREAD_PRIORITY_TIME_CRITICAL. We have found this to give a very good boost without requiring that you run Rack as administrator (which is not recommended). + +We only test on Windows 7. If you have issues with other versions of Windows, please let us know. + +## Notes on OS X + +As usual, it just works on OS X. + +## Caveats and limitations + +If you try this plugin, be aware that changing Rack’s thread priority behind its back may cause some things to misbehave. We have not seen this happen, but it's a real possibility. Running at an elevated priority could make something in VCV or the plugins misbehave or glitch. + +Another limitation is that Thread Booster does not boost the thread(s) that move data between Rack's audio engine and the Audio (sound card) Module(s). + +## Is it dangerous to run at "realtime" priority? + +If you scour the Internet you will find some warnings about boosting thread priorities to super high thread priorities, saying that there is a danger your computer will become unresponsive, possibly even needing to be re-booted. + +While in general this is true, it is not true that raising the priority of a single thread will do this, as a single thread can at worst use all the cycles of a single core. For the last 10 years or so all CPUs have more than one core, so there will always be plenty of CPU left over. + +Most audio programs in fact do raise the priority of their audio threads, and of course work fine. So, while we don't guarantee that Thread Booster will fix all clicks and pops, we do believe that it is very unlikely to do any harm. + +## Questions for testers + +Please use our [GitHub issues](https://github.com/squinkylabs/SquinkyVCV/issues) to send us feedback. Some things we are curious about: + +* With a fully loaded session that starts to make pops and clicks, does thread booster fix it? +* If you are able to run realtime, is there any noticeable difference between boosted and real-time? +* For all reports, please list operating system and version (i.e. Windows-10, OSX 10.12, Ubuntu 16.04). + +## Known issues + +On the Mac, Boosting and the switching back to normal will not restore the original priority. It will actually be running at a lower priority. + +When the Thread Booster comes up initially, no LEDs are illuminated - the normal LED should be. diff --git a/plugins/community/repos/squinkylabs-plug1/docs/unit-test.md b/plugins/community/repos/squinkylabs-plug1/docs/unit-test.md new file mode 100644 index 00000000..3f19698f --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/docs/unit-test.md @@ -0,0 +1,28 @@ +# Some notes on our unit tests + +The ./test folder contains a unit test program and test-support classes. + +In general most of this software is fairly unremarkable code that supports unit testing of the various parts of our plugins. + +We find that reasonably thorough unit testing is even more valuable for plugins than for "normal" code. Some of the reasons are: + +* Without unit tests, it can be very easy to make filters that don’t have the response they should ("oops – forget to multiply by 2 pi"), lookup tables aren’t as accurate as they should be, numeric approximations have gross discontinuities, etc. +* Unit tests may be built and debugged with a conventional IDE, whereas (at least for us) debugging a plugin with gdb from inside a running instance of VCV Rack is more difficult. +* It is easy to measure CPU usage in a unit test, whereas it is difficult to impossible to measure while running in VCV Rack. + +This last point is very important, as one of the basic rules of code optimization is that a programmers intuition (guess) as to where the bottlenecks are is remarkably unreliable – you must drive your optimizations from real data. For example, we discovered that over half the CPU time in our vocal filter was in evaluating assertions! + +As stated above, much of the test code is unremarkable, other than its existence. There are a couple of modules however that might be useful to others. + +[MeasureTime](../test/MeasureTime.h) is used to measure the CPU usage of any arbitrary code. It takes a simple lambda and profiles it. + +[Composite pattern](composites.md) allows us to run our plugin code inside a test application as well as inside a VCV Track plugin module. + +[Assert Library](../test/asserts.h) is a very basic collection of assertion macros loosely based on the Chai Assert framework. + +We have enhanced [Makefile](../Makefile) in several useful ways with the addition of [test.mk](../test.mk): + +* Default `make` will generate a plugin with assertions disabled. +* `make test` will generate test.exe, our unit test application, with asserts enabled. +* `make perf` will generate perf.exe, our unit test application, with asserts disabled. +* `make cleantest` is like `make clean`, but for out test code. diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFT.cpp b/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFT.cpp new file mode 100644 index 00000000..ab4c736c --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFT.cpp @@ -0,0 +1,201 @@ + +#include "AudioMath.h" +#include "FFT.h" +#include "FFTData.h" + +#include +#include "kiss_fft.h" +#include "kiss_fftr.h" + +#include "AudioMath.h" + + +bool FFT::forward(FFTDataCpx* out, const FFTDataReal& in) +{ + if (out->buffer.size() != in.buffer.size()) { + return false; + } + + // step 1: create the cfg, if needed + if (in.kiss_cfg == 0) { + bool inverse_fft = false; + kiss_fftr_cfg newCfg= kiss_fftr_alloc((int)in.buffer.size(), + inverse_fft, + nullptr, nullptr); + + assert (newCfg); + if (!newCfg) { + return false; + } + + // now save off in our typeless pointer. + assert(sizeof(newCfg) == sizeof(in.kiss_cfg)); + in.kiss_cfg = newCfg; + } + + // step 2: do the fft + kiss_fftr_cfg theCfg = reinterpret_cast(in.kiss_cfg); + + // TODO: need a test that this assumption is correct (that we kiss_fft_cpx == std::complex. + kiss_fft_cpx * outBuffer = reinterpret_cast(out->buffer.data()); + kiss_fftr(theCfg, in.buffer.data(), outBuffer); + + // step 4: scale + const float scale = float(1.0 / in.buffer.size()); + for (size_t i = 0; i < in.buffer.size(); ++i) { + out->buffer[i] *= scale; + } + + return true; +} + + +bool FFT::inverse(FFTDataReal* out, const FFTDataCpx& in) +{ + if (out->buffer.size() != in.buffer.size()) { + return false; + } + + // step 1: create the cfg, if needed + if (in.kiss_cfg == 0) { + bool inverse_fft = true; + kiss_fftr_cfg newCfg = kiss_fftr_alloc((int) in.buffer.size(), + inverse_fft, + nullptr, nullptr); + + assert(newCfg); + if (!newCfg) { + return false; + } + + // now save off in our typeless pointer. + assert(sizeof(newCfg) == sizeof(in.kiss_cfg)); + in.kiss_cfg = newCfg; + } + + // step 2: do the fft + kiss_fftr_cfg theCfg = reinterpret_cast(in.kiss_cfg); + + // TODO: need a test that this assumption is correct (that we kiss_fft_cpx == std::complex. + const kiss_fft_cpx * inBuffer = reinterpret_cast(in.buffer.data()); + + kiss_fftri(theCfg, inBuffer, out->buffer.data()); + return true; +} + +int FFT::freqToBin(float freq, float sampleRate, int numBins) +{ + assert(freq <= (sampleRate / 2)); + // bin(numBins) <> sr / 2; + return (int)((freq / sampleRate)*(numBins * 2)); +} + +float FFT::bin2Freq(int bin, float sampleRate, int numBins) +{ + return sampleRate * float(bin) / (float(numBins) * 2); +} + +static float randomPhase() +{ + float phase = (float) rand(); + phase = phase / (float) RAND_MAX; // 0..1 + phase = (float) (phase * (2 * AudioMath::Pi)); + return phase; +} + +static void makeNegSlope(FFTDataCpx* output, const ColoredNoiseSpec& spec) +{ + const int numBins = int(output->size()); + const float lowFreqCorner = 40; + + // find bin for 40 Hz + const int bin40 = FFT::freqToBin(lowFreqCorner, spec.sampleRate, numBins); + + // fill bottom bins with 1.0 mag + for (int i = 0; i <= bin40; ++i) { + output->set(i, std::polar(1.f, randomPhase())); + } + + // now go to the end and at slope + 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 float gain = float(AudioMath::gainFromDb(gainDb)); + output->set(i, std::polar(gain, randomPhase())); + } else { + output->set(i, cpx(0, 0)); + } + } + output->set(0, 0); // make sure dc bin zero +} + +static void makePosSlope(FFTDataCpx* output, const ColoredNoiseSpec& spec) +{ + const int numBins = int(output->size()); + + // find bin for high corner + const int binHigh = FFT::freqToBin(spec.highFreqCorner, spec.sampleRate, numBins); + + // now go to the end and at slope + float gainMax = 1; // even if nothing in the bins (ut) needs something in there. + 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 float gain = float(AudioMath::gainFromDb(gainDb)); + gainMax = std::max(gain, gainMax); + output->set(i, std::polar(gain, randomPhase())); + } else { + output->set(i, cpx(0, 0)); + } + } + + // fill top bins with mag mag + for (int i = numBins - 1; i >= binHigh; --i) { + if (i < numBins / 2) { + output->set(i, std::polar(gainMax, randomPhase())); + } else { + output->set(i, cpx(0.0)); + } + } + + output->set(0, 0); // make sure dc bin zero +} + +void FFT::makeNoiseSpectrum(FFTDataCpx* output, const ColoredNoiseSpec& spec) +{ + // for now, zero all first. + const int frameSize = (int) output->size(); + for (int i = 0; i < frameSize; ++i) { + cpx x(0,0); + output->set(i, x); + } + if (spec.slope < 0) { + makeNegSlope(output, spec); + } else { + makePosSlope(output, spec); + } +} + +static float getPeak(const FFTDataReal& data) +{ + float peak = 0; + for (int i = 0; i < data.size(); ++i) { + peak = std::max(peak, std::abs(data.get(i))); + } + return peak; +} + +void FFT::normalize(FFTDataReal* data) +{ + const float peak = getPeak(*data); + const float correction = 1.0f / peak; + for (int i = 0; i < data->size(); ++i) { + float x = data->get(i); + x *= correction; + data->set(i, x); + } +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFT.h b/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFT.h new file mode 100644 index 00000000..01b717bb --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFT.h @@ -0,0 +1,39 @@ +#pragma once + +class FFTDataCpx; +class FFTDataReal; + + +class ColoredNoiseSpec +{ +public: + float slope = 0; + float highFreqCorner = 4000; + float sampleRate = 44100; + bool operator != (const ColoredNoiseSpec& other) const + { + return (slope != other.slope) || + (highFreqCorner != other.highFreqCorner) || + (sampleRate != other.sampleRate); + } +}; + +class FFT +{ +public: + /** Forward FFT will do the 1/N scaling + */ + static bool forward(FFTDataCpx* out, const FFTDataReal& in); + static bool inverse(FFTDataReal* out, const FFTDataCpx& in); + + // static FFTDataCpx* makeNoiseFormula(float slope, float highFreqCorner, int frameSize); + + /** + * Fills a complex FFT frame with frequency domain data describing noise + */ + 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); +}; \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFTCrossFader.cpp b/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFTCrossFader.cpp new file mode 100644 index 00000000..ecda08d5 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFTCrossFader.cpp @@ -0,0 +1,82 @@ + +#include "ColoredNoise.h" +#include "FFTCrossFader.h" +#include + +NoiseMessage* FFTCrossFader::step(float* out) +{ + NoiseMessage* usedMessage = nullptr; + if (dataFrames[0] && !dataFrames[1]) { + // just one frame - play it; + *out = dataFrames[0]->dataBuffer->get(curPlayOffset[0]); + advance(0); + + } else if (dataFrames[0] && dataFrames[1]) { + // 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 buffer1Value = dataFrames[1]->dataBuffer->get(curPlayOffset[1]) * curPlayOffset[1]; + + // TODO: do we need to pre-divide + *out = (buffer1Value + buffer0Value) / (crossfadeSamples-1); + if (makeupGain) { + float gain = std::sqrt(2.0f) - 1; + float offset = float(curPlayOffset[1]); + float crossM1 = float(crossfadeSamples - 1); + const float halfFade = crossM1 / 2.f; + // printf(" halfFade = %f offset=%f, crossm1=%f initgain=%f\n", halfFade, offset, crossM1, gain); + if (offset < halfFade) { + gain *= offset / crossM1; + gain *= 2.f; + // printf(" low case, off/cross=%f\n", offset / crossM1); + } else { + gain *= (crossM1 - offset) / crossM1; + gain *= 2; + // printf(" high case, c-off/cross=%f\n", (crossM1 - offset) / crossM1); + } + gain += 1; + *out *= gain; + } + advance(0); + advance(1); + if (curPlayOffset[1] == crossfadeSamples) { + // finished fade, can get rid of 0 + usedMessage = dataFrames[0]; + dataFrames[0] = dataFrames[1]; + curPlayOffset[0] = curPlayOffset[1]; + dataFrames[1] = nullptr; + curPlayOffset[1] = 0; + } + + } else { + *out = 0; + } + return usedMessage;; +} + +void FFTCrossFader::advance(int index) +{ + ++curPlayOffset[index]; + if (curPlayOffset[index] >= dataFrames[index]->dataBuffer->size()) { + curPlayOffset[index] = 0; + } +} + +NoiseMessage * FFTCrossFader::acceptData(NoiseMessage* msg) +{ + NoiseMessage* returnedBuffer = nullptr; + if (dataFrames[0] == nullptr) { + dataFrames[0] = msg; + curPlayOffset[0] = 0; + } + else if (dataFrames[1] == nullptr) { + dataFrames[1] = msg; + curPlayOffset[1] = 0; + } else { + // we are full, just ignore this one. + returnedBuffer = msg; + } + return returnedBuffer; +} diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFTCrossFader.h b/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFTCrossFader.h new file mode 100644 index 00000000..fa991b0e --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFTCrossFader.h @@ -0,0 +1,50 @@ +#pragma once + +class NoiseMessage; + +/** + * This is a specialized gizmo just for fading between two + * FFT frames + */ +class FFTCrossFader +{ +public: + FFTCrossFader(int crossfadeSamples) : crossfadeSamples(crossfadeSamples) + { + } + NoiseMessage * step(float* out); + NoiseMessage * acceptData(NoiseMessage*); + bool empty() const + { + return !dataFrames[0]; + } + const NoiseMessage* playingMessage() const + { + // TODO: should return second, it exists? + return dataFrames[0]; + } + void enableMakeupGain(bool enable) + { + makeupGain = enable; + } +private: + /** + * The size of the crossfade, in samples + */ + const int crossfadeSamples; + + bool makeupGain = false; + + /** + * current playhead, relative to start of each buffer + */ + int curPlayOffset[2] = {0, 0}; + + + NoiseMessage* dataFrames[2] = {nullptr, nullptr}; + + /** Advance the play offset, + * wrap on overflow. + */ + void advance(int index); +}; \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFTData.cpp b/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFTData.cpp new file mode 100644 index 00000000..7324028c --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFTData.cpp @@ -0,0 +1,67 @@ + +#include "FFTData.h" +#include "kiss_fft.h" +#include "kiss_fftr.h" + +#include + +int FFTDataCpx::_count = 0; +FFTDataCpx::FFTDataCpx(int numBins) : + buffer(numBins) +{ + ++_count; +} + +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; +} diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFTData.h b/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFTData.h new file mode 100644 index 00000000..6db2c050 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/fft/FFTData.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include + + +class FFT; + +/** + * Our wrapper api uses std::complex, so we don't need to expose kiss_fft_cpx + * outside. Our implementation assumes the two are equivalent, and that a + * reinterpret_cast can bridge them. + */ +using cpx = std::complex; + +class FFTDataCpx +{ +public: + friend FFT; + FFTDataCpx(int numBins); + ~FFTDataCpx(); + cpx get(int bin) const; + void set(int bin, cpx value); + + int size() const + { + return (int) buffer.size(); + } + static int _count; +private: + std::vector buffer; + + /** + * 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 complex input will be inverse FFTs. + */ + mutable void * kiss_cfg = 0; +}; + +/** + * Holds an fft frame of real data. + */ +class FFTDataReal +{ +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(); + } + static int _count; +private: + std::vector buffer; + + /** + * 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; +}; \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/filters/BiquadFilter.h b/plugins/community/repos/squinkylabs-plug1/dsp/filters/BiquadFilter.h new file mode 100644 index 00000000..e313646c --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/filters/BiquadFilter.h @@ -0,0 +1,75 @@ +#pragma once + +template class BiquadState; +template class BiquadParams; + +#include "BiquadParams.h"// what is our forward declaration strategy here? put implementation in c++? +#include "DspFilter.h" // TODO: get rid of this. we need it now because you can't forward declare Cascade::Stage + +/* + * + * (I call this node x in the implementation) + * | + * + * >----- + ----------|--> b0 >-- + --------> + * | | | + * | Z-1 | + * | | | + * |--< -a1 <--|--> b1 >---- + * | | | + * | Z-1 | + * | | | + * | | | + * |--< -a2 <--|--> b2 >---- + * + * + * + */ +template +class BiquadFilter +{ +public: + BiquadFilter() = delete; // we are only static + template + static T run(T input, BiquadState& state, const BiquadParams& params); + + /** + * Translate filter coefficients from Dsp:: conventions to DspParam structure + */ + template + static void fillFromStages(BiquadParams& params, Dsp::Cascade::Stage * stages, int numStages); +}; + +template +template +inline void BiquadFilter::fillFromStages(BiquadParams& params, Dsp::Cascade::Stage * stages, int numStages) +{ + assert(numStages == N); + + for (int i = 0; i < N; ++i) { + const Dsp::Cascade::Stage& stage = stages[i]; + + params.B0(i) = (T) stage.b[0]; + params.B1(i) = (T) stage.b[1]; + params.B2(i) = (T) stage.b[2]; + params.A1(i) = (T) stage.a[1]; + params.A2(i) = (T) stage.a[2]; + } +} + +template +template +inline T BiquadFilter::run(T input, BiquadState& state, const BiquadParams& params) +{ + + for (int stage = 0; stage < N; ++stage) { + T x = input + (params.A1(stage) * state.z0(stage) + params.A2(stage) * state.z1(stage)); + + input = params.B0(stage) * x + + params.B1(stage) * state.z0(stage) + + params.B2(stage) * state.z1(stage); + state.z1(stage) = state.z0(stage); + state.z0(stage) = x; + } + return input; +} diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/filters/BiquadParams.h b/plugins/community/repos/squinkylabs-plug1/dsp/filters/BiquadParams.h new file mode 100644 index 00000000..3fa84dab --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/filters/BiquadParams.h @@ -0,0 +1,137 @@ +#pragma once +#include + +extern int _numBiquads; +/** + * Structure to hold the input parameter of a biquad filter: in this case the tap weights. + * + * T is the numeric type (float or double) + * N is the number of stages. So N=2 could be for a three pole or four pole biquad) + */ +template +class BiquadParams +{ +public: + BiquadParams(); + ~BiquadParams(); + + // TODO2: make N be unsigned + + // Let's block copies. They work fine, but we don't need + // to copy. + BiquadParams(const BiquadParams&) = delete; + BiquadParams& operator=(const BiquadParams&) = delete; + + T& B0(int stage); + T& B1(int stage); + T& B2(int stage); + T& A1(int stage); + T& A2(int stage); + T B0(int stage) const; + T B1(int stage) const; + T B2(int stage) const; + T A1(int stage) const; + T A2(int stage) const; + + void dump() const; +private: + T _taps[5 * N]; +}; + +template +inline void BiquadParams::dump() const +{ + for (int stage = 0; stage < N; ++stage) { + printf("%d B0=%f\n", stage, B0(stage)); + printf("%d B1=%f\n", stage, B1(stage)); + printf("%d B2=%f\n", stage, B2(stage)); + printf("%d A1=%f\n", stage, A1(stage)); + printf("%d A2=%f\n\n", stage, A2(stage)); + } +} + +template +inline BiquadParams::BiquadParams() +{ + assert(N > 0); + for (int i = 0; i < N * 5; ++i) { + _taps[i] = 0; + } + ++_numBiquads; +} + +template +inline BiquadParams::~BiquadParams() +{ + --_numBiquads; +} + +template +inline T& BiquadParams::B0(int stage) +{ + assert(stage >= 0 && stage < N); + return _taps[stage * 5]; +} + +template +inline T& BiquadParams::B1(int stage) +{ + assert(stage >= 0 && stage < N); + return _taps[stage * 5 + 1]; +} + +template +inline T& BiquadParams::B2(int stage) +{ + assert(stage >= 0 && stage < N); + return _taps[stage * 5 + 2]; +} + +template +T BiquadParams::A1(int stage) const +{ + assert(stage >= 0 && stage < N); + return _taps[stage * 5 + 3]; +} + +template +T BiquadParams::A2(int stage) const +{ + assert(stage >= 0 && stage < N); + return _taps[stage * 5 + 4]; +} + +template +T BiquadParams::B0(int stage) const +{ + assert(stage >= 0 && stage < N); + return _taps[stage * 5]; +} + +template +T BiquadParams::B1(int stage) const +{ + assert(stage >= 0 && stage < N); + return _taps[stage * 5 + 1]; +} + +template +T BiquadParams::B2(int stage) const +{ + assert(stage >= 0 && stage < N); + return _taps[stage * 5 + 2]; +} + +template +T& BiquadParams::A1(int stage) +{ + assert(stage >= 0 && stage < N); + return _taps[stage * 5 + 3]; +} + +template +T& BiquadParams::A2(int stage) +{ + assert(stage >= 0 && stage < N); + return _taps[stage * 5 + 4]; +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/filters/BiquadState.h b/plugins/community/repos/squinkylabs-plug1/dsp/filters/BiquadState.h new file mode 100644 index 00000000..9ab8e217 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/filters/BiquadState.h @@ -0,0 +1,54 @@ + +#pragma once + +/** + * Structure to hold the mutable state of a biquad filter: in this case the delay memory. + * + * T is the numeric type (float or double) + * N is the number of stages. So N=2 could be for a three pole or four pole biquad) + */ +template +class BiquadState +{ +public: + BiquadState(); + ~BiquadState(); + /** + * accessor for delay memory 0 + * @param stage is the biquad stage index + */ + T& z0(int stage); + T& z1(int stage); +private: + T _state[N * 2]; +}; + +template +inline BiquadState::BiquadState() +{ + assert(N > 0); + for (int i = 0; i < N * 2; ++i) { + _state[i] = 0; + } + _numBiquads++; +} + +template +inline BiquadState::~BiquadState() +{ + _numBiquads--; +} + +template +inline T& BiquadState::z0(int stage) +{ + assert(stage >= 0 && stage < N); + return _state[stage * 2]; +} + +template +inline T& BiquadState::z1(int stage) +{ + assert(stage >= 0 && stage < N); + return _state[stage * 2 + 1]; +} diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/filters/ButterworthFilterDesigner.cpp b/plugins/community/repos/squinkylabs-plug1/dsp/filters/ButterworthFilterDesigner.cpp new file mode 100644 index 00000000..2064a8b0 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/filters/ButterworthFilterDesigner.cpp @@ -0,0 +1,35 @@ +/** + * ButterworthFilterDesigner + * a bunch of functions for generating the parameters of butterworth filters + */ + +#include "ButterworthFilterDesigner.h" +#include "DspFilter.h" +#include "BiquadFilter.h" +#include + +template +void ButterworthFilterDesigner::designThreePoleLowpass(BiquadParams& outParams, T frequency) +{ + using Filter = Dsp::ButterLowPass<3, 1>; + std::unique_ptr lp3(new Filter()); // std::make_unique is not until C++14 + lp3->SetupAs(frequency); + assert(lp3->GetStageCount() == 2); + BiquadFilter::fillFromStages(outParams, lp3->Stages(), lp3->GetStageCount()); +} + +template +void ButterworthFilterDesigner::designTwoPoleLowpass(BiquadParams& outParams, T frequency) +{ + using Filter = Dsp::ButterLowPass<2, 1>; + std::unique_ptr lp2(new Filter()); + lp2->SetupAs(frequency); + assert(lp2->GetStageCount() == 1); + BiquadFilter::fillFromStages(outParams, lp2->Stages(), lp2->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 +template class ButterworthFilterDesigner; +template class ButterworthFilterDesigner; diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/filters/ButterworthFilterDesigner.h b/plugins/community/repos/squinkylabs-plug1/dsp/filters/ButterworthFilterDesigner.h new file mode 100644 index 00000000..8f0ef436 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/filters/ButterworthFilterDesigner.h @@ -0,0 +1,14 @@ + +#pragma once + +template +class BiquadParams; + +template +class ButterworthFilterDesigner +{ +public: + ButterworthFilterDesigner() = delete; // we are only static + static void designThreePoleLowpass(BiquadParams& pOut, T frequency); + static void designTwoPoleLowpass(BiquadParams& pOut, T frequency); +}; \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/filters/FormantTables2.cpp b/plugins/community/repos/squinkylabs-plug1/dsp/filters/FormantTables2.cpp new file mode 100644 index 00000000..07189deb --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/filters/FormantTables2.cpp @@ -0,0 +1,283 @@ + +#include +#include + +#include "AudioMath.h" +#include "FormantTables2.h" + + +static const float freqLookup[FormantTables2::numModels][FormantTables2::numFormantBands][FormantTables2::numVowels] = { + // model = 0(bass) + { + // F1 a + //0=a,1=e,2=i, 3=o 4=u + {600, 400, 250, 400, 350}, + // F2 + {1040, 1620, 1750, 750, 600}, + // F3 + {2250, 2400, 2600, 2400, 2400}, + // F4 + {2450, 2800, 3050, 2600, 2675}, + //F5 + {2750, 3100, 3340, 2900, 2950} + }, + //1(tenor) + { + // F1 + //0=a,1=e,2=i, 3=o 4=u + {650, 400, 290, 400, 350 }, + // F2 + {1080, 1700, 1870, 800, 600 }, + // F3 + {2650, 2600, 2800, 2600, 2700}, + // F4 + {2900, 3200, 3250, 2800, 2900}, + //F5 + {3250, 3850, 3540, 3000, 3300} + }, + //2(countertenor) + { + // F1 + //0=a,1=e,2=i, 3=o 4=u + {660, 440, 270, 430, 370}, + // F2 + {1120, 1800, 1850, 820, 630}, + // F3 + {2750, 2700, 2900, 2700, 2750}, + // F4 + {3000, 3000, 3350, 3000, 3000}, + //F5 + {3350, 3300, 3590, 3300, 3400} + }, + //3(alto) + { + // F1 + //0=a,1=e,2=i, 3=o 4=u + {800, 400, 350, 450, 325}, + // F2 + {1150, 1600, 1700, 800, 700}, + // F3 + {2800, 2700, 2700, 2830, 2530}, + // F4 + {3500, 3300, 3700, 3500, 3500}, + //F5 + {4950, 4950, 4950, 4950, 4950} + }, + // 4(soprano) + { + // F1 + //0=a,1=e,2=i, 3=o 4=u + {800, 350, 270, 450, 325}, + // F2 + {1150, 2000, 2140, 800, 700}, + // F3 + {2900, 2800, 2950, 2830, 2700}, + // F4 + {3900, 3600, 3900, 3800, 3800}, + //F5 + {4950, 4950, 4950, 4950, 4950} + } +}; + +static const float bwLookup[FormantTables2::numModels][FormantTables2::numFormantBands][FormantTables2::numVowels] = { + // model = 0(bass) + { + // F1 0 = a, 1 = e, 2 = i, 3 = o 4 = u + {60, 40, 60, 40, 40}, + // F2 + {70, 80, 90, 80, 80}, + // F3 + {110, 100, 100, 100, 100}, + // F4 + {120, 120, 120, 120, 120}, + //F5 + {130, 120, 120, 120, 120} + }, + //1(tenor) + { + // F1 0 = a, 1 = e, 2 = i, 3 = o 4 = u + {80, 70, 40, 70, 40}, + // F2 + {90, 80, 90, 80, 60}, + // F3 + {120, 100, 100, 100, 100}, + // F4 + {130, 120, 120, 130, 120}, + // F5 + {140, 120, 120, 135, 120} + }, + //2(countertenor) + { + // F1 0 = a, 1 = e, 2 = i, 3 = o 4 = u + {80, 70, 40, 40, 40}, + // F2 + {90, 80, 90, 80, 60}, + // F3 + {120, 100, 100, 100, 100}, + // F4 + {130, 120, 120, 120, 120}, + //F5 + {140, 120, 120, 120, 120} + }, + //3(alto) + { + // F1 0 = a, 1 = e, 2 = i, 3 = o 4 = u + {80, 60, 50, 70, 50 }, + // F2 + {90, 80, 100, 80, 60}, + // F3 + {120, 120, 120, 100, 170}, + // F4 + {130, 150, 150, 130, 180}, + //F5 + {140, 200, 200, 135, 200} + }, +// 4(soprano) + { + // F1 0 = a, 1 = e, 2 = i, 3 = o 4 = u + {80, 60, 60, 40, 50}, + // F2 + {90, 100, 90, 80, 60}, + // F3 + {120, 120, 100, 100, 170}, + // F4 + {130, 150, 120, 120, 180}, + //F5 + {140, 200, 120, 120, 200} + } +}; + +static const float gainLookup[FormantTables2::numModels][FormantTables2::numFormantBands][FormantTables2::numVowels] = { + // model = 0(bass) + { + // F1 0 = a, 1 = e, 2 = i, 3 = o 4 = u + {0, 0, 0, 0, 0, }, + // F2 + {-7, -12, -30, -11, -20}, + // F3 + {-9, -9, -16, -21, -32}, + // F4 + {-12, -12, -22, -20, -28}, + //F5 + {-18, -18, -28, -40, -36} + }, + //1(tenor) + { + // F1 0 = a, 1 = e, 2 = i, 3 = o 4 = u + {0, 0, 0, 0, 0}, + // F2 + {-6, -14, -15, -10, -20}, + // F3 + {-7, -12, -18, -12, -17}, + // F4 + {-8, -14, -20, -12, -14}, + //F5 + {-22, -20, -30, -26, -26} + }, + //2(countertenor) + { + // F1 0 = a, 1 = e, 2 = i, 3 = o 4 = u + {0, 0, 0, 0, 0}, + // F2 + {-6, -14, -24, -10, -20}, + // F3 + {-23, -18, -24, -26, -23}, + // F4 + {-24, -20, -36, -22, -30}, + //F5 + {-38, -20, -36, -34, -34} + }, + //3(alto) + { + // F1 0 = a, 1 = e, 2 = i, 3 = o 4 = u + {0, 0, 0, 0, 0}, + // F2 + {-4, -24, -30, -9, -12}, + // F3 + {-20, -30, -30, -16, -30}, + // F4 + {-36, -35, -36, -28, -40}, + //F5 + {-60, -60, -60, -55, -64} + }, + // 4(soprano) + { + // F1 0 = a, 1 = e, 2 = i, 3 = o 4 = u + {0, 0, 0, 0, 0}, + // F2 + {-6, -20, -12, -11, -16}, + // F3 + {-32, -15, -26, -22, -35}, + // F4 + {-20, -40, -26, -22, -40}, + //F5 + {-50, -56, -44, -50, -60} + } +}; + +FormantTables2::FormantTables2() +{ + + for (int model = 0; model < numModels; ++model) { + for (int formantBand = 0; formantBand < numFormantBands; ++formantBand) { + + LookupTableParams& fparams = freqInterpolators[model][formantBand]; + const float *freqValues = freqLookup[model][formantBand]; + + // It would be more efficient to init the tables with logs, but this + // on the fly conversion is fine. + float temp[numVowels]; + for (int vowel = 0; vowel < numVowels; ++vowel) { + temp[vowel] = std::log2(freqValues[vowel]); + } + LookupTable::initDiscrete(fparams, numVowels, temp); + + // Init Wb lookups with normalized bw ( f2-f1 / fc) + LookupTableParams& bwparams = bwInterpolators[model][formantBand]; + const float *bwValues = bwLookup[model][formantBand]; + for (int vowel = 0; vowel < numVowels; ++vowel) { + temp[vowel] = bwValues[vowel] / freqValues[vowel]; + } + + LookupTable::initDiscrete(bwparams, numVowels, temp); + + LookupTableParams& gparams = gainInterpolators[model][formantBand]; + const float* gainValues = gainLookup[model][formantBand]; + for (int vowel = 0; vowel < numVowels; ++vowel) { + // temp[vowel] = (float) AudioMath::gainFromDb(gainValues[vowel]); + // let's leave these as db + temp[vowel] = gainValues[vowel]; + } + LookupTable::initDiscrete(gparams, numVowels, temp); + } + } +} + +float FormantTables2::getLogFrequency(int model, int formantBand, float vowel) +{ + assert(model >= 0 && model <= numModels); + assert(formantBand >= 0 && formantBand <= numFormantBands); + assert(vowel >= 0 && vowel < numVowels); + + LookupTableParams& params = freqInterpolators[model][formantBand]; + return LookupTable::lookup(params, vowel); +} + +float FormantTables2::getNormalizedBandwidth(int model, int formantBand, float vowel) +{ + assert(model >= 0 && model <= numModels); + assert(formantBand >= 0 && formantBand <= numFormantBands); + assert(vowel >= 0 && vowel < numVowels); + + LookupTableParams& params = bwInterpolators[model][formantBand]; + return LookupTable::lookup(params, vowel); +} +float FormantTables2::getGain(int model, int formantBand, float vowel) +{ + assert(model >= 0 && model <= numModels); + assert(formantBand >= 0 && formantBand <= numFormantBands); + assert(vowel >= 0 && vowel < numVowels); + + LookupTableParams& params = gainInterpolators[model][formantBand]; + return LookupTable::lookup(params, vowel); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/filters/FormantTables2.h b/plugins/community/repos/squinkylabs-plug1/dsp/filters/FormantTables2.h new file mode 100644 index 00000000..bae1b7e3 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/filters/FormantTables2.h @@ -0,0 +1,29 @@ +#pragma once +#include "LookupTable.h" + +class FormantTables2 +{ +public: + static const int numVowels = 5; + static const int numModels = 5; + static const int numFormantBands = 5; + /** + * Interpolates the frequency using lookups + * @param model = 0(bass) 1(tenor) 2(countertenor) 3(alto) 4(soprano) + * @param index = 0..4 (formant F1..F5) + * @param vowel is the continuous index into the per/vowel lookup tables (0..4) + * 0 = a, 1 = e, 2=i, 3 = o 4 = u + */ + float getLogFrequency(int model, int index, float vowel); + float getNormalizedBandwidth(int model, int index, float vowel); + float getGain(int model, int index, float vowel); + + FormantTables2(); + FormantTables2(const FormantTables2&) = delete; + const FormantTables2& operator==(const FormantTables2&) = delete; +private: + + LookupTableParams freqInterpolators[numModels][numFormantBands]; + LookupTableParams bwInterpolators[numModels][numFormantBands]; + LookupTableParams gainInterpolators[numModels][numFormantBands]; +}; diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/filters/HilbertFilterDesigner.cpp b/plugins/community/repos/squinkylabs-plug1/dsp/filters/HilbertFilterDesigner.cpp new file mode 100644 index 00000000..4830e45d --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/filters/HilbertFilterDesigner.cpp @@ -0,0 +1,220 @@ +/** + * HilbertFilterDesigner + * generate a pair of Hilbert filters + */ + +#include "HilbertFilterDesigner.h" +#include "DspFilter.h" +#include "BiquadFilter.h" +#include + + +const int hilbertOrder = 6; +const bool dumpHilbert = false; + +namespace Dsp { + + /* with these looked up poles, and a "cutoff freq" of 4. + * the filter is good from 4hz to 4k, + * with a phase error ripple about +- .15 degrees + */ + + const double leftPoles[] = { + .3609, + 2.7412, + 11.1573, + 44.7581, + 179.6242, + 798.4578 + }; + + const double rightPoles[] = { + 1.2524, + 5.5671, + 22.3423, + 89.6271, + 364.7914, + 2770.1114 + }; + + const double shift = 4; + + struct Hilb : Prototype + { + + double log2(double x) + { + return log(x) / log(2.0); + } + void dump() + { + printf("log2(2) = %f 8=%f\n", log2(2), log2(8)); + double lastLog = 0; + for (int i = 0; i < 6; ++i) { + double l = shift * leftPoles[i]; + double r = shift * rightPoles[i]; + + printf("i=%d left pole = %f right = %f ratio = %f\n", i, l, r, r / l); + + printf(" log(left)=%f, log(r)=%f\n", log2(l), log2(r)); + + if (i == 0) { + printf(" delta log(r)=%f\n", log2(r) - log2(l)); + + } else { + printf(" delta log(l0=%f (r)=%f\n", log2(l) - lastLog, log2(r) - log2(l)); + } + lastLog = log2(r); + + + if (i > 0) { + double l0 = leftPoles[i - 1]; + double r0 = rightPoles[i - 1]; + printf("L/Lprev = %f, R/Rprev=%f\n", l / l0, r / r0); + } + } + } + + void Design(const Spec &spec) + { + + int n = spec.order; + assert(n == hilbertOrder); + + if (dumpHilbert) + dump(); + + const bool side = spec.rollOff > .5 ? true : false; + + SetPoles(n); + SetZeros(n); + + for (int i = 0; i < n; ++i) { + double f = side ? leftPoles[i] : rightPoles[i]; + Pole(i) = Complex(-f, 0); // TODO: why do we need this negative sign? + Zero(i) = Complex(f, 0); + } + + m_normal.w = 0; + m_normal.gain = 1; + +#ifdef _LOG + Roots & zeros = Zeros(); + Roots & poles = Poles(); + + for (int i = 0; i < n; ++i) { + char buf[512]; + sprintf(buf, "in hibdes i=%d pole = %f,%f i zero = %f,%f i\n", i, + poles.GetNth(i).real(), + poles.GetNth(i).imag(), + zeros.GetNth(i).real(), + zeros.GetNth(i).imag()); + DebugUtil::trace(buf); + + } +#endif + } + }; + + + struct Hilbert : PoleFilterSpace + { + void SetupAs(bool leftSide, double sampleRate) + { + Spec spec; + spec.order = hilbertOrder; + + spec.cutoffFreq = shift; + spec.sampleRate = sampleRate; + + spec.rollOff = leftSide ? 1 : 0; // jam side parameter into this obscure member + PoleFilterSpace::Setup(spec); + } + }; + + + /** just a simple allpass, for debugging + */ +#if 0 + struct HilbTest : Prototype + { + public: + void Design(const Spec &spec) + { + int n = spec.order; + assert(n == hilbertOrderTest); + + const bool side = spec.rollOff > .5 ? true : false; + + + SetPoles(n); + SetZeros(n); + + assert(n == 1); + + double f = .25; + Pole(0) = Complex(-f, 0); + Zero(0) = Complex(f, 0); + + + m_normal.w = 0; + m_normal.gain = 1; + + + +#ifdef _LOG + Roots & zeros = Zeros(); + Roots & poles = Poles(); + + for (int i = 0; i < n; ++i) { + char buf[512]; + sprintf(buf, "i=%d pole = %f,%f i zero = %f,%f i\n", i, + poles.GetNth(i).real(), + poles.GetNth(i).imag(), + zeros.GetNth(i).real(), + zeros.GetNth(i).imag()); + DebugUtil::trace(buf); + + } +#endif + } + }; + + struct HilbertTest : PoleFilterSpace + { + void SetupAs(CalcT cutoffFreq, bool side) + { + Spec spec; + spec.order = hilbertOrderTest; + + spec.cutoffFreq = cutoffFreq * 1; + spec.sampleRate = 44100; + + spec.rollOff = side ? 1 : 0; // jam side parameter into this obscure member + PoleFilterSpace::Setup(spec); + } + }; +#endif +} + + + + +template +void HilbertFilterDesigner::design(double sampleRate, BiquadParams& outSin, BiquadParams& outCos) +{ + Dsp::Hilbert hilbert; + + hilbert.SetupAs(false, sampleRate); + + BiquadFilter::fillFromStages(outSin, hilbert.Stages(), hilbert.GetStageCount()); + hilbert.SetupAs(true, sampleRate); + BiquadFilter::fillFromStages(outCos, hilbert.Stages(), hilbert.GetStageCount()); +} + +// Explicit instantiation, so we can put implementation into .cpp file +// TODO: option to take out double version (if we don't need it) +// Or put all in header +template class HilbertFilterDesigner; +template class HilbertFilterDesigner; + diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/filters/HilbertFilterDesigner.h b/plugins/community/repos/squinkylabs-plug1/dsp/filters/HilbertFilterDesigner.h new file mode 100644 index 00000000..8ed1f1b9 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/filters/HilbertFilterDesigner.h @@ -0,0 +1,15 @@ +#pragma once + +template +class BiquadParams; + +template +class HilbertFilterDesigner +{ +public: + HilbertFilterDesigner() = delete; // we are only static + /** + * generates a pair of biquads, on will be 90 degrees shifter from the other + */ + static void design(double sampleRate, BiquadParams& pOutSin, BiquadParams& pOutCos); +}; diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/filters/StateVariableFilter.h b/plugins/community/repos/squinkylabs-plug1/dsp/filters/StateVariableFilter.h new file mode 100644 index 00000000..a9a61109 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/filters/StateVariableFilter.h @@ -0,0 +1,156 @@ +#pragma once + +#include "AudioMath.h" + +template class StateVariableFilterState; +template class StateVariableFilterParams; + +/** + * + * |-----------------------------------------------------> hi pass + * | |->Band (+)----->Notch + * | | | + * input ->( + )------|---> Fc >--(+)-|-> Z**-1 >-|-> Fc >->(+)------>-.--|--> LowPass + * |(-1) | (-1) | | | | | + * | | |-<--------<---| |- +class StateVariableFilter +{ +public: + StateVariableFilter() = delete; // we are only static + static T run(T input, StateVariableFilterState& state, const StateVariableFilterParams& params); + +}; + +template +inline T StateVariableFilter::run(T input, StateVariableFilterState& state, const StateVariableFilterParams& params) +{ + const T dLow = state.z2 + params.fcGain * state.z1; + const T dHi = input - (state.z1 * params.qGain + dLow); + T dBand = dHi * params.fcGain + state.z1; + + // TODO: figure out why we get these crazy values +#if 1 + if (dBand >= 1000) { + dBand = 999; // clip it + } + if (dBand < -1000) { + dBand = -999; // clip it + } + +#endif + + T d; + switch (params.mode) { + case StateVariableFilterParams::Mode::LowPass: + d = dLow; + break; + case StateVariableFilterParams::Mode::HiPass: + d = dHi; + break; + case StateVariableFilterParams::Mode::BandPass: + d = dBand; + break; + case StateVariableFilterParams::Mode::Notch: + d = dLow + dHi; + break; + default: + assert(false); + d = 0.0; + } + + state.z1 = dBand; + state.z2 = dLow; + + return d; +} + +/****************************************************************/ + +template +class StateVariableFilterParams +{ +public: + friend StateVariableFilter; + enum class Mode + { + BandPass, LowPass, HiPass, Notch + }; + + /** + * Set the filter Q. + * Values must be > .5 + */ + void setQ(T q); + + /** + * Normalized bandwidth is bw / fc + * Also is 1 / Q + */ + void setNormalizedBandwidth(T bw); + T getNormalizedBandwidth() const + { + return qGain; + } + + /** + * Set the center frequency. + * units are 1 == sample rate + */ + void setFreq(T f); + void setMode(Mode m) + { + mode = m; + } +private: + Mode mode = Mode::BandPass; + T qGain = 1.; // internal amp gains + T fcGain = T(.001); +}; + +template +inline void StateVariableFilterParams::setQ(T q) +{ + if (q < .49) { + assert(false); + q = T(.6); + } + qGain = 1 / q; +} + +template +inline void StateVariableFilterParams::setNormalizedBandwidth(T bw) +{ + qGain = bw; +} + +template +inline void StateVariableFilterParams::setFreq(T fc) +{ + // Note that we are skipping the high freq warping. + // Going for speed over accuracy + fcGain = T(AudioMath::Pi) * T(2) * fc; +} + +/*******************************************************************************************/ + +template +class StateVariableFilterState +{ +public: + T z1 = 0; // the delay line buffer + T z2 = 0; // the delay line buffer +}; diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/generators/MultiModOsc.h b/plugins/community/repos/squinkylabs-plug1/dsp/generators/MultiModOsc.h new file mode 100644 index 00000000..abbf9d5c --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/generators/MultiModOsc.h @@ -0,0 +1,128 @@ +#pragma once + +#include + +#include "SawOscillator.h" + +/** + * A bunch of LFOs at slightly different frequencies added together in different ways. + * Taken from Bernie Hutchins' ElectroNotes. + */ +template +class MultiModOsc +{ +public: + MultiModOsc() = delete; + /** + * Make state and params be nested classes so we don't have + * to type as many template arguments. + */ + class State + { + public: + friend MultiModOsc; + private: + SawOscillatorState states[NOsc]; + }; + class Params + { + public: + friend MultiModOsc; + Params(); + /** + * @param rate is -1..1 arbitrary "low frequency" units + */ + void setRateAndSpread(T rate, T spread, int matrixMode, T inverseSampleRate); + private: + SawOscillatorParams params[NOsc]; + int matrixMode = 0; + }; + + static void run(T * output, State&, const Params&); +}; + +template +inline MultiModOsc::Params::Params() +{ + setRateAndSpread(.5, .5, 0, T(1.0 / 44100)); +} + +template +inline void MultiModOsc::Params::setRateAndSpread(T rate, T spread, int inMatrixMode, T inverseSampleRate) +{ + assert(rate >= -10 && rate <= 10); // just a sanity check + assert(inverseSampleRate > (1.0 / 200000)); + assert(inverseSampleRate < (1.0 / 2000)); // same + + T BaseRate = 1.0; + BaseRate *= std::pow(T(3), rate); + const T dNormSpread = spread * T((3.0 / 2.0) + .5); + for (int i = 0; i < NOsc; i++) { + T dMult = 1; + switch (i) { + case 1: + dMult = 1 / T(1.11); + break; + case 0: + dMult = 1.0; + break; + case 2: + dMult = T(1.32); + break; + case 3: + dMult = 1.25; + break; + case 4: + dMult = 1.5; + break; + default: + assert(false); + } + dMult -= 1.0; // norm to 0 + dMult *= dNormSpread; + dMult += 1.0; + + const T x = BaseRate * dMult; + const T actual = x * inverseSampleRate; + SawOscillator::setFrequency(params[i], actual); + this->matrixMode = inMatrixMode; + + } +} + +template +inline void MultiModOsc::run(T* output, State& state, const Params& params) +{ + T modulators[NOsc]; + for (int i = 0; i < NOsc; ++i) { + modulators[i] = SawOscillator::runTri(state.states[i], params.params[i]); + } + // The old implementation had a smarter algorithm, but + // for now hard-wiring it for 4/3 is OK + if ((NOsc == 4) && (NOut == 3)) { + switch (params.matrixMode) { + case 0: // classic mix + output[0] = modulators[0] + modulators[1] + modulators[2]; // not 3 + output[1] = modulators[0] + modulators[1] + modulators[3]; // not 2 + output[2] = modulators[0] + modulators[2] + modulators[3]; // not 1 + break; + case 1: + // slight variation on classic + output[0] = modulators[0] + modulators[1] + modulators[2]; // not 3 + output[1] = modulators[1] + modulators[2] + modulators[3]; // not 0 + output[2] = modulators[0] + modulators[2] + modulators[3]; // not 1 + break; + case 2: + // invert some + output[0] = modulators[0] + modulators[1] + modulators[2]; // not 3 + output[1] = -modulators[0] + modulators[2] + modulators[3]; // not 0 + output[2] = -modulators[1] - modulators[2] - modulators[3]; // not 1 + break; + default: + assert(false); + + } + } else { + assert(false); // need to return something + } +} diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/generators/SawOscillator.h b/plugins/community/repos/squinkylabs-plug1/dsp/generators/SawOscillator.h new file mode 100644 index 00000000..1290096c --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/generators/SawOscillator.h @@ -0,0 +1,103 @@ +#pragma once + +/** + * SawOscillator + * + * Good features: + * frequency may be adjusted while running without glitches + * can generate a quadrature output + * can be configured to do positive and negative frequency + */ +template class SawOscillatorParams; +template class SawOscillatorState; + +template +class SawOscillator +{ +public: + SawOscillator() = delete; // we are only static + static void setFrequency(SawOscillatorParams& params, T freq); // TODO: so we want setters on params? + + // Generates a saw wave from 0..1 + static T runSaw(SawOscillatorState& state, const SawOscillatorParams& params); + + // Generates a triangle wave from -1..1 + static T runTri(SawOscillatorState& state, const SawOscillatorParams& params); + + /** + * gets the regular output and the quadrature output + */ + static void runQuadrature(T& out, T& outQuad, SawOscillatorState& state, const SawOscillatorParams& params); +}; + +template +inline T SawOscillator::runSaw(SawOscillatorState& state, const SawOscillatorParams& params) +{ + T ret = state.phase; + state.phase += params.phaseIncrement; + if (state.phase >= 1) { + state.phase -= 1; + } + + if (frequencyCanBeNegative && (state.phase < 0)) { + state.phase += 1; + } + + return ret; +} + + +template +inline T SawOscillator::runTri(SawOscillatorState& state, const SawOscillatorParams& params) +{ + T output = 4 * runSaw(state, params); // Saw 0...4 + // printf("in tri, 4x = %f ", output); + if (output > 3) { + output -= 4; + } else if (output > 1) { + output = 2 - output; + } + // printf("final = %f \n", output); + return output; +} + +template +inline void SawOscillator::runQuadrature(T& out, T& outQuad, SawOscillatorState& state, const SawOscillatorParams& params) +{ + out = runSaw(state, params); + T quad = out + T(.25); + if (quad >= 1) { + quad -= 1; + } + outQuad = quad; +} + + + +template +inline void SawOscillator::setFrequency(SawOscillatorParams& params, T freq) +{ + if (frequencyCanBeNegative) { + assert(freq >= -.5 && freq < .5); + } else { + assert(freq >= 0 && freq < .5); + } + params.phaseIncrement = freq; +} + +template +class SawOscillatorParams +{ +public: + T phaseIncrement = 0; +}; + +template +class SawOscillatorState +{ +public: + /** + * phase increments from 0...1 + */ + T phase = 0; +}; diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/generators/SinOscillator.h b/plugins/community/repos/squinkylabs-plug1/dsp/generators/SinOscillator.h new file mode 100644 index 00000000..1b74b3f8 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/generators/SinOscillator.h @@ -0,0 +1,87 @@ +#pragma once + +#include "AudioMath.h" +#include "LookupTable.h" +#include "ObjectCache.h" +#include "SawOscillator.h" + +template class SinOscillatorParams; +template class SinOscillatorState; + +/** + * A simple sin oscillator based on lookup table. + * Advantage: + * fast. + * frequency may be changed without phase discontinuity. + * Optional quadrature output. + * Disadvantage: + * Not the best spectral purity (significant amount of phase jitter) + */ +template +class SinOscillator +{ +public: + SinOscillator() = delete; // we are only static + static void setFrequency(SinOscillatorParams&, T frequency); + static T run(SinOscillatorState&, const SinOscillatorParams&); + static void runQuadrature(T& output, T& outputQuadrature, SinOscillatorState&, const SinOscillatorParams&); +}; + +template +inline void SinOscillator::setFrequency(SinOscillatorParams& params, T frequency) +{ + + std::function 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::init(params.lookupParams, 256, 0, 1, f); + // } + assert(params.lookupParams->isValid()); + + SawOscillator::setFrequency(params.sawParams, frequency); +} + +template +inline T SinOscillator::run( + SinOscillatorState& state, const SinOscillatorParams& params) +{ + + const T temp = SawOscillator::runSaw(state.sawState, params.sawParams); + const T ret = LookupTable::lookup(*params.lookupParams, temp); + return ret; +} + +template +inline void SinOscillator::runQuadrature( + T& output, T& outputQuadrature, SinOscillatorState& state, const SinOscillatorParams& params) +{ + + T saw, quadratureSaw; + SawOscillator::runQuadrature(saw, quadratureSaw, state.sawState, params.sawParams); + output = LookupTable::lookup(*params.lookupParams, saw); + outputQuadrature = LookupTable::lookup(*params.lookupParams, quadratureSaw); +}; + +template +class SinOscillatorParams +{ +public: + SawOscillatorParams sawParams; + // LookupTableParams lookupParams; + std::shared_ptr> lookupParams; + SinOscillatorParams() + { + lookupParams = ObjectCache::getSinLookup(); + } + SinOscillatorParams(const SinOscillatorParams&) = delete; +}; + +template +class SinOscillatorState +{ +public: + SawOscillatorState sawState; +}; diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/falco/DspFilter.cpp b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/falco/DspFilter.cpp new file mode 100644 index 00000000..b45b54fb --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/falco/DspFilter.cpp @@ -0,0 +1,2815 @@ +/******************************************************************************* + +"A Collection of Useful C++ Classes for Digital Signal Processing" +By Vincent Falco + +Official project location: +http://code.google.com/p/dspfilterscpp/ + +See DspFilter.cpp for notes and bibliography. + +-------------------------------------------------------------------------------- + +License: MIT License (http://www.opensource.org/licenses/mit-license.php) +Copyright (c) 2009 by Vincent Falco + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +******************************************************************************** + +Please direct all comments to either the music-dsp mailing list or +the DSP and Plug-in Development forum: + + http://music.columbia.edu/cmc/music-dsp/ + + http://www.kvraudio.com/forum/viewforum.php?f=33 + http://www.kvraudio.com/forum/ + +Support is provided for performing N-order Dsp floating point filter +operations on M-channel data with a caller specified floating point type. +The implementation breaks a high order IIR filter down into a series of +cascaded second order stages. Tests conclude that numerical stability is +maintained even at higher orders. For example the Butterworth low pass +filter is stable at up to 53 poles. + +Processing functions are provided to use either Direct Form I or Direct +Form II of the filter transfer function. Direct Form II is slightly faster +but can cause discontinuities in the output if filter parameters are changed +during processing. Direct Form I is slightly slower, but maintains fidelity +even when parameters are changed during processing. + +To support fast parameter changes, filters provide two functions for +adjusting parameters. A high accuracy Setup() function, and a faster +form called SetupFast() that uses approximations for trigonometric +functions. The approximations work quite well and should be suitable for +most applications. + +Channels are stored in an interleaved format with M samples per frame +arranged contiguously. A single class instance can process all M channels +simultaneously in an efficient manner. A 'skip' parameter causes the +processing function to advance by skip additional samples in the destination +buffer in between every frame. Through manipulation of the skip paramter it +is possible to exclude channels from processing (for example, only processing +the left half of stereo interleaved data). For multichannel data which is +not interleaved, it will be necessary to instantiate multiple instance of +the filter and set skip=0. + +There are a few other utility classes and functions included that may prove useful. + +Classes: + +Order for PoleFilterSpace derived classes is specified in the number of poles, +except for band pass and band stop filters, for which the number of pole pairs +is specified. + +For some filters there are two versions of Setup(), the one called +SetupFast() uses approximations to trigonometric functions for speed. +This is an option if you are doing frequent parameter changes to the filter. + +There is an example function at the bottom that shows how to use the classes. + + +For a tutorial on digital filter design these are useful resources: + +http://crca.ucsd.edu/~msp/techniques/v0.08/book-html/node1.html + + - Need fast version of pow( 10, x ) + + - Able to specify band-width in octaves + + - Specify center frequency by midi note number + F = 440 * 2^((d-69)/12) where D is your midi note. + know that middle C is considered 60. + http://en.wikipedia.org/wiki/MIDI_Tuning_Standard + +-------------------------------------------------------------------------------- + +PoleFilterSpace ideas are based on a java applet (http://www.falstad.com/dfilter/) +developed by Paul Falstad. + +All of this code was written by the author Vincent Falco except where marked. + +*******************************************************************************/ + +#ifdef _UNITTEST + +#include "DebugUtil.h" +#define bassert assert +#endif + +#include "DspFilter.h" +#include +#include + + + +//------------------------------------------------------------------------------ + +#ifdef _MSC_VER + #define DSP_SSE3_AVAIL +#else + // other build environments +#endif + +//------------------------------------------------------------------------------ + +#ifdef _MSC_VER + #ifdef DSP_SSE3_AVAIL + #include + #include + #endif +#else + // other build environments +#endif + + +//static void dumpRoots(const char * title, Dsp::Roots &roots); + +//****************************************************************************** + +namespace Dsp +{ + + //-------------------------------------------------------------------------- + + // Brent's method is a numerical analysis technique for finding the + // minimum (or maximum) of a function. For filters, we use this to + // normalize the gain of a filter by finding the location where the + // filter has the highest magnitude response, and scaling the output + // by the reciprocal of the magnitude to reach unity gain. For most + // filters this is unnecessary, because we can just sample the response + // at a well known angular frequency. For example, w=0 for a low pass, + // w=pi for a high pass, the geometric center of the passband on a band-pass + // or 0 or pi for a band-stop. For filters with ripple we use a simple + // adjustment based on the ripple amount and whether or not the filter + // is of odd order. However, Brent's method is included in case anyone + // needs it. + + // Implementation of Brent's Method provided by + // John D. Cook (http://www.johndcook.com/) + // The return value of Minimize is the minimum of the function f. + // The location where f takes its minimum is returned in the variable minLoc. + // Notation and implementation based on Chapter 5 of Richard Brent's book + // "Algorithms for Minimization Without Derivatives". + template + CalcT BrentMinimize + ( + TFunction& f, // [in] objective function to minimize + CalcT leftEnd, // [in] smaller value of bracketing interval + CalcT rightEnd, // [in] larger value of bracketing interval + CalcT epsilon, // [in] stopping tolerance + CalcT& minLoc // [out] location of minimum + ) + { + CalcT d, e, m, p, q, r, tol, t2, u, v, w, fu, fv, fw, fx; + static const CalcT c = 0.5*(3.0 - ::std::sqrt(5.0)); + static const CalcT SQRT_DBL_EPSILON = ::std::sqrt(DBL_EPSILON); + + CalcT& a = leftEnd; CalcT& b = rightEnd; CalcT& x = minLoc; + + v = w = x = a + c*(b - a); d = e = 0.0; + fv = fw = fx = f(x); + int counter = 0; + loop: + counter++; + m = 0.5*(a + b); + tol = SQRT_DBL_EPSILON*::fabs(x) + epsilon; t2 = 2.0*tol; + // Check stopping criteria + if (::fabs(x - m) > t2 - 0.5*(b - a)) + { + p = q = r = 0.0; + if (::fabs(e) > tol) + { + // fit parabola + r = (x - w)*(fx - fv); + q = (x - v)*(fx - fw); + p = (x - v)*q - (x - w)*r; + q = 2.0*(q - r); + (q > 0.0) ? p = -p : q = -q; + r = e; e = d; + } + if (::fabs(p) < ::fabs(0.5*q*r) && p < q*(a - x) && p < q*(b - x)) + { + // A parabolic interpolation step + d = p/q; + u = x + d; + // f must not be evaluated too close to a or b + if (u - a < t2 || b - u < t2) + d = (x < m) ? tol : -tol; + } + else + { + // A golden section step + e = (x < m) ? b : a; + e -= x; + d = c*e; + } + // f must not be evaluated too close to x + if (::fabs(d) >= tol) + u = x + d; + else if (d > 0.0) + u = x + tol; + else + u = x - tol; + fu = f(u); + // Update a, b, v, w, and x + if (fu <= fx) + { + (u < x) ? b = x : a = x; + v = w; fv = fw; + w = x; fw = fx; + x = u; fx = fu; + } + else + { + (u < x) ? a = u : b = u; + if (fu <= fw || w == x) + { + v = w; fv = fw; + w = u; fw = fu; + } + else if (fu <= fv || v == x || v == w) + { + v = u; fv = fu; + } + } + goto loop; // Yes, the dreaded goto statement. But the code + // here is faithful to Brent's orginal pseudocode. + } + return fx; + } + + //-------------------------------------------------------------------------- + // + // Fast Trigonometric Functions + // + //-------------------------------------------------------------------------- + + // Three approximations for both sine and cosine at a given angle. + // The faster the routine, the larger the error. + // From http://lab.polygonal.de/2007/07/18/fast-and-accurate-sinecosine-approximation/ + + // Tuned for maximum pole stability. r must be in the range 0..kPi + // This one breaks down considerably at the higher angles. It is + // included only for educational purposes. + inline void fastestsincos( CalcT r, CalcT *sn, CalcT *cs ) + { + const CalcT c=0.70710678118654752440; // std::sqrt(2)/2 + CalcT v=(2-4*c)*r*r+c; + if(r kPi) x -= 2*kPi; + //compute sine + if (x < 0) *sn = 1.27323954 * x + 0.405284735 * x * x; + else *sn = 1.27323954 * x - 0.405284735 * x * x; + //compute cosine: sin(x + PI/2) = cos(x) + x += kPi_2; + if (x > kPi ) x -= 2*kPi; + if (x < 0) *cs = 1.27323954 * x + 0.405284735 * x * x; + else *cs = 1.27323954 * x - 0.405284735 * x * x; + } + + // Slower than ::fastersincos() but still faster than + // sin(), and with the best accuracy of these routines. + inline void fastsincos( CalcT x, CalcT *sn, CalcT *cs ) + { + CalcT s, c; + //always wrap input angle to -PI..PI + if (x < -kPi) x += 2*kPi; + else if (x > kPi) x -= 2*kPi; + //compute sine + if (x < 0) + { + s = 1.27323954 * x + .405284735 * x * x; + if (s < 0) s = .225 * (s * -s - s) + s; + else s = .225 * (s * s - s) + s; + } + else + { + s = 1.27323954 * x - 0.405284735 * x * x; + if (s < 0) s = .225 * (s * -s - s) + s; + else s = .225 * (s * s - s) + s; + } + *sn=s; + //compute cosine: sin(x + PI/2) = cos(x) + x += kPi_2; + if (x > kPi ) x -= 2*kPi; + if (x < 0) + { + c = 1.27323954 * x + 0.405284735 * x * x; + if (c < 0) c = .225 * (c * -c - c) + c; + else c = .225 * (c * c - c) + c; + } + else + { + c = 1.27323954 * x - 0.405284735 * x * x; + if (c < 0) c = .225 * (c * -c - c) + c; + else c = .225 * (c * c - c) + c; + } + *cs=c; + } + + // Faster approximations to std::sqrt() + // From http://ilab.usc.edu/wiki/index.php/Fast_Square_Root + // The faster the routine, the more error in the approximation. + + // Log Base 2 Approximation + // 5 times faster than std::sqrt() + + inline float fastsqrt1( float x ) + { + union { Int32 i; float x; } u; + u.x = x; + u.i = (Int32(1)<<29) + (u.i >> 1) - (Int32(1)<<22); + return u.x; + } + + inline double fastsqrt1( double x ) + { + union { Int64 i; double x; } u; + u.x = x; + u.i = (Int64(1)<<61) + (u.i >> 1) - (Int64(1)<<51); + return u.x; + } + + // Log Base 2 Approximation with one extra Babylonian Step + // 2 times faster than std::sqrt() + + inline float fastsqrt2( float x ) + { + float v=fastsqrt1( x ); + v = 0.5f * (v + x/v); // One Babylonian step + return v; + } + + inline double fastsqrt2(const double x) + { + double v=fastsqrt1( x ); + v = 0.5f * (v + x/v); // One Babylonian step + return v; + } + + // Log Base 2 Approximation with two extra Babylonian Steps + // 50% faster than std::sqrt() + + inline float fastsqrt3( float x ) + { + float v=fastsqrt1( x ); + v = v + x/v; + v = 0.25f* v + x/v; // Two Babylonian steps + return v; + } + + inline double fastsqrt3(const double x) + { + double v=fastsqrt1( x ); + v = v + x/v; + v = 0.25 * v + x/v; // Two Babylonian steps + return v; + } +}; + +//****************************************************************************** + +using namespace Dsp; + +//****************************************************************************** + +// Lightweight class for retrieving CPU information. +struct Cpu +{ +public: + struct Info + { + bool bMMX; + bool bSSE; + bool bSSE2; + bool bSSE3; + bool bSSSE3; // supplemental SSE3 + bool bSSE41; + bool bSSE42; + }; + + Cpu(); + + const Info &GetInfo( void ); + +protected: + Info m_info; +}; + +//------------------------------------------------------------------------------ + +Cpu::Cpu() +{ + m_info.bMMX=false; + m_info.bSSE=false; + m_info.bSSE2=false; + m_info.bSSE3=false; + m_info.bSSSE3=false; + m_info.bSSE41=false; + m_info.bSSE42=false; + +#ifdef _MSC_VER + int inf[4]; + __cpuid( inf, 0 ); + int nIds=inf[0]; + + if( nIds>=1 ) + { + __cpuid( inf, 1 ); + + m_info.bMMX =(inf[3]&(1<<23))!=0; + m_info.bSSE =(inf[3]&(1<<24))!=0; + m_info.bSSE2 =(inf[3]&(1<<25))!=0; + m_info.bSSE3 =(inf[2]&(1<<0))!=0; + m_info.bSSSE3 =(inf[2]&(1<<9))!=0; + m_info.bSSE41 =(inf[2]&(1<<19))!=0; + m_info.bSSE42 =(inf[2]&(1<<20))!=0; + } +#endif +} + +//------------------------------------------------------------------------------ + +const Cpu::Info &Cpu::GetInfo( void ) +{ + return m_info; +} + +//------------------------------------------------------------------------------ + +static Cpu gCpu; + +//****************************************************************************** +// +// Cascade +// +//****************************************************************************** + +Cascade::Cascade() +{ + m_stageCount=0; +} + +//------------------------------------------------------------------------------ + +int Cascade::GetStageCount( void ) +{ + return m_stageCount; +} + +//------------------------------------------------------------------------------ + +void Cascade::SetStageCount( int n ) +{ + assert( n>=1 && n<=m_stageMax ); + m_stageCount=n; +} + +//------------------------------------------------------------------------------ + +Cascade::Stage *Cascade::Stages( void ) +{ + return m_stage; +} + +//------------------------------------------------------------------------------ + +void Cascade::SetStage1( CalcT a1, CalcT a2, CalcT b0, CalcT b1, CalcT b2 ) +{ + m_stage->a[1]=a1; + m_stage->a[2]=a2; + m_stage->b[0]=b0; + m_stage->b[1]=b1; + m_stage->b[2]=b2; +} + +//------------------------------------------------------------------------------ + +void Cascade::Reset( void ) +{ + for( int i=0;iResponse_radian( w ) ); +} + +//****************************************************************************** +// +// CascadeFilter +// +//****************************************************************************** + +void CascadeFilter::Clear( void ) +{ + int n=m_nchan*GetStageCount(); + memset( m_histp, 0, n*sizeof(m_histp[0]) ); +} + +//------------------------------------------------------------------------------ + +// ALL SSE OPTIMIZATIONS ASSUME CalcT as double + +#ifdef DSP_SSE3_AVAIL + +template +static void ProcessISSEStageStereo( size_t frames, Ty *dest, Cascade::Stage *s, + CascadeFilter::Hist *h, int skip ) +{ +#if 1 + CalcT b0=s->b[0]; + __m128d m0=_mm_loadu_pd( &s->a[1] ); // a1 , a2 + __m128d m1=_mm_loadu_pd( &s->b[1] ); // b1 , b2 + __m128d m2=_mm_loadu_pd( &h[0].v[0] ); // h->v[0] , h->v[1] + __m128d m3=_mm_loadu_pd( &h[0].v[2] ); // h->v[2] , h->v[3] + __m128d m4=_mm_loadu_pd( &h[1].v[0] ); // h->v[0] , h->v[1] + __m128d m5=_mm_loadu_pd( &h[1].v[2] ); // h->v[2] , h->v[3] + + while( frames-- ) + { + CalcT in, b0in, out; + + __m128d m6; + __m128d m7; + + in=CalcT(*dest); + b0in=b0*in; + + m6=_mm_mul_pd ( m1, m2 ); // b1*h->v[0] , b2*h->v[1] + m7=_mm_mul_pd ( m0, m3 ); // a1*h->v[2] , a2*h->v[3] + m6=_mm_add_pd ( m6, m7 ); // b1*h->v[0] + a1*h->v[2], b2*h->v[1] + a2*h->v[3] + m7=_mm_load_sd( &b0in ); // b0*in , 0 + m6=_mm_add_sd ( m6, m7 ); // b1*h->v[0] + a1*h->v[2] + in*b0 , b2*h->v[1] + a2*h->v[3] + 0 + m6=_mm_hadd_pd( m6, m7 ); // b1*h->v[0] + a1*h->v[2] + in*b0 + b2*h->v[1] + a2*h->v[3], in*b0 + _mm_store_sd( &out, m6 ); + m6=_mm_loadh_pd( m6, &in ); // out , in + m2=_mm_shuffle_pd( m6, m2, _MM_SHUFFLE2( 0, 1 ) ); // h->v[0]=in , h->v[1]=h->v[0] + m3=_mm_shuffle_pd( m6, m3, _MM_SHUFFLE2( 0, 0 ) ); // h->v[2]=out, h->v[3]=h->v[2] + + *dest++=Ty(out); + + in=CalcT(*dest); + b0in=b0*in; + + m6=_mm_mul_pd ( m1, m4 ); // b1*h->v[0] , b2*h->v[1] + m7=_mm_mul_pd ( m0, m5 ); // a1*h->v[2] , a2*h->v[3] + m6=_mm_add_pd ( m6, m7 ); // b1*h->v[0] + a1*h->v[2], b2*h->v[1] + a2*h->v[3] + m7=_mm_load_sd( &b0in ); // b0*in , 0 + m6=_mm_add_sd ( m6, m7 ); // b1*h->v[0] + a1*h->v[2] + in*b0 , b2*h->v[1] + a2*h->v[3] + 0 + m6=_mm_hadd_pd( m6, m7 ); // b1*h->v[0] + a1*h->v[2] + in*b0 + b2*h->v[1] + a2*h->v[3], in*b0 + _mm_store_sd( &out, m6 ); + m6=_mm_loadh_pd( m6, &in ); // out , in + m4=_mm_shuffle_pd( m6, m4, _MM_SHUFFLE2( 0, 1 ) ); // h->v[0]=in , h->v[1]=h->v[0] + m5=_mm_shuffle_pd( m6, m5, _MM_SHUFFLE2( 0, 0 ) ); // h->v[2]=out, h->v[3]=h->v[2] + + *dest++=Ty(out); + + dest+=skip; + } + + // move history from registers back to state + _mm_storeu_pd( &h[0].v[0], m2 ); + _mm_storeu_pd( &h[0].v[2], m3 ); + _mm_storeu_pd( &h[1].v[0], m4 ); + _mm_storeu_pd( &h[1].v[2], m5 ); + +#else + // Template-specialized version from which the assembly was modeled + CalcT a1=s->a[1]; + CalcT a2=s->a[2]; + CalcT b0=s->b[0]; + CalcT b1=s->b[1]; + CalcT b2=s->b[2]; + while( frames-- ) + { + CalcT in, out; + + in=CalcT(*dest); + out=b0*in+b1*h[0].v[0]+b2*h[0].v[1] +a1*h[0].v[2]+a2*h[0].v[3]; + h[0].v[1]=h[0].v[0]; h[0].v[0]=in; + h[0].v[3]=h[0].v[2]; h[0].v[2]=out; + in=out; + *dest++=Ty(in); + + in=CalcT(*dest); + out=b0*in+b1*h[1].v[0]+b2*h[1].v[1] +a1*h[1].v[2]+a2*h[1].v[3]; + h[1].v[1]=h[1].v[0]; h[1].v[0]=in; + h[1].v[3]=h[1].v[2]; h[1].v[2]=out; + in=out; + *dest++=Ty(in); + + dest+=skip; + } +#endif +} + +#endif + +//------------------------------------------------------------------------------ + +template +void CascadeFilter::ProcessI( size_t frames, Ty *dest, int skip ) +{ +#ifdef DSP_SSE3_AVAIL + // Note there could be a loss of accuracy here. Unlike the original version + // of Process...() we are applying each stage to all of the input data. + // Since the underlying type Ty could be float, the results from this function + // may be different than the unoptimized version. However, it is much faster. + if( m_nchan==2 && gCpu.GetInfo().bSSE3 ) + { + int nstage=GetStageCount(); + Cascade::Stage *s=Stages(); + Hist *h=m_histp; + for( int i=nstage;i;i--,h+=2,s++ ) + { + ProcessISSEStageStereo( frames, dest, s, h, skip ); + } + } + else +#endif + { + int nstage=GetStageCount(); + Cascade::Stage *stagep=Stages(); + while( frames-- ) + { + Hist *h=m_histp; + for( int j=m_nchan;j;j-- ) + { + CalcT in=CalcT(*dest); + Cascade::Stage *s=stagep; + for( int i=nstage;i;i--,h++,s++ ) + { + CalcT out; + out=s->b[0]*in + s->b[1]*h->v[0] + s->b[2]*h->v[1] + + s->a[1]*h->v[2] + s->a[2]*h->v[3]; + h->v[1]=h->v[0]; h->v[0]=in; + h->v[3]=h->v[2]; h->v[2]=out; + in=out; + } + *dest++=Ty(in); + } + dest+=skip; + } + } +} + +//------------------------------------------------------------------------------ + +template +void CascadeFilter::ProcessII( size_t frames, Ty *dest, int skip ) +{ + int nstage=GetStageCount(); + Cascade::Stage *stagep=Stages(); + while( frames-- ) + { + Hist *h=m_histp; + for( int j=m_nchan;j;j-- ) + { + CalcT in=CalcT(*dest); + Cascade::Stage *s=stagep; + for( int i=nstage;i;i--,h++,s++ ) + { + CalcT d2=h->v[2]=h->v[1]; + CalcT d1=h->v[1]=h->v[0]; + CalcT d0=h->v[0]= + in+s->a[1]*d1 + s->a[2]*d2; + in=s->b[0]*d0 + s->b[1]*d1 + s->b[2]*d2; + } + *dest++=Ty(in); + } + dest+=skip; + } +} + +//****************************************************************************** +// +// Biquad +// +//****************************************************************************** + +// Formulas from http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt + +//------------------------------------------------------------------------------ + +Biquad::Biquad() +{ + SetStageCount( 1 ); +} + +//****************************************************************************** + +void BiquadLp::SetupCommon( CalcT sn, CalcT cs, CalcT q ) +{ + CalcT alph = sn / ( 2 * q ); + CalcT a0 = 1 / ( 1 + alph ); + CalcT b1 = 1 - cs; + CalcT b0 = a0 * b1 * 0.5; + CalcT a1 = 2 * cs; + CalcT a2 = alph - 1; + SetStage1( a1*a0, a2*a0, b0, b1*a0, b0 ); +} + +//------------------------------------------------------------------------------ + +void BiquadLp::Setup( CalcT normFreq, CalcT q ) +{ + CalcT w0 = 2 * kPi * normFreq; + CalcT cs = cos(w0); + CalcT sn = sin(w0); + SetupCommon( sn, cs, q ); +} + +//------------------------------------------------------------------------------ + +void BiquadLp::SetupFast( CalcT normFreq, CalcT q ) +{ + CalcT w0 = 2 * kPi * normFreq; + CalcT sn, cs; + fastsincos( w0, &sn, &cs ); + SetupCommon( sn, cs, q ); +} + +//****************************************************************************** + +void BiquadHp::SetupCommon( CalcT sn, CalcT cs, CalcT q ) +{ + CalcT alph = sn / ( 2 * q ); + CalcT a0 = -1 / ( 1 + alph ); + CalcT b1 = -( 1 + cs ); + CalcT b0 = a0 * b1 * -0.5; + CalcT a1 = -2 * cs; + CalcT a2 = 1 - alph; + SetStage1( a1*a0, a2*a0, b0, b1*a0, b0 ); +} + +//------------------------------------------------------------------------------ + +void BiquadHp::Setup( CalcT normFreq, CalcT q ) +{ + CalcT w0 = 2 * kPi * normFreq; + CalcT cs = cos(w0); + CalcT sn = sin(w0); + SetupCommon( sn, cs, q ); +} + +//------------------------------------------------------------------------------ + +void BiquadHp::SetupFast( CalcT normFreq, CalcT q ) +{ + CalcT w0 = 2 * kPi * normFreq; + CalcT sn, cs; + fastsincos( w0, &sn, &cs ); + SetupCommon( sn, cs, q ); +} + +//****************************************************************************** + +void BiquadBp1::SetupCommon( CalcT sn, CalcT cs, CalcT q ) +{ + CalcT alph = sn / ( 2 * q ); + CalcT a0 = -1 / ( 1 + alph ); + CalcT b0 = a0 * ( sn * -0.5 ); + CalcT a1 = -2 * cs; + CalcT a2 = 1 - alph; + SetStage1( a1*a0, a2*a0, b0, 0, -b0 ); +} + +//-------------------------------------------------------------------------- + +void BiquadBp1::Setup( CalcT normFreq, CalcT q ) +{ + CalcT w0 = 2 * kPi * normFreq; + CalcT cs = cos(w0); + CalcT sn = sin(w0); + SetupCommon( sn, cs, q ); +} + +//-------------------------------------------------------------------------- + +void BiquadBp1::SetupFast( CalcT normFreq, CalcT q ) +{ + CalcT w0 = 2 * kPi * normFreq; + CalcT sn, cs; + fastsincos( w0, &sn, &cs ); + SetupCommon( sn, cs, q ); +} + +//****************************************************************************** + +void BiquadBp2::SetupCommon( CalcT sn, CalcT cs, CalcT q ) +{ + CalcT alph = sn / ( 2 * q ); + CalcT b0 = -alph; + CalcT b2 = alph; + CalcT a0 = -1 / ( 1 + alph ); + CalcT a1 = -2 * cs; + CalcT a2 = 1 - alph; + SetStage1( a1*a0, a2*a0, b0*a0, 0, b2*a0 ); +} + +//-------------------------------------------------------------------------- + +void BiquadBp2::Setup( CalcT normFreq, CalcT q ) +{ + CalcT w0 = 2 * kPi * normFreq; + CalcT cs = cos(w0); + CalcT sn = sin(w0); + SetupCommon( sn, cs, q ); +} + +//-------------------------------------------------------------------------- + +void BiquadBp2::SetupFast( CalcT normFreq, CalcT q ) +{ + CalcT w0 = 2 * kPi * normFreq; + CalcT sn, cs; + fastsincos( w0, &sn, &cs ); + SetupCommon( sn, cs, q ); +} + +//****************************************************************************** + +void BiquadBs::SetupCommon( CalcT sn, CalcT cs, CalcT q ) +{ + CalcT alph = sn / ( 2 * q ); + CalcT a0 = 1 / ( 1 + alph ); + CalcT b1 = a0 * ( -2 * cs ); + CalcT a2 = alph - 1; + SetStage1( -b1, a2*a0, a0, b1, a0 ); +} + +//-------------------------------------------------------------------------- + +void BiquadBs::Setup( CalcT normFreq, CalcT q ) +{ + CalcT w0 = 2 * kPi * normFreq; + CalcT cs = cos(w0); + CalcT sn = sin(w0); + SetupCommon( sn, cs, q ); +} + +//-------------------------------------------------------------------------- + +void BiquadBs::SetupFast( CalcT normFreq, CalcT q ) +{ + CalcT w0 = 2 * kPi * normFreq; + CalcT sn, cs; + fastsincos( w0, &sn, &cs ); + SetupCommon( sn, cs, q ); +} + +//****************************************************************************** + +void BiquadAp::SetupCommon( CalcT sn, CalcT cs, CalcT q ) +{ + CalcT alph = sn / ( 2 * q ); + CalcT b2 = 1 + alph; + CalcT a0 = 1 / b2; + CalcT b0 =( 1 - alph ) * a0; + CalcT b1 = -2 * cs * a0; + SetStage1( -b1, -b0, b0, b1, b2*a0 ); +} + +//-------------------------------------------------------------------------- + +void BiquadAp::Setup( CalcT normFreq, CalcT q ) +{ + CalcT w0 = 2 * kPi * normFreq; + CalcT cs = cos(w0); + CalcT sn = sin(w0); + SetupCommon( sn, cs, q ); +} + +//-------------------------------------------------------------------------- + +void BiquadAp::SetupFast( CalcT normFreq, CalcT q ) +{ + CalcT w0 = 2 * kPi * normFreq; + CalcT sn, cs; + fastsincos( w0, &sn, &cs ); + SetupCommon( sn, cs, q ); +} + +//****************************************************************************** + +void BiquadLs::SetupCommon( CalcT cs, CalcT A, CalcT sa ) +{ + CalcT An = A-1; + CalcT Ap = A+1; + CalcT Ancs = An*cs; + CalcT Apcs = Ap*cs; + CalcT b0 = A * (Ap - Ancs + sa ); + CalcT b2 = A * (Ap - Ancs - sa ); + CalcT b1 = 2 * A * (An - Apcs); + CalcT a2 = sa - (Ap + Ancs); + CalcT a0 = 1 / (Ap + Ancs + sa ); + CalcT a1 = 2 * (An + Apcs); + SetStage1( a1*a0, a2*a0, b0*a0, b1*a0, b2*a0 ); +} + +//-------------------------------------------------------------------------- + +void BiquadLs::Setup( CalcT normFreq, CalcT dB, CalcT shelfSlope ) +{ + CalcT A = pow( 10, dB/40 ); + CalcT w0 = 2 * kPi * normFreq; + CalcT cs = cos(w0); + CalcT sn = sin(w0); + CalcT al = sn / 2 * ::std::sqrt( (A + 1/A) * (1/shelfSlope - 1) + 2 ); + CalcT sa = 2 * ::std::sqrt( A ) * al; + SetupCommon( cs, A, sa ); +} + +//-------------------------------------------------------------------------- + +// This could be optimized further +void BiquadLs::SetupFast( CalcT normFreq, CalcT dB, CalcT shelfSlope ) +{ + CalcT A = pow( 10, dB/40 ); + CalcT w0 = 2 * kPi * normFreq; + CalcT sn, cs; + fastsincos( w0, &sn, &cs ); + CalcT al = sn / 2 * fastsqrt1( (A + 1/A) * (1/shelfSlope - 1) + 2 ); + CalcT sa = 2 * fastsqrt1( A ) * al; + SetupCommon( cs, A, sa ); +} + +//****************************************************************************** + +void BiquadHs::SetupCommon( CalcT cs, CalcT A, CalcT sa ) +{ + CalcT An = A-1; + CalcT Ap = A+1; + CalcT Ancs = An*cs; + CalcT Apcs = Ap*cs; + CalcT b0 = A * (Ap + Ancs + sa ); + CalcT b1 = -2 * A * (An + Apcs); + CalcT b2 = A * (Ap + Ancs - sa ); + CalcT a0 = 1 / (Ap - Ancs + sa ); + CalcT a2 = Ancs + sa - Ap; + CalcT a1 = -2 * (An - Apcs); + SetStage1( a1*a0, a2*a0, b0*a0, b1*a0, b2*a0 ); +} + +//-------------------------------------------------------------------------- + +void BiquadHs::Setup( CalcT normFreq, CalcT dB, CalcT shelfSlope ) +{ + CalcT A = pow( 10, dB/40 ); + CalcT w0 = 2 * kPi * normFreq; + CalcT cs = cos(w0); + CalcT sn = sin(w0); + + CalcT alph = sn / 2 * ::std::sqrt( (A + 1/A) * (1/shelfSlope - 1) + 2 ); + CalcT sa = 2 * ::std::sqrt( A ) * alph; + SetupCommon( cs, A, sa ); +} + +//-------------------------------------------------------------------------- + +void BiquadHs::SetupFast( CalcT normFreq, CalcT dB, CalcT shelfSlope ) +{ + CalcT A = pow( 10, dB/40 ); + CalcT w0 = 2 * kPi * normFreq; + CalcT sn, cs; + fastsincos( w0, &sn, &cs ); + + CalcT alph = sn / 2 * fastsqrt1( (A + 1/A) * (1/shelfSlope - 1) + 2 ); + CalcT sa = 2 * fastsqrt1( A ) * alph; + SetupCommon( cs, A, sa ); +} + +//****************************************************************************** + +void BiquadEq::SetupCommon( CalcT sn, CalcT cs, CalcT alph, CalcT A ) +{ + CalcT t=alph*A; + CalcT b0 = 1 - t; + CalcT b2 = 1 + t; + t=alph/A; + CalcT a0 = 1 / ( 1 + t ); + CalcT a2 = t - 1; + CalcT b1 = a0 * ( -2 * cs ); + CalcT a1 = -b1; + + SetStage1( a1, a2*a0, b0*a0, b1, b2*a0 ); +} + +//-------------------------------------------------------------------------- + +void BiquadEq::Setup( CalcT normFreq, CalcT dB, CalcT bandWidth ) +{ + CalcT A = pow( 10, dB/40 ); + CalcT w0 = 2 * kPi * normFreq; + CalcT cs = cos(w0); + CalcT sn = sin(w0); + CalcT alph = sn * sinh( kLn2/2 * bandWidth * w0/sn ); + SetupCommon( sn, cs, alph, A ); +} + +//-------------------------------------------------------------------------- + +void BiquadEq::SetupFast( CalcT normFreq, CalcT dB, CalcT bandWidth ) +{ + CalcT A = pow( 10, dB/40 ); + CalcT w0 = 2 * kPi * normFreq; + CalcT sn, cs; + fastsincos( w0, &sn, &cs ); + CalcT alph = sn * sinh( kLn2/2 * bandWidth * w0/sn ); + SetupCommon( sn, cs, alph, A ); +} + +//****************************************************************************** +// +// Layout +// +//****************************************************************************** + +void Layout::Realize( Cascade *cascade ) { + Realize_custom(cascade); + //Realize_orig(cascade); +} + +// I had as .00000001, but couldn't make 12th order butter +const double smallNumber = .00000001; +bool closeTo(double x, double y, double tolerance) { + return std::abs(x - y) < tolerance; +} + +void fillStageOneRoot_notNormalized(double * taps, Complex root) { + if (!closeTo(root.imag(), 0, smallNumber)) { + throw std::runtime_error("unmatched complex root"); + } + + + /* h(z) = root - (z**-1) + * will be zero when z**-1 == root + * or z == 1/root + */ + taps[0] = 1.0 / root.real(); + taps[1] = -1; + taps[2] = 0; +} + +bool areConjugates(const Complex& root1, const Complex& root2) { + return closeTo(root1.real(), root2.real(), smallNumber) && + closeTo(root1.imag(), -root2.imag(), smallNumber); + //return (root1.real() == root2.real() && + // closeTo(root1.imag(), -root2.imag(), smallNumber)); +} + +bool isComplex(const Complex& x) { + + return !closeTo(x.imag(), 0, smallNumber); +} + +// (z-p1)*(z-p2) = z**2 - ( p1 + p2) z + p1 * p2 +// = (p1 * p2) + ( -(p1 + p2)) * z + z2 +// = (p1 * p2) * z**-2 - (p1 + p2) * z**-1 + 1 +void fillStageTwoRoots_notNormalized(double * taps, Complex root1, Complex root2) { + if ( (root1.imag() == 0) && (root2.imag()==0)) { + // both are real is ok + } + else if ( areConjugates(root1, root2)) { + } + else + throw std::runtime_error("unmatched complex roots"); + + + // h(z) = root - z ;will be zero when z == root + taps[0] = 1.0; + taps[1] = -(root1 + root2).real(); + taps[2] =(root1 * root2).real(); + //taps[0] = (root1 * root2).real(); + //taps[1] = -(root1 + root2).real(); + //taps[2] =1; +} +/* On input: H(z) = 1 / (a0 + a1z + a2z); + * on output H(z) = 1 / (1 - a1z - a2z) + * + * we drop the scale factor - it will be recalculated later + */ +void normalizeDenominator(double * denTaps) { + denTaps[1] *= -1.0; + denTaps[2] *= -1.0; // now a0 - a1z -a2z + + double scale = 1.0 / denTaps[0]; + + denTaps[0] *= scale; + denTaps[1] *= scale; + denTaps[2] *= scale; + +} +void fillStageOnePole(Cascade::Stage * stage, Complex pole) { + double * taps = stage->a; + fillStageOneRoot_notNormalized(taps, pole); + normalizeDenominator(taps); +} + +void fillStageTwoPoles(Cascade::Stage * stage, Complex pole1, Complex pole2) { + double * taps = stage->a; + fillStageTwoRoots_notNormalized(taps, pole1, pole2); + normalizeDenominator(taps); +} +void fillStageTwoZeros(Cascade::Stage * stage, Complex zero1, Complex zero2) { + double * taps = stage->b; + fillStageTwoRoots_notNormalized(taps, zero1, zero2); +} + +void fillStageOneZero(Cascade::Stage * stage, Complex zero) { + double * taps = stage->b; + fillStageOneRoot_notNormalized(taps, zero); +} + +int locateMatchingRoot(Roots& roots, int indexThatNeedsMatch, int indexMatchDestination) { + const Complex& rootToMatch = roots.GetNth(indexThatNeedsMatch); + for (int i = indexThatNeedsMatch+1; i< roots.GetCount(); ++i) { + Complex& root = roots.GetNth(i); + if (areConjugates(rootToMatch, root)) { + return i; + } + } + throw std::runtime_error("unmatched complex roots c"); +} + +void pairRoots2(Roots& roots) { + int num = roots.GetCount(); + for (int i=0; i= (num-1)) { + // if no more to pair with, we are hosed + throw std::runtime_error("unmatched complex roots b"); + } + else if (areConjugates(root, roots.GetNth(i + 1)) ){ + // if next root already a match, we are cool + ++i; // skip over the matching + } + else { + // here we are first of the pair, but our mate is missing + const int match = locateMatchingRoot(roots, i, i+1); + + // now swap the unmatched root for the match + Complex temp = roots.GetNth(i+1); // save off the one we are moving + roots.GetNth(i+1) = roots.GetNth(match); + roots.GetNth(match) = temp; + ++i; // advance over our mate + + } + } + } + +} + +#if 0 // unused, for now +static void dumpRoots(const char * title, Roots &roots) { + char buf[512]; + sprintf_s(buf, sizeof(buf), "\nDump Roots: %s\n", title); + //DebugUtil::trace(buf); + + for (int i=0; iSetStageCount( stages ); + + + cascade->Reset(); + + for (int i=0; iStages()+ i); + if (poles == 1) { + Complex c=Pole(i * 2); + fillStageOnePole(s, c); + --poles; + } + else { + Complex c1=Pole(i * 2); + Complex c2=Pole(i * 2 + 1); + fillStageTwoPoles(s, c1, c2); + poles -= 2; + } + if (zeros == 1) { + Complex c=Zero(i * 2); + fillStageOneZero(s, c); + --zeros; + } + else { + Complex c1=Zero(i * 2); + Complex c2=Zero(i * 2 + 1); + fillStageTwoZeros(s, c1, c2); + zeros -= 2; + } + + } + + // Normalization + + // The assertions are arbitrary.... + assert(m_normal.w >= 0 && m_normal.w <= kPi); + assert(m_normal.gain < 1000000000000); + assert(m_normal.gain > -1000000000000); + + cascade->Scale( m_normal.gain / std::abs( cascade->Response_radian( m_normal.w ) ) ); +} + +void Layout::Realize_orig( Cascade *cascade ) +{ + // Calculate number of second order sections required. + { + int s1=(CountPoles()+1)/2; + int s2=(CountZeros()+1)/2; + assert( s1==s2 ); // I am not sure if it works otherwise + cascade->SetStageCount( s1>s2?s1:s2 ); + } + + cascade->Reset(); + + int n; + + // Poles + + n=0; + for( int i=0;i0 ) + BuildA( cascade, 2*c.real(), -std::norm(c), &n ); + } + + // Zeros + + n=0; + for( int i=0;i0 ) { + //DebugUtil::trace("Realize img>0\n"); + BuildB( cascade, std::norm(c), -2*c.real(), 1, &n ); + } + else { + //DebugUtil::trace("Realize img<0\n"); + } + } + + // Normalization + + assert(m_normal.w >= 0 && m_normal.w <= 2.0); + assert(m_normal.gain < 1000000000000); + assert(m_normal.gain > -1000000000000); + + cascade->Scale( m_normal.gain / std::abs( cascade->Response_radian( m_normal.w ) ) ); +}; + +//------------------------------------------------------------------------------ + +void Layout::BuildA( Cascade *cascade, CalcT x1, CalcT x2, int *na ) +{ + if( x2!=0 ) + { + Cascade::Stage *s=cascade->Stages()+cascade->GetStageCount()-1-*na; + assert( s->a[1]==0 && s->a[2]==0 ); + s->a[1]=x1; + s->a[2]=x2; + (*na)++; +#ifdef _LOG + { + char buf[512]; + sprintf(buf, "builda2, set a1 to %f a2 %f\n", s->a[1], s->a[2]); + DebugUtil::trace(buf); + } +#endif + } + else + { + // combine + Cascade::Stage *s=cascade->Stages(); + s->a[2]=-s->a[1]*x1; + s->a[1]+=x1; +#ifdef _LOG + { + char buf[512]; + sprintf(buf, "builda1, set a1 to %f a2 %f\n", s->a[1], s->a[2]); + DebugUtil::trace(buf); + } +#endif + if( s->a[2]!=0 ) + { + int n=cascade->GetStageCount()-1-*na; + if( n>0 ) + { + Cascade::Stage *f=cascade->Stages()+n; + f->a[1]=s->a[1]; + f->a[2]=s->a[2]; + s->a[1]=0; + s->a[2]=0; +#ifdef _LOG + { + char buf[512]; + sprintf(buf, "builda1, moved a UP, set a1, a2 to zero\n"); + DebugUtil::trace(buf); + } +#endif + (*na)++; + } + } + } +} + +//------------------------------------------------------------------------------ + +void Layout::BuildB( Cascade *cascade, CalcT x0, CalcT x1, CalcT x2, int *nb ) +{ + +#ifdef _LOG + { + char buf[512]; + sprintf(buf, "enter buildb x0=%f x1=%f x2=%f\n", + x0, x1, x2); + DebugUtil::trace(buf); + } +#endif + if( x2!=0 ) + { + Cascade::Stage *s=cascade->Stages()+cascade->GetStageCount()-1-*nb; + s->b[0]=x0; + s->b[1]=x1; + s->b[2]=x2; + (*nb)++; + +#ifdef _LOG + { + char buf[512]; + sprintf(buf, "buildb, set b0 to %f b1 to %f b2 %f\n", s->b[0], s->b[1], s->b[2]); + DebugUtil::trace(buf); + } +#endif + } + else + { + + + // combine + // (b0 + z b1)(x0 + z x1) = (b0 x0 + (b1 x0+b0 x1) z + b1 x1 z^2) + Cascade::Stage *s=cascade->Stages(); + +#ifdef _LOG + { + char buf[512]; + sprintf(buf, "enter buildb2 b0=%f b1=%f b2=%f \n", + s->b[0], s->b[1], s->b[2] + ); + DebugUtil::trace(buf); + } +#endif + + s->b[2]=s->b[1]*x1; + s->b[1]=s->b[1]*x0+s->b[0]*x1; + s->b[0]*=x0; + +#ifdef _LOG + { + char buf[512]; + sprintf(buf, "buildb 2, set b0 to %f b1 to %f b2 %f\n", s->b[0], s->b[1], s->b[2]); + DebugUtil::trace(buf); + } +#endif + if( s->b[2]!=0 ) + { + int n=cascade->GetStageCount()-1-*nb; + if( n>0 ) + { + Cascade::Stage *f=cascade->Stages()+n; + f->b[0]=s->b[0]; + f->b[1]=s->b[1]; + f->b[2]=s->b[2]; + s->b[0]=1; + s->b[1]=0; + s->b[2]=0; + (*nb)++; +#ifdef _LOG + { + char buf[512]; + sprintf(buf, "buildb 3, set b to 1, bnext b0 to %f b1 to %f b2 %f\n", f->b[0], f->b[1], f->b[2]); + DebugUtil::trace(buf); + } +#endif + } + } + } +} + +//****************************************************************************** + +const Complex Dsp::infinity(std::numeric_limits::infinity()); +//const Complex Dsp::positiveInfinity( 2 ); + +//****************************************************************************** +// +// Transformations +// +//****************************************************************************** + +void LowPass::Transform( const Spec &spec, Layout *result, const Layout &layout ) +{ + Transform( spec, &result->Poles(), layout.Poles() ); + Transform( spec, &result->Zeros(), layout.Zeros() ); + + Normalization n0=layout.GetNormalization(); + Normalization &n=result->GetNormalization(); + n=n0; +} + +//------------------------------------------------------------------------------ + +void LowPass::Transform( const Spec &spec, Roots *result, const Roots &roots ) +{ + CalcT w=2*kPi*spec.cutoffFreq/spec.sampleRate; + // prewarp + CalcT k=tan(w*0.5); + result->SetCount( roots.GetCount() ); + + assert (k > 0); +#ifdef _LOG + { + char buf[512]; + sprintf(buf, "trans fc=%f sr = %f w = %f k = %f\n", + spec.cutoffFreq, spec.sampleRate, w, k); + DebugUtil::trace(buf); + } +#endif + + for( int i=0;iGetNth(i); + + if( r==infinity ) + { + c=Complex( -1, 0 ); + } + else + { + // frequency transform + c=r*k; + // bilinear low-pass xform + c=(1.+c)/(1.-c); + } +#ifdef _LOG + { + char buf[512]; + sprintf(buf, "trans[%d] from %f,%f to %f,%f\n", i, + r.real(), r.imag(), + c.real(), c.imag() + ); + DebugUtil::trace(buf); + } +#endif + } +} + +//------------------------------------------------------------------------------ + +void HighPass::Transform( const Spec &spec, Layout *result, const Layout &layout ) +{ + Transform( spec, &result->Poles(), layout.Poles() ); + Transform( spec, &result->Zeros(), layout.Zeros() ); + + Normalization n0=layout.GetNormalization(); + Normalization &n=result->GetNormalization(); + n.w=kPi-n0.w; + n.gain=n0.gain; +} + +//------------------------------------------------------------------------------ + +void HighPass::Transform( const Spec &spec, Roots *result, const Roots &roots ) +{ + + + + + CalcT w=2*kPi*spec.cutoffFreq/spec.sampleRate; + CalcT k=1./tan(w*0.5); // prewarp + result->SetCount( roots.GetCount() ); + + +#ifdef _LOG + { + char buf[512]; + sprintf(buf, "HP trans fc=%f width=%f sr = %f w = %f k = %f\n", + spec.cutoffFreq, + spec.normWidth, + spec.sampleRate, w, k); + DebugUtil::trace(buf); + } +#endif + + + for( int i=0;iGetNth(i); + + if( r==infinity ) + { + c=Complex( 1, 0 ); + } + else + { + // frequency transform + c=r*k; + // bilinear high-pass xform + c=-(1.+c)/(1.-c); + } +#ifdef _LOG + { + char buf[512]; + sprintf(buf, "trans[%d] from %f,%f to %f,%f\n", i, + r.real(), r.imag(), + c.real(), c.imag() + ); + DebugUtil::trace(buf); + } +#endif + } +} + +//------------------------------------------------------------------------------ + +void BandPass::Transform( const Spec &spec, Layout *result, const Layout &layout ) +{ + //DebugUtil::trace("----- BandPass::Transform (poles then zeros) -----\n"); + Transform( spec, &result->Poles(), layout.Poles() ); + Transform( spec, &result->Zeros(), layout.Zeros() ); + + Normalization n0=layout.GetNormalization(); + Normalization &n=result->GetNormalization(); + + if( n0.w==0 ) // hack + { + CalcT angularWidth=2*kPi*spec.normWidth; + CalcT wc2=2*kPi*spec.centerFreq/spec.sampleRate-(angularWidth/2); + CalcT wc =wc2+angularWidth; + if( wc2<1e-8 ) + wc2=1e-8; + if( wc >kPi-1e-8 ) + wc =kPi-1e-8; + + wc+=n0.w; + wc2+=n0.w; + n.w=2*atan(sqrt(tan(wc*0.5)*tan(wc2*0.5))); + } + else + { + // yes this is a giant hack to make shelves work + n.w=((spec.centerFreq/spec.sampleRate)<0.25)?kPi:0; + } + n.gain=n0.gain; +} + +//------------------------------------------------------------------------------ + +void BandPass::Transform( const Spec &spec, Roots *result, const Roots &roots ) +{ + CalcT angularWidth=2*kPi*spec.normWidth; + m_wc2=2*kPi*spec.centerFreq/spec.sampleRate-(angularWidth/2); + m_wc =m_wc2+angularWidth; + + // Probably filter spec is whack + assert(m_wc2 >= 1e-8); + assert(m_wc <= kPi-1e-8 ); + + if( m_wc2<1e-8 ) + m_wc2=1e-8; + if( m_wc >kPi-1e-8 ) + m_wc =kPi-1e-8; + + int n=roots.GetCount(); + result->SetCount( n*2 ); + + +#ifdef _LOG + { + char buf[512]; + sprintf(buf, "BP trans fc=%f width=%f sr = %f wc2 = %f wc = %f\n", + spec.cutoffFreq, + spec.normWidth, + spec.sampleRate, m_wc2, m_wc); + DebugUtil::trace(buf); + } +#endif + + for( int i=0;iGetNth(j)=Complex( -1, 0 ); + result->GetNth(j+1)=Complex( 1, 0 ); + } + else + { + // bilinear transform + c=(1.+c)/(1.-c); + result->GetNth(j)=BandPassTransform( j, c ); + result->GetNth(j+1)=BandPassTransform( j+1, c ); + } + #ifdef _LOG + { + char buf[512]; + sprintf(buf, "trans[%d] from %f,%f to %f,%f + %f,%f\n", i, + c.real(), c.imag(), + result->GetNth(j).real(), result->GetNth(j).imag(), + result->GetNth(j+1).real(), result->GetNth(j+1).imag() + ); + DebugUtil::trace(buf); + } +#endif + } +} + +//------------------------------------------------------------------------------ + +Complex BandPass::BandPassTransform( int i, const Complex &c ) +{ + CalcT a= cos((m_wc+m_wc2)*0.5)/ + cos((m_wc-m_wc2)*0.5); + CalcT b=1/tan((m_wc-m_wc2)*0.5); + Complex c2(0); + c2=addmul( c2, 4*(b*b*(a*a-1)+1), c ); + c2+=8*(b*b*(a*a-1)-1); + c2*=c; + c2+=4*(b*b*(a*a-1)+1); + c2=std::sqrt( c2 ); + if ((i & 1) == 0) + c2=-c2; + c2=addmul( c2, 2*a*b, c ); + c2+=2*a*b; + Complex c3(0); + c3=addmul( c3, 2*(b-1), c ); + c3+=2*(1+b); + return c2/c3; +} + +//------------------------------------------------------------------------------ + +void BandStop::Transform( const Spec &spec, Layout *result, const Layout &layout ) +{ + Transform( spec, &result->Poles(), layout.Poles() ); + Transform( spec, &result->Zeros(), layout.Zeros() ); + + Normalization n0=layout.GetNormalization(); + Normalization &n=result->GetNormalization(); + + n.w=((spec.centerFreq/spec.sampleRate)<0.25)?kPi:0; + n.gain=n0.gain; +} + +void BandStop::Transform( const Spec &spec, Roots *result, const Roots &roots ) +{ + CalcT angularWidth=2*kPi*spec.normWidth; + m_wc2=2*kPi*spec.centerFreq/spec.sampleRate-(angularWidth/2); + m_wc =m_wc2+angularWidth; + if( m_wc2<1e-8 ) + m_wc2=1e-8; + if( m_wc >kPi-1e-8 ) + m_wc =kPi-1e-8; + + int n=roots.GetCount(); + result->SetCount( n*2 ); + for( int i=0;iGetNth(j)=BandStopTransform( j, c ); + result->GetNth(j+1)=BandStopTransform( j+1, c ); + } +} + +//------------------------------------------------------------------------------ + +void BandStop::DesignZeros( const Spec &spec, Layout *layout ) +{ + int n=spec.order; + Roots *roots=&layout->Zeros(); + roots->SetCount( n*2 ); + for( int i=0;iGetNth(i)=BandStopTransform( i, Complex( -1 ) ); + } +} + +//------------------------------------------------------------------------------ + +Complex BandStop::BandStopTransform( int i, const Complex &c ) +{ + CalcT a=cos((m_wc+m_wc2)*.5) / + cos((m_wc-m_wc2)*.5); + CalcT b=tan((m_wc-m_wc2)*.5); + Complex c2(0); + c2=addmul( c2, 4*(b*b+a*a-1), c ); + c2+=8*(b*b-a*a+1); + c2*=c; + c2+=4*(a*a+b*b-1); + c2=std::sqrt( c2 ); + c2*=((i&1)==0)?.5:-.5; + c2+=a; + c2=addmul( c2, -a, c ); + Complex c3( b+1 ); + c3=addmul( c3, b-1, c ); + return c2/c3; +} + +//****************************************************************************** +// +// Prototype +// +//****************************************************************************** + +//****************************************************************************** +// +// Butterworth +// +//****************************************************************************** + +void Butter::Design( const Spec &spec ) +{ + int n=spec.order; + + SetPoles( n ); + SetZeros( n ); + for( int i=0;i=abs(gainDb) ) + rippleDb=abs(gainDb); + if( gainDb<0 ) + rippleDb=-rippleDb; + CalcT G=std::pow( 10., gainDb/20.0 ); + CalcT Gb=std::pow( 10., (gainDb-rippleDb)/20.0 ); + CalcT G0=1; + CalcT g0=pow(G0,1./n); + CalcT eps; + if( Gb!=G0 ) + eps=sqrt((G*G-Gb*Gb)/(Gb*Gb-G0*G0)); + else + eps=G-1; // This is surely wrong + CalcT b=pow(G/eps+Gb*sqrt(1+1/(eps*eps)), 1./n); + CalcT u=log(b/g0); + CalcT v=log(pow(1./eps+sqrt(1+1/(eps*eps)),1./n)); + + SetPoles( n ); + SetZeros( n ); + for( int i=0;i=abs(gainDb) ) + rippleDb=abs(gainDb); + if( gainDb<0 ) + rippleDb=-rippleDb; + CalcT G=std::pow( 10., gainDb/20.0 ); + CalcT Gb=std::pow( 10., (rippleDb)/20.0 ); + CalcT G0=1; + CalcT g=pow(G,1./n); + CalcT eps; + if( Gb!=G0 ) + eps=sqrt((G*G-Gb*Gb)/(Gb*Gb-G0*G0)); + else + eps=G-1; // This is surely wrong + CalcT b=pow(G0*eps+Gb*sqrt(1+eps*eps), 1./n); + CalcT u=log(b/g); + CalcT v=log(pow(eps+sqrt(1+eps*eps),1./n)); + + SetPoles( n ); + SetZeros( n ); + for( int i=0;i m_em) + m_c1[2*m_m] = 0; + for (i = 0; i <= 2*m_m; i += 2) + m_a1[m_m-i/2] = m_c1[i] + m_d1[i]; + CalcT a0 = findfact(m_m); + int r = 0; + while (r < m_em/2) + { + r++; + m_p[r] /= 10; + m_q1[r] /= 100; + CalcT d = 1+m_p[r]+m_q1[r]; + m_b1[r] = (1+m_p[r]/2)*fbb/d; + m_zf1[r] = fb/pow(d, .25); + m_zq1[r] = 1/sqrt(abs(2*(1-m_b1[r]/(m_zf1[r]*m_zf1[r])))); + m_zw1[r] = tp*m_zf1[r]; + m_rootR[r] = -.5*m_zw1[r]/m_zq1[r]; + m_rootR[r+m_em/2] = m_rootR[r]; + m_rootI[r] = .5*sqrt(abs(m_zw1[r]*m_zw1[r]/(m_zq1[r]*m_zq1[r]) - 4*m_zw1[r]*m_zw1[r])); + m_rootI[r+m_em/2] = -m_rootI[r]; + } + if (a0 != 0) + { + m_rootR[r+1+m_em/2] = -sqrt(fbb/(.1*a0-1))*tp; + m_rootI[r+1+m_em/2] = 0; + } + + SetPoles( n ); + SetZeros( n ); + for( int i=0;i m_em) + { + ji = i-m_em; + jf = m_em; + } + m_c1[i] = 0; + int j; + for(j = ji; j <= jf; j += 2) + m_c1[i] += m_a1[j]*(m_a1[i-j]*pow(10., m_m-i/2)); +} + +//------------------------------------------------------------------------------ + +// calculate f(z) +void Elliptic::calcfz( void ) +{ + int i = 1; + if( m_nin == 1 ) + m_s1[i++] = 1; + for (; i <= m_nin+m_n2; i++) + m_s1[i] = m_s1[i+m_n2] = m_z1[i-m_nin]; + prodpoly(m_nin+2*m_n2); + for (i = 0; i <= m_em; i += 2) + m_a1[i] = m_e*m_b1[i]; + for (i = 0; i <= 2*m_em; i += 2) + calcfz2(i); +} + +//------------------------------------------------------------------------------ + +// determine q(z) +void Elliptic::calcqz( void ) +{ + int i; + for (i = 1; i <= m_nin; i++) + m_s1[i] = -10; + for (; i <= m_nin+m_n2; i++) + m_s1[i] = -10*m_z1[i-m_nin]*m_z1[i-m_nin]; + for (; i <= m_nin+2*m_n2; i++) + m_s1[i] = m_s1[i-m_n2]; + prodpoly(m_m); + int dd = ((m_nin & 1) == 1) ? -1 : 1; + for (i = 0; i <= 2*m_m; i += 2) + m_d1[i] = dd*m_b1[i/2]; +} + +//------------------------------------------------------------------------------ + +// compute factors +CalcT Elliptic::findfact(int t) +{ + int i; + CalcT a = 0; + for (i = 1; i <= t; i++) + m_a1[i] /= m_a1[0]; + m_a1[0] = m_b1[0] = m_c1[0] = 1; + int i1 = 0; + for(;;) + { + if (t <= 2) + break; + CalcT p0 = 0, q0 = 0; + i1++; + for(;;) + { + m_b1[1] = m_a1[1] - p0; + m_c1[1] = m_b1[1] - p0; + for (i = 2; i <= t; i++) + m_b1[i] = m_a1[i]-p0*m_b1[i-1]-q0*m_b1[i-2]; + for (i = 2; i < t; i++) + m_c1[i] = m_b1[i]-p0*m_c1[i-1]-q0*m_c1[i-2]; + int x1 = t-1; + int x2 = t-2; + int x3 = t-3; + CalcT x4 = m_c1[x2]*m_c1[x2]+m_c1[x3]*(m_b1[x1]-m_c1[x1]); + if (x4 == 0) + x4 = 1e-3; + CalcT ddp = (m_b1[x1]*m_c1[x2]-m_b1[t]*m_c1[x3])/x4; + p0 += ddp; + CalcT dq = (m_b1[t]*m_c1[x2]-m_b1[x1]*(m_c1[x1]-m_b1[x1]))/x4; + q0 += dq; + if (abs(ddp+dq) < 1e-6) + break; + } + m_p[i1] = p0; + m_q1[i1] = q0; + m_a1[1] = m_a1[1]-p0; + t -= 2; + for (i = 2; i <= t; i++) + m_a1[i] -= p0*m_a1[i-1]+q0*m_a1[i-2]; + if (t <= 2) + break; + } + + if (t == 2) + { + i1++; + m_p[i1] = m_a1[1]; + m_q1[i1] = m_a1[2]; + } + if (t == 1) + a = -m_a1[1]; + + return a; +} + +//------------------------------------------------------------------------------ + +CalcT Elliptic::calcsn(CalcT u) +{ + CalcT sn = 0; + int j; + // q = modular constant + CalcT q = exp(-kPi*m_Kprime/m_K); + CalcT v = kPi*.5*u/m_K; + for (j = 0; ; j++) + { + CalcT w = pow(q, j+.5); + sn += w*sin((2*j+1)*v)/(1-w*w); + if (w < 1e-7) + break; + } + return sn; +} + +//------------------------------------------------------------------------------ + +CalcT Elliptic::ellipticK(CalcT k) +{ + CalcT a[50]; + CalcT theta[50]; + a[0] = atan(k/sqrt(1-k*k)); + theta[0] = kPi*.5; + int i = 0; + for(;;) + { + CalcT x = 2/(1+sin(a[i]))-1; + CalcT y = sin(a[i])*sin(theta[i]); + a[i+1] = atan(sqrt(1-x*x)/x); + theta[i+1] = .5*(theta[i]+atan(y/sqrt(1-y*y))); + CalcT e = 1-a[i+1]*2/kPi; + i++; + if (e < 1e-7) + break; + if (i == 49) + break; + } + int j; + CalcT p = 1; + for (j = 1; j <= i; j++) + p *= 1+cos(a[j]); + CalcT x = kPi*.25 + theta[i]/2; + return log(tan(x))*p; +} + +//****************************************************************************** +// +// RootFinder +// + +void RootFinder::solve( int degree, Roots *roots, bool bPolish, bool bSort ) +{ + assert( degree<=m_maxdegree ); + + const CalcT EPS=1.0e-14; + int i,its; + Complex x,b,c; + int m=degree; + // copy of coefficients + for( int j=0;j<=m;j++ ) + m_ad[j]=m_a[j]; + // for each root + roots->SetCount( degree ); + for( int j=m-1;j>=0;j-- ) + { + // initial guess at 0 + x=0.0; + laguerre( j+1, m_ad, x, its ); + if( abs(imag(x)) <= 2.0*EPS*abs(real(x)) ) + x=Complex( real(x), 0.0 ); + roots->GetNth(j)=x; + b=m_ad[j+1]; + // deflate + for (int jj=j;jj>=0;jj--) + { + c=m_ad[jj]; + m_ad[jj]=b; + b=x*b+c; + } + } + // polish + if( bPolish ) + for( int j=0;jGetNth(j), its ); + // sort + if( bSort ) + for( int j=1;jGetNth(j); + for( i=j-1;i>=0;i-- ) + { + if( real(roots->GetNth(i)) <= real(x) ) + break; + roots->GetNth(i+1)=roots->GetNth(i); + } + roots->GetNth(i+1)=x; + } +} + +//------------------------------------------------------------------------------ + +void RootFinder::laguerre( int degree, Complex a[], Complex &x, int &its) +{ + const int MR=8,MT=10,MAXIT=MT*MR; + const CalcT EPS=std::numeric_limits::epsilon(); + static const CalcT frac[MR+1]= + {0.0,0.5,0.25,0.75,0.13,0.38,0.62,0.88,1.0}; + Complex dx,x1,b,d,f,g,h,sq,gp,gm,g2; + int m=degree; + for (int iter=1;iter<=MAXIT;iter++) { + its=iter; + b=a[m]; + CalcT err=abs(b); + d=f=0.0; + CalcT abx=abs(x); + for (int j=m-1;j>=0;j--) { + f=x*f+d; + d=x*d+b; + b=x*b+a[j]; + err=abs(b)+abx*err; + } + err *= EPS; + if (abs(b) <= err) return; + g=d/b; + g2=g*g; + h=g2-2.0*f/b; + sq=sqrt(CalcT(m-1)*(CalcT(m)*h-g2)); + gp=g+sq; + gm=g-sq; + CalcT abp=abs(gp); + CalcT abm=abs(gm); + if (abp < abm) gp=gm; + dx=std::max(abp,abm) > 0.0 ? CalcT(m)/gp : std::polar(1+abx,CalcT(iter)); + x1=x-dx; + if (x == x1) return; + if (iter % MT != 0) x=x1; + else x -= frac[iter/MT]*dx; + } + + throw; +} + +//------------------------------------------------------------------------------ + +Complex RootFinder::eval( int degree, const Complex &x ) +{ + Complex y; + if( x!=0. ) + { + for( int i=0;i<=degree;i++ ) + y+=m_a[i]*pow(x,double(i)); + } + else + { + y=m_a[0]; + } + + return y; +} +//****************************************************************************** + +// returns factorial(n) (n!) +static double fact( double n ) +{ + if( n==0 ) + return 1; + else + return n*fact(n-1); +} + +// returns the k-th zero based coefficient of the reverse bessel polynomial of degree n +static double reversebessel( int k, int n ) +{ + return fact(2*n-k)/((fact(n-k)*fact(k))*pow(2.,n-k)); +} + +// returns the k-th zero based coefficient of the reverse bessel polynomial of degree n +#if 0 // unused, for now +static double bessel( int k, int n ) +{ + return fact(n+k)/((fact(n-k)*fact(k))*pow(2.,k)); +} +#endif +//------------------------------------------------------------------------------ + +void Bessel::Design( const Spec &spec ) +{ + int n=spec.order; + + CalcT k=1./sqrt((2*n-1)*log(2.)); + + RootFinderSpace<100> rf; + for( int i=0;i<=n;i++ ) + rf.coef()[i]=Complex( reversebessel( i, n ), 0 ); + rf.solve( n, &Poles() ); + + SetZeros( n ); + for( int i=0;i rf; + rf.solve( degree, ca, co ); + int count=co.size(); + + CalcT r=pow( g, 1./n ); + + SetPoles( count ); + for( int i=0;i f; + + // Set the cutoff to 18,000 Hz. + f.Setup( 18000./sampleRate ); + + // Apply the filter to the data in place using Direct Form II. + f.ProcessII( frames, stereoData ); + } + + { + // Create a two channel low-pass Biquad filter. + Dsp::BiquadLowPass<2> f; + + // Set the cutoff to 440Hz, with Q=0.25 using approximations + // to trigonometric functions for fast parameter changes. + f.SetupFast( 440./sampleRate, 0.25 ); + + // Apply the filter to the data in place using Direct Form I. + f.ProcessI( frames, stereoData ); + } + + // Deinterleave the data, process it using + // separate objects, and re-interleave it. + { + float leftChannel[frames]; + float rightChannel[frames]; + + // De-interleave the data. + deinterleave( frames, leftChannel, rightChannel, stereoData ); + + // Create two four pole pair (8 poles total) Chebyshev + // type I band-pass single channel filters. + Dsp::Cheby1BandPass<4, 1> f[2]; + + // Set the center frequency to 10,000 Hz with a width + // of 800Hz, no more than 1dB of ripple in the pass-band. + f[0].Setup( 10000.0/sampleRate, 800./sampleRate, 1 ); + f[1].Setup( 10000.0/sampleRate, 800./sampleRate, 1 ); + + // Process each channel using Direct Form II. + f[0].ProcessII( frames, leftChannel ); + f[1].ProcessII( frames, rightChannel ); + + // Re-interleave the data. + interleave( frames, stereoData, leftChannel, rightChannel ); + } + + // Apply separate filters to each channel of stereo data. + { + // Create two 2 pole pair Chebyshev type I band-stop single channel filters. + Dsp::Cheby2BandStop<2, 1> f[2]; + + // Set the center frequency to 10,000 Hz with a width + // of 800Hz, with 24dB attenuation in the stop-band. + f[0].Setup( 10000.0/sampleRate, 800./sampleRate, 24 ); + f[1].Setup( 10000.0/sampleRate, 800./sampleRate, 24 ); + + // Process each channel using Direct Form I. skip is set to 1 + // because we want Process() to jump by 1 sample after each output sample + f[0].ProcessI( frames, stereoData, 1 ); + f[1].ProcessI( frames, stereoData+1, 1 ); // right channel starts at stereoData+1 + } + + // Use one filter to process each channel of stereo data. + { + // Create a 2 pole Butterworth high-pass single channel filter + Dsp::ButterHighPass<2,1> f; + + // Set the cutoff frequency to 10,000Hz. + f.Setup( 10000.0/sampleRate ); + + // Process the left channel using Direct Form I using skip=1. + f.ProcessI( frames, stereoData, 1 ); + + // Clear the filter's history buffer to get + // it ready for the right channel. + f.Clear(); + + // Process the right channel using Direct Form I. + f.ProcessI( frames, stereoData+1, 1 ); + } + + // Use one filter to process two single channel buffers having + // different underlying types, with Direct Form I used for the first + // buffer and Direct Form II used for the second. Admittedly this + // is quite a contrived example. + { + float buf1[frames]; + double buf2[frames]; + + // Create a 1 pole Butterworth high-pass single channel filter + Dsp::ButterHighPass<1,1> f; + + // Set the cutoff frequency to 10,000Hz. + f.Setup( 10000.0/sampleRate ); + + // Process buf1 using Direct Form I. + f.ProcessI( frames, buf1 ); + + // Clear the history buffer to avoid + // contamination, since its a different input signal. + f.Clear(); + + // Process buf2 using Direct Form II + f.ProcessII( frames, buf2 ); + + // Ahhh, the miracle of templates + } + + // Plot the magnitude and phase response of a filter. + { + // Create a 1 pole Butterworth low-pass single channel filter + Dsp::ButterLowPass<1,1> f; + + // Set the cutoff to 8,000 Hz. + f.Setup( 8000./sampleRate ); + + // Plot the magnitude response at each frequency. + for( int freq=20;freq( size_t frames, float *dest, int skip ); +template void CascadeFilter::ProcessII( size_t frames, float *dest, int skip ); +#ifdef DSP_SSE3_AVAIL +template void ProcessISSEStageStereo( size_t frames, float *dest, Cascade::Stage *stage, CascadeFilter::Hist *h, int skip ); +#endif + +template void CascadeFilter::ProcessI( size_t frames, double *dest, int skip ); +template void CascadeFilter::ProcessII( size_t frames, double *dest, int skip ); +#ifdef DSP_SSE3_AVAIL +template void ProcessISSEStageStereo( size_t frames, double *dest, Cascade::Stage *stage, CascadeFilter::Hist *h, int skip ); +#endif + +//****************************************************************************** +/* + BIBLIOGRAPHY + +"Chebyshev PoleFilterSpace Properties" +http://cnx.org/content/m16906/latest/ + +"High-Order Digital Parametric Equalizer Design" +Sophocles J. Orfanidis +http://www.ece.rutgers.edu/~orfanidi/ece521/hpeq.pdf + +*/ +//****************************************************************************** +/* + +To do: + +- rewrite of filter specifications +- documentation of filter specifications +- bibliography and references for each formula +- optimize prototypes +- fix chebyI and chebyII shelf formulas for ripple specification + +Changes: + +- Added RootFinder, for finding complex roots of polynomials +- Added Bessel low pass analog prototype +- Added BesselLowPass, BesselHighPass, BesselBandPass, and BesselBandStop filters +*/ diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/falco/DspFilter.h b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/falco/DspFilter.h new file mode 100644 index 00000000..10f266f9 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/falco/DspFilter.h @@ -0,0 +1,1879 @@ +/******************************************************************************* + +"A Collection of Useful C++ Classes for Digital Signal Processing" +By Vincent Falco + +Official project location: +http://code.google.com/p/dspfilterscpp/ + +See DspFilter.cpp for notes and bibliography. + +-------------------------------------------------------------------------------- + +License: MIT License (http://www.opensource.org/licenses/mit-license.php) +Copyright (c) 2009 by Vincent Falco + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*******************************************************************************/ + +/** + * Portions modified by SquinkyLabs, 2010-2018. + * Added ability to generate biquad pairs. + * Fixed bugs + * Added hilbert filter designer + */ + +#ifndef __DSP_FILTER__ +#define __DSP_FILTER__ + + +#include +#include +#include + +#include +#include +#include + +#include +//#define _LOG + + +#ifdef _MSC_VER +/* MSVC generates a flood of warnings about unreferenced functions */ +//#pragma warning(disable:4505) +#else + #include + #include +#endif +//#include "DebugUtil.h" +namespace Dsp +{ + //-------------------------------------------------------------------------- + + // Compile-time assert to keep us sane + + #define DSP_JOIN(x, y) DSP_JOIN_AGAIN(x, y) + #define DSP_JOIN_AGAIN(x, y) x ## y + + #define DSP_STATIC_ASSERT(e, msg) \ + typedef char DSP_JOIN(assertion_ ## msg ## _failed_at_line_, __LINE__) [(e) ? 1 : -1] + + template + struct is_same { enum { value = false }; }; + template + struct is_same { enum { value = true }; }; + + //-------------------------------------------------------------------------- + // + // Configuration + // + //-------------------------------------------------------------------------- + + // Regardless of the type of sample that the filter operates on (e.g. + // float or double), all calculations are performed using double (or + // better) for stability and accuracy. This controls the underlying + // type used for calculations: + typedef double CalcT; + + typedef int Int32; // Must be 32 bits + + // Must be 64 bits +#ifdef _MSC_VER + typedef __int64 Int64; +#else + typedef int64_t Int64; +#endif + + // This is used to prevent denormalization. + const CalcT vsa=1.0 / 4294967295.0; // for CalcT as float + + // These constants are so important, I made my own copy. If you improve + // the resolution of CalcT be sure to add more significant digits to these. + const CalcT kPi =3.1415926535897932384626433832795028841971; + const CalcT kPi_2 =1.5707963267948966192313216916397514420986; + const CalcT kLn2 =0.693147180559945309417; + const CalcT kLn10 =2.30258509299404568402; + + //-------------------------------------------------------------------------- + + template + inline Ty asinh( Ty x ) + { + return log( x+::std::sqrt(x*x+1) ); + } + + template + inline Ty acosh( Ty x ) + { + return log( x+::std::sqrt(x*x-1) ); + } + + //-------------------------------------------------------------------------- + // + // Complex + // + //-------------------------------------------------------------------------- + + template + inline std::complex addmul( const std::complex &c, Ty v, const std::complex &c1 ) + { + return std::complex( c.real()+v*c1.real(), c.imag()+v*c1.imag() ); + } + + template + inline std::complex recip( const std::complex &c ) + { + Ty n=1.0/std::norm(c); + return std::complex( n*c.real(), n*c.imag() ); + } + + typedef std::complex Complex; + + //-------------------------------------------------------------------------- + // + // Numerical Analysis + // + //-------------------------------------------------------------------------- + + // Container for a set of complex polynomial roots. + // Storage is provided by a derived class. + struct Roots + { + // Get the number of roots available. + int GetCount( void ) const + { + return m_rootCount; + } + + // Set the number of roots available up to max. + void SetCount( int n ) + { + assert( n>=1 && n<=m_rootMax ); + m_rootCount=n; + } + + // Retrieve zero-based i-th root. + Complex &GetNth( int i ) + { + assert( i>=0 && i=0 && i + struct RootsSpace : Roots + { + RootsSpace() + { + m_rootMax=sizeof(m_roots)/sizeof(m_roots[0]); + m_root=m_roots; + } + + private: + Complex m_roots[maxdegree]; + }; + + //-------------------------------------------------------------------------- + + // Finds the complex roots of the given polynomial with + // complex-valued coefficients using a numerical method. + struct RootFinder + { + // Find roots of polynomial f(x)=a[0]+a[1]*x+a[2]*x^2...+a[degree]*x^degree + // The input coefficients are set using coef()[]. + // The solutions are placed in roots. + void solve( int degree, Roots *roots, bool bPolish=false, bool bSort=false ); + + // Evaluates the polynomial at x + Complex eval( int degree, const Complex &x ); + + // Direct access to the input coefficient array of size degree+1. + Complex *coef( void ) + { + return m_a; + } + + private: + // Improves x as a root using Laguerre's method. + // The input coefficient array has degree+1 elements. + void laguerre( int degree, Complex a[], Complex &x, int &its ); + + protected: + int m_maxdegree; + Complex *m_a; // input coefficients (m_maxdegree+1 elements) + Complex *m_ad; // copy of deflating coefficients + }; + + //------------------------------------------------------------------------------ + + template + struct RootFinderSpace : virtual RootFinder + { + RootFinderSpace() + { + m_maxdegree=maxdegree; + m_a=m_a0; + m_ad=m_ad0; + } + + private: + Complex m_a0[maxdegree+1]; + Complex m_ad0[maxdegree+1]; + }; + + //-------------------------------------------------------------------------- + // + // Utility Classes + // + //-------------------------------------------------------------------------- + + // Tracks the peaks in the signal stream using the attack and release parameters + template + class EnvelopeFollower + { + public: + EnvelopeFollower(); + + void Setup( double attackMs, double releaseMs, int sampleRate ); + + template + void Process( size_t count, const Ty *src , int skip=0 ); + + CalcT Envelope( void ) const; + + CalcT env[channels]; + + protected: + CalcT a; + CalcT r; + }; + + //-------------------------------------------------------------------------- + + template + EnvelopeFollower::EnvelopeFollower() + { + for( int i=0;i + CalcT EnvelopeFollower::Envelope( void ) const + { + return env[0]; + } + + template + void EnvelopeFollower::Setup( double attackMs, double releaseMs, int sampleRate ) + { + a = pow( 0.01, 1.0 / ( attackMs * sampleRate * 0.001 ) ); + r = pow( 0.01, 1.0 / ( releaseMs * sampleRate * 0.001 ) ); + } + + template + template + void EnvelopeFollower::Process( size_t count, const Ty *src, int skip ) + { + skip=channels+skip; + for( int i=0;ie ) e = a * ( e - v ) + v; + else e = r * ( e - v ) + v; + s+=skip; + } + env[i]=e; + } + } + + //-------------------------------------------------------------------------- + + // Uses the envelope follower to scale the audio signal into range + // and prevent clipping. If audio signal is already between 0..1, does + // nothing. IsClipping() will return true if the last call to Process() + // contained source data that would have otherwise caused clipping. + // + // For musical purposes attack=10ms and release=500ms seems to work well. + // + struct AutoLimiter + { + void Setup( double attackMs, double releaseMs, int sampleRate ); + + bool IsClipping( void ) const; + + template + void Process( size_t nSamples, Ty *dest ); + + private: + EnvelopeFollower<1> e; + }; + + //-------------------------------------------------------------------------- + + inline void AutoLimiter::Setup( double attackMs, double releaseMs, int sampleRate ) + { + e.Setup( attackMs, releaseMs, sampleRate ); + } + + inline bool AutoLimiter::IsClipping( void ) const + { + return e.Envelope()>1; + } + + template + void AutoLimiter::Process( size_t count, Ty *dest ) + { + while( count-- ) + { + Ty v=*dest; + // don't worry, this should get optimized + e.Process( 1, &v, skip ); + if( e.Envelope()>1 ) + *dest=Ty(*dest/e.Envelope()); + dest+=skip; + } + } + + //-------------------------------------------------------------------------- + // + // Utility Functions + // + // These may prove useful. Since they support multiple channels + // code can measure everything uniformly in terms of number of frames. + // + //-------------------------------------------------------------------------- + + // Fill a sample buffer with zeroes + template + void zero( int channels, size_t frames, Ty *dest, int destSkip=0 ) + { + if( destSkip==0 ) + { + ::memset( dest, 0, channels*frames*sizeof(Ty) ); + } + else + { + while( frames-- ) + { + int n=channels; + while( n-- ) + { + *dest++=0; + } + dest+=destSkip; + } + } + } + + //-------------------------------------------------------------------------- + + // Copy interleaved samples from src to dest (may not overlap). + // skip specifies the number of samples in between interleaved frames. + + // This will perform a conversion between numerical types + template + void copy( int channels, size_t frames, Td *dest, const Ts *src, int destSkip=0, int srcSkip=0 ) + { + while( frames-- ) + { + int n=channels; + while( n-- ) + { + *dest++=*src++; + } + dest+=destSkip; + src+=srcSkip; + } + } + + // Faster version of copy when the source and destination types are the same + template + void copy( int channels, size_t frames, Ty *dest, const Ty *src, int destSkip=0, int srcSkip=0 ) + { + if( destSkip==0 && srcSkip==0 ) + { + ::memcpy( dest, src, channels * frames * sizeof(src[0]) ); + } + else + { + copy( channels, frames, dest, src, destSkip, srcSkip ); + } + } + + //-------------------------------------------------------------------------- + + // Add each sample in src to dest. Does not perform clipping or overflow testing. + template + void mix( int channels, size_t frames, Td *dest, const Ts *src, int destSkip=0, int srcSkip=0 ) + { + while( frames-- ) + { + int n=channels; + while( n-- ) + { + *dest+++=Td(*src++); + } + dest+=destSkip; + src+=srcSkip; + } + } + + //-------------------------------------------------------------------------- + + // Multiply each sample by mult. Does not perform clipping or overflow testing. + template + void scale( int channels, size_t frames, Td *dest, Ty mult, int destSkip=0 ) + { + while( frames-- ) + { + int n=channels; + while( n-- ) + { + *dest++=Td(*dest*mult); + } + dest+=destSkip; + } + } + + //-------------------------------------------------------------------------- + + // Half-wave rectify + template + void rectifyhalf( int channels, size_t frames, Ty *dest, int skip=0 ) + { + while( frames-- ) + { + int n=channels; + while( n-- ) + { + Ty v=*dest; + if( v<0 ) + v=0; + *dest++=v; + } + dest+=skip; + } + } + + //-------------------------------------------------------------------------- + + // Full-wave rectify + template + void rectifyfull( int channels, size_t frames, Ty *dest, int skip=0 ) + { + while( frames-- ) + { + int n=channels; + while( n-- ) + { + *dest++=std::abs(*dest); + } + dest+=skip; + } + } + + //-------------------------------------------------------------------------- + + // Calculate n-order difference + template + void derivative( /*int order,*/ int channels, size_t frames, Ty *dest, int destSkip=0 ) + { + assert( frames>0 ); + + int prev=-(channels+destSkip); + destSkip=destSkip+2*prev; + + Ty *dest0=dest; + + frames--; + dest=dest+frames*(channels+destSkip); + + if( frames>1 ) + { + while( frames-- ) + { + int n=channels; + while( n-- ) + { + *dest++=*dest-dest[prev]; + } + dest=dest+destSkip; + } + } + + int n=channels; + while( n-- ) + { + *dest++=0; + } + } + + //-------------------------------------------------------------------------- + + /* + template + void convolve( int channels, size_t frames, Ty *dest, int kernelSize, const Ty *kernel, int destSkip=0 ) + { + assert( frames>kernelSize ); + + const Ty *kernel0=kernel; + + frames-=kernelSize; + while( frames-- ) + { + int n=channels; + while( n-- ) + { + kernel=kernel0; + } + } + } + */ + + //-------------------------------------------------------------------------- + + // Interleave separate channels from source pointers to destination + // (Destination requires channels*frames samples of storage) + template + void interleave( int channels, size_t frames, Td *dest, const Ts *src[] ) + { + assert( channels!=1 ); + + switch( channels ) + { + case 2: + { + // unroll further if desired + const Ts *l=src[0]; + const Ts *r=src[1]; + switch( frames%4 ) + { + case 3: *dest++=*l++; *dest++=*r++;; + case 2: *dest++=*l++; *dest++=*r++;; + case 1: *dest++=*l++; *dest++=*r++;; + }; + frames/=4; + while( frames-- ) + { + *dest++=*l++; *dest++=*r++; + *dest++=*l++; *dest++=*r++; + *dest++=*l++; *dest++=*r++; + *dest++=*l++; *dest++=*r++; + } + } + break; + + default: + { + for( int i=0;i + void interleave( size_t frames, Td *dest, const Ts *left, const Ts *right ) + { + const Ts *src[2]; + src[0]=left; + src[1]=right; + interleave( 2, frames, dest, src ); + } + + //-------------------------------------------------------------------------- + + // Deinterleave channels from interleaved data to separate pointers. + template + void deinterleave( int channels, size_t frames, Td *dest[], const Ts *src ) + { + assert( channels!=1 ); + + switch( channels ) + { + case 2: + { + // unroll further if desired + Td *l=dest[0]; + Td *r=dest[1]; + switch( frames%4 ) + { + case 3: *l++=*src++; *r++=*src++; + case 2: *l++=*src++; *r++=*src++; + case 1: *l++=*src++; *r++=*src++; + }; + frames/=4; + while( frames-- ) + { + *l++=*src++; *r++=*src++; + *l++=*src++; *r++=*src++; + *l++=*src++; *r++=*src++; + *l++=*src++; *r++=*src++; + } + } + break; + + default: + { + for( int i=0;i + void deinterleave( size_t frames, Td *left, Td *right, const Ts *src ) + { + Td *dest[2]; + dest[0]=left; + dest[1]=right; + deinterleave( 2, frames, dest, src ); + } + + //-------------------------------------------------------------------------- + /* + Units + + w Angular frequency in radians per sample. 0..pi + + */ + //-------------------------------------------------------------------------- + + // Common structure for all filter specifications. + struct Spec + { + int order; // PoleFilterSpace order, >=1 + CalcT sampleRate; // Sample rate in Hz + CalcT cutoffFreq; // Cutoff frequency in Hz + CalcT passRippleDb; // Passband ripple in Db + CalcT stopBandDb; // Minimum stopband attenuation in Db + //CalcT cornerFreq1; // left corner frequency in Hz + //CalcT cornerFreq2; // right corner frequency in Hz + CalcT centerFreq; + CalcT normWidth; + CalcT gainDb; // gain or cut in Db + CalcT rollOff; // for elliptics + + Spec( void ) + { + // This is used as a flag to tell us + // that the structure is uninitialized. + order=0; + } + }; + + //-------------------------------------------------------------------------- + + // Information required to normalize the magnitude response of a filter. + // The Cascade determines the actual magnitude response at w, and applies a + // scale factor to the coefficients to achieve the specified target gain at w. + struct Normalization + { + CalcT w; // angular frequency + CalcT gain; // target gain + }; + + //-------------------------------------------------------------------------- + + // Representation of an Infinite Impulse Response filter modeled + // as a series of coefficients of second order sections. Derived + // classes provide storage for the coefficients. + struct Cascade + { + struct Stage; + + // Original source had no virtual destructor. + // I tried to put one in years ago, and had crashes. + // Seems ok now. + virtual ~Cascade() + { + } + + + // Initializes some important fields. + Cascade(); + + // Return the number of active stages. + int GetStageCount( void ); + + // Set the number of active stages up to max. + void SetStageCount( int n ); + + // Direct access to the stage array. + Stage *Stages( void ); + + // Convenience function for Biquads. + void SetStage1( CalcT a1, CalcT a2, CalcT b0, CalcT b1, CalcT b2 ); + + // Reset coefficients in preparation for realization. + void Reset( void ); + + // Add output scale factor to the cascade. + void Scale( CalcT factor ); + + // Determine response at angular frequency. + // Note that this is the only(?) funciton in the library that works 0.. 2 pi instead of 0..1 + Complex Response_radian( CalcT w ) const; + + // Determine resonse at normalized freq + Complex Response_normalized( CalcT f) const { return Response_radian(f * AudioMath::Pi * 2); } + + // Functor for finding the local + // maximum of the response magnitude. + struct ResponseFunctor + { + ResponseFunctor( Cascade *cascade ); + CalcT operator()( CalcT w ); + + private: + Cascade *m_cascade; + }; + + // The coefficients of one second-order-section. + struct Stage + { + // Reset coefficients. + void Reset( void ) + { + a[1]=0; a[2]=0; b[0]=1; b[1]=0; b[2]=0; + } + + CalcT a[3]; + CalcT b[3]; + }; + + protected: + int m_stageCount; + int m_stageMax; + Stage * m_stage; + }; + + //-------------------------------------------------------------------------- + + // Storage for Cascade stages. + template + struct CascadeSpace : virtual Cascade + { + CascadeSpace() + { + m_stageMax=sizeof(m_stages)/sizeof(m_stages[0]); + m_stage=m_stages; + } + + private: + // Each stage is order 2. + Stage m_stages[(maxorder+1)/2]; + }; + + //-------------------------------------------------------------------------- + + // Adds the ability to process sample data to a Cascade. + // Storage for each channel's history information is provided + // by derived classes. + struct CascadeFilter : virtual Cascade + { + // Clear the history buffer. Used on initialization, + // and should also be used if the audio source is changed + // in between filtering. + void Clear( void ); + + // Process data in place using Direct Form I + // skip is added after each frame. + // Direct Form I is more suitable when the filter parameters + // are changed often. However, it is slightly slower. + template + void ProcessI( size_t frames, Ty *dest, int skip=0 ); + + // Process data in place using Direct Form II + // skip is added after each frame. + // Direct Form II is slightly faster than Direct Form I, + // but changing filter parameters on stream can result + // in discontinuities in the output. It is best suited + // for a filter whose parameters are set only once. + template + void ProcessII( size_t frames, Ty *dest, int skip=0 ); + + // Convenience function that just calls ProcessI. + // Feel free to change the implementation. + template + void Process( size_t frames, Ty *dest, int skip=0 ) + { + ProcessI( frames, dest, skip ); + } + + // History information for one channel of one stage. + struct Hist + { + CalcT v[4]; + }; + + protected: + int m_nchan; + Hist * m_histp; + }; + + //-------------------------------------------------------------------------- + // + // Biquad Second Order IIR Filters + // + //-------------------------------------------------------------------------- + + // Biquad with stage storage. + struct Biquad : CascadeSpace<1> + { + Biquad(); + }; + + //-------------------------------------------------------------------------- + + // Low pass + struct BiquadLp : Biquad + { + void Setup ( CalcT normFreq, CalcT q ); + void SetupFast ( CalcT normFreq, CalcT q ); + protected: + void SetupCommon ( CalcT sn, CalcT cs, CalcT q ); + }; + + //-------------------------------------------------------------------------- + + // High pass + struct BiquadHp : Biquad + { + public: + void Setup ( CalcT normFreq, CalcT q ); + void SetupFast ( CalcT normFreq, CalcT q ); + protected: + void SetupCommon ( CalcT sn, CalcT cs, CalcT q ); + }; + + //-------------------------------------------------------------------------- + + // Band pass 1 + // Constant skirt gain, peak gain=Q + struct BiquadBp1 : Biquad + { + void Setup ( CalcT normFreq, CalcT q ); + void SetupFast ( CalcT normFreq, CalcT q ); + protected: + void SetupCommon ( CalcT sn, CalcT cs, CalcT q ); + }; + + //-------------------------------------------------------------------------- + + // Band pass 2 + // Constant 0dB peak gain + struct BiquadBp2 : Biquad + { + void Setup ( CalcT normFreq, CalcT q ); + void SetupFast ( CalcT normFreq, CalcT q ); + protected: + void SetupCommon ( CalcT sn, CalcT cs, CalcT q ); + }; + + //-------------------------------------------------------------------------- + + // Band stop + struct BiquadBs : Biquad + { + void Setup ( CalcT normFreq, CalcT q ); + void SetupFast ( CalcT normFreq, CalcT q ); + protected: + void SetupCommon ( CalcT sn, CalcT cs, CalcT q ); + }; + + //-------------------------------------------------------------------------- + + // All pass + struct BiquadAp : Biquad + { + void Setup ( CalcT normFreq, CalcT q ); + void SetupFast ( CalcT normFreq, CalcT q ); + protected: + void SetupCommon ( CalcT sn, CalcT cs, CalcT q ); + }; + + //-------------------------------------------------------------------------- + + // Low shelf + struct BiquadLs : Biquad + { + void Setup ( CalcT normFreq, CalcT dB, CalcT shelfSlope=1.0 ); + void SetupFast ( CalcT normFreq, CalcT dB, CalcT shelfSlope=1.0 ); + protected: + void SetupCommon ( CalcT cs, CalcT A, CalcT sa ); + }; + + //-------------------------------------------------------------------------- + + // High shelf + struct BiquadHs : Biquad + { + void Setup ( CalcT normFreq, CalcT dB, CalcT shelfSlope=1.0 ); + void SetupFast ( CalcT normFreq, CalcT dB, CalcT shelfSlope=1.0 ); + protected: + void SetupCommon ( CalcT cs, CalcT A, CalcT sa ); + }; + + //-------------------------------------------------------------------------- + + // Peak/notch + struct BiquadEq : Biquad + { + void Setup ( CalcT normFreq, CalcT dB, CalcT bandWidth ); + void SetupFast ( CalcT normFreq, CalcT dB, CalcT bandWidth ); + protected: + void SetupCommon ( CalcT sn, CalcT cs, CalcT alph, CalcT A ); + }; + + //-------------------------------------------------------------------------- + + // Biquad filter. + template + struct BiquadFilter : CascadeFilter + { + BiquadFilter() + { + m_nchan=channels; + m_histp=m_hist; + memset( m_hist, 0, sizeof(m_hist) ); + } + + private: + Hist m_hist[channels]; + }; + + //-------------------------------------------------------------------------- + + template + struct BiquadLowPass : BiquadLp, BiquadFilter + { + }; + + template + struct BiquadHighPass : BiquadHp, BiquadFilter + { + }; + + template + struct BiquadBandPass1 : BiquadBp1, BiquadFilter + { + }; + + template + struct BiquadBandPass2 : BiquadBp2, BiquadFilter + { + }; + + template + struct BiquadBandStop : BiquadBs, BiquadFilter + { + }; + + template + struct BiquadAllPass : BiquadAp, BiquadFilter + { + }; + + template + struct BiquadLowShelf : BiquadLs, BiquadFilter + { + }; + + template + struct BiquadHighShelf: BiquadHs, BiquadFilter + { + }; + + template + struct BiquadPeak: BiquadEq, BiquadFilter + { + }; + + //-------------------------------------------------------------------------- + // + // Pole Filters + // + //-------------------------------------------------------------------------- + + extern const Complex infinity; + + //-------------------------------------------------------------------------- + + // Representation of a filter as a set of poles and + // zeroes, corresponding to complex-valued roots of + // a rational transfer function. Storage is provided + // by a derived class. + struct Layout + { + // Return the number of available poles. + int CountPoles( void ) const + { + return m_pole->GetCount(); + } + + // Return the number of available zeros. + int CountZeros( void ) const + { + return m_zero->GetCount(); + } + + // Set the number of available poles up to max. + void SetPoles( int n ) + { + m_pole->SetCount( n ); + } + + // Set the number of available zeros up to max. + void SetZeros( int n ) + { + m_zero->SetCount( n ); + } + + // Retrieve the zero-based i-th pole. + Complex &Pole( int i ) + { + return m_pole->GetNth(i); + } + + // Retrieve the zero-based i-th zero. + Complex &Zero( int i ) + { + return m_zero->GetNth(i); + } + + // Direct access to the set of all poles. + Roots &Poles( void ) + { + return *m_pole; + } + + const Roots &Poles( void ) const + { + return *m_pole; + } + + // Direct access to the set of all zeros. + Roots &Zeros( void ) + { + return *m_zero; + } + + const Roots &Zeros( void ) const + { + return *m_zero; + } + + // Access normalization parameters. + Normalization &GetNormalization( void ) + { + return m_normal; + } + + const Normalization &GetNormalization( void ) const + { + return m_normal; + } + + // Build a Cascade from poles and zeroes. + void Realize( Cascade *cascade ); + void Realize_orig( Cascade *cascade ); + void Realize_custom( Cascade *cascade ); + void pairRoots(); + + private: + // Helpers for Realize(). + void BuildA( Cascade *cascade, CalcT x1, CalcT x2, int *na ); + void BuildB( Cascade *cascade, CalcT x0, CalcT x1, CalcT x2, int *nb ); + + protected: + Roots *m_pole; // The pole roots. + Roots *m_zero; // The zero roots. + Normalization m_normal; + }; + + //-------------------------------------------------------------------------- + + // Storage for a Layout. + template + struct LayoutSpace : virtual Layout + { + LayoutSpace() + { + m_pole=&m_poles; + m_zero=&m_zeros; + }; + + private: + RootsSpace m_poles; + RootsSpace m_zeros; + }; + + //-------------------------------------------------------------------------- + + // An abstract analog to digital transformation. This converts the + // layout of the analog prototype into a digital layout based on + // the type of transformation (low pass, high pass, band pass, band stop). + struct Transformation + { + }; + + // Low pass to low pass. + struct LowPass : Transformation + { + void Transform( const Spec &spec, Layout *result, const Layout &layout ); + protected: + void Transform( const Spec &spec, Roots *result, const Roots &roots ); + }; + + // Low pass to high pass. + struct HighPass : Transformation + { + void Transform( const Spec &spec, Layout *result, const Layout &layout ); + protected: + void Transform( const Spec &spec, Roots *result, const Roots &roots ); + }; + + // Low pass to band pass. + // The number of poles and zeroes is doubled. + struct BandPass : Transformation + { + void Transform( const Spec &spec, Layout *result, const Layout &layout ); + protected: + void Transform( const Spec &spec, Roots *result, const Roots &roots ); + Complex BandPassTransform( int i, const Complex &c ); + CalcT m_wc; + CalcT m_wc2; + }; + + // Low pass to band stop. + // The number of poles and zeroes is doubled. + struct BandStop : Transformation + { + void Transform( const Spec &spec, Layout *result, const Layout &layout ); + void Transform( const Spec &spec, Roots *result, const Roots &roots ); + void DesignZeros( const Spec &spec, Layout *layout ); + protected: + Complex BandStopTransform( int i, const Complex &c ); + CalcT m_wc; + CalcT m_wc2; + }; + + //-------------------------------------------------------------------------- + + // Abstract analog filter prototype. The filter is designed with fixed + // specifications and then transformed to the desired response. + // The layout is cached for fast parameter changes. + struct Prototype : virtual Layout + { + }; + + //-------------------------------------------------------------------------- + + // Abstract digital pole filter base + struct PoleFilter : CascadeFilter, virtual Layout + { + virtual void Setup( const Spec &spec )=0; + }; + + //-------------------------------------------------------------------------- + + // Component aggregate for a cascade filter that provides storage + // for coefficients, history buffer, and processing capabilities. + template + struct PoleFilterSpace : PoleFilter, LayoutSpace, CascadeSpace + { + PoleFilterSpace() + { + m_nchan=channels; + m_histp=m_hist; + memset( m_hist, 0, sizeof(m_hist) ); + } + + void Setup( const Spec &spec ) override + { + m_proto.Design( spec ); + m_trans.Transform( spec, this, m_proto ); + Realize( this ); + } + + private: + template + struct PrototypeSpace : Base, LayoutSpace + { + }; + + PrototypeSpace m_proto; + Trans m_trans; + Hist m_hist[channels*((maxorder+1)/2)]; + }; + + //-------------------------------------------------------------------------- + // + // Butterworth + // + //-------------------------------------------------------------------------- + + // Low pass prototype + struct Butter : Prototype + { + void Design( const Spec &spec ); + }; + + // Low shelf prototype + struct ButterShelf : Prototype + { + void Design( const Spec &spec ); + }; + + //-------------------------------------------------------------------------- + + template + struct ButterLowPass : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ButterHighPass : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ButterBandPass : PoleFilterSpace + { + void SetupAs( CalcT centerFreq, CalcT normWidth ) + { + Spec spec; + spec.order=order; + spec.centerFreq=centerFreq; + spec.normWidth=normWidth; + spec.sampleRate=1; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ButterBandStop : PoleFilterSpace + { + void SetupAs( CalcT centerFreq, CalcT normWidth ) + { + Spec spec; + spec.order=order; + spec.centerFreq=centerFreq; + spec.normWidth=normWidth; + spec.sampleRate=1; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ButterLowShelf : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq, CalcT gainDb ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + spec.gainDb=gainDb; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ButterHighShelf : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq, CalcT gainDb ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + spec.gainDb=gainDb; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ButterEq : PoleFilterSpace + { + void SetupAs( CalcT centerFreq, CalcT normWidth, CalcT gainDb ) + { + Spec spec; + spec.order=order; + spec.centerFreq=centerFreq; + spec.normWidth=normWidth; + spec.sampleRate=1; + spec.gainDb=gainDb; + PoleFilterSpace::Setup( spec ); + } + }; + + //-------------------------------------------------------------------------- + // + // Chebyshev Type I + // + //-------------------------------------------------------------------------- + + // Low pass prototype + struct ChebyI : Prototype + { + void Design( const Spec &spec ); + }; + + // Low shelf prototype + struct ChebyIShelf : Prototype + { + void Design( const Spec &spec ); + }; + + //-------------------------------------------------------------------------- + + template + struct ChebyILowPass : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq, CalcT rippleDb ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + spec.passRippleDb=rippleDb; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ChebyIHighPass : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq, CalcT rippleDb ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + spec.passRippleDb=rippleDb; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ChebyIBandPass : PoleFilterSpace + { + void SetupAs( CalcT centerFreq, CalcT normWidth, CalcT rippleDb ) + { + Spec spec; + spec.order=order; + spec.centerFreq=centerFreq; + spec.normWidth=normWidth; + spec.sampleRate=1; + spec.passRippleDb=rippleDb; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ChebyIBandStop : PoleFilterSpace + { + void SetupAs( CalcT centerFreq, CalcT normWidth, CalcT rippleDb ) + { + Spec spec; + spec.order=order; + spec.centerFreq=centerFreq; + spec.normWidth=normWidth; + spec.sampleRate=1; + spec.passRippleDb=rippleDb; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ChebyILowShelf : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq, CalcT gainDb, CalcT rippleDb ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + spec.passRippleDb=rippleDb; + spec.gainDb=gainDb; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ChebyIHighShelf : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq, CalcT gainDb, CalcT rippleDb ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + spec.passRippleDb=rippleDb; + spec.gainDb=gainDb; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ChebyIEq : PoleFilterSpace + { + void SetupAs( CalcT centerFreq, CalcT normWidth, CalcT gainDb, CalcT rippleDb ) + { + Spec spec; + spec.order=order; + spec.centerFreq=centerFreq; + spec.normWidth=normWidth; + spec.sampleRate=1; + spec.passRippleDb=rippleDb; + spec.gainDb=gainDb; + PoleFilterSpace::Setup( spec ); + } + }; + + //-------------------------------------------------------------------------- + // + // Chebyshev Type II + // + //-------------------------------------------------------------------------- + + // Low pass prototype + struct ChebyII : Prototype + { + void Design( const Spec &spec ); + }; + + // Low shelf prototype + struct ChebyIIShelf : Prototype + { + void Design( const Spec &spec ); + }; + + //-------------------------------------------------------------------------- + + template + struct ChebyIILowPass : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq, CalcT stopBandDb ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + spec.stopBandDb=stopBandDb; + PoleFilterSpace::Setup( spec ); + } + }; + + //-------------------------------------------------------------------------- + + template + struct ChebyIIHighPass : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq, CalcT stopBandDb ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + spec.stopBandDb=stopBandDb; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ChebyIIBandPass : PoleFilterSpace + { + void SetupAs( CalcT centerFreq, CalcT normWidth, CalcT stopBandDb ) + { + Spec spec; + spec.order=order; + spec.centerFreq=centerFreq; + spec.normWidth=normWidth; + spec.sampleRate=1; + spec.stopBandDb=stopBandDb; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ChebyIIBandStop : PoleFilterSpace + { + void SetupAs( CalcT centerFreq, CalcT normWidth, CalcT stopBandDb ) + { + Spec spec; + spec.order=order; + spec.centerFreq=centerFreq; + spec.normWidth=normWidth; + spec.sampleRate=1; + spec.stopBandDb=stopBandDb; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ChebyIILowShelf : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq, CalcT gainDb, CalcT rippleDb ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + spec.passRippleDb=rippleDb; + spec.gainDb=gainDb; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ChebyIIHighShelf : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq, CalcT gainDb, CalcT rippleDb ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + spec.passRippleDb=rippleDb; + spec.gainDb=gainDb; + PoleFilterSpace::Setup( spec ); + } + }; + + template + struct ChebyIIEq : PoleFilterSpace + { + void SetupAs( CalcT centerFreq, CalcT normWidth, CalcT gainDb, CalcT rippleDb ) + { + Spec spec; + spec.order=order; + spec.centerFreq=centerFreq; + spec.normWidth=normWidth; + spec.sampleRate=1; + spec.passRippleDb=rippleDb; + spec.gainDb=gainDb; + PoleFilterSpace::Setup( spec ); + } + }; + + //-------------------------------------------------------------------------- + // + // Elliptic + // + //-------------------------------------------------------------------------- + + // Low pass prototype + struct Elliptic : Prototype + { + void Design( const Spec &spec ); + + protected: + void prodpoly ( int sn ); + void calcfz2 ( int i ); + void calcfz ( void ); + void calcqz ( void ); + CalcT findfact ( int t ); + CalcT calcsn ( CalcT u ); + CalcT ellipticK ( CalcT k ); + + protected: + template + struct CalcArray + { + CalcT &operator[](size_t index) + { + assert( index>=0 && index m_zeros; + CalcArray<100> m_c1; + CalcArray<100> m_b1; + CalcArray<100> m_a1; + CalcArray<100> m_d1; + CalcArray<100> m_q1; + CalcArray<100> m_z1; + CalcArray<100> m_f1; + CalcArray<100> m_s1; + CalcArray<100> m_p ; + CalcArray<100> m_zw1; + CalcArray<100> m_zf1; + CalcArray<100> m_zq1; + CalcArray<100> m_rootR; + CalcArray<100> m_rootI; + CalcT m_e; + int m_nin; + int m_m; + int m_n2; + int m_em; + }; + + //-------------------------------------------------------------------------- + + template + struct EllipticLowPass : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq, CalcT passRippleDb, CalcT rollOff ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + spec.passRippleDb=passRippleDb; + spec.rollOff=rollOff; + PoleFilterSpace::Setup( spec ); + } + }; + + //-------------------------------------------------------------------------- + + template + struct EllipticHighPass : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq, CalcT passRippleDb, CalcT rollOff ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + spec.passRippleDb=passRippleDb; + spec.rollOff=rollOff; + PoleFilterSpace::Setup( spec ); + } + }; + + //-------------------------------------------------------------------------- + + template + struct EllipticBandPass : PoleFilterSpace + { + void SetupAs( CalcT centerFreq, CalcT normWidth, CalcT passRippleDb, CalcT rollOff ) + { + Spec spec; + spec.order=order; + spec.centerFreq=centerFreq; + spec.normWidth=normWidth; + spec.sampleRate=1; + spec.passRippleDb=passRippleDb; + spec.rollOff=rollOff; + PoleFilterSpace::Setup( spec ); + } + }; + + //-------------------------------------------------------------------------- + + template + struct EllipticBandStop : PoleFilterSpace + { + void SetupAs( CalcT centerFreq, CalcT normWidth, CalcT passRippleDb, CalcT rollOff ) + { + Spec spec; + spec.order=order; + spec.centerFreq=centerFreq; + spec.normWidth=normWidth; + spec.sampleRate=1; + spec.passRippleDb=passRippleDb; + spec.rollOff=rollOff; + PoleFilterSpace::Setup( spec ); + } + }; + + //-------------------------------------------------------------------------- + // + // Bessel + // + //-------------------------------------------------------------------------- + + // Low pass prototype + struct Bessel : Prototype + { + void Design( const Spec &spec ); + }; + + // Low shelf prototype + struct BesselShelf : Prototype + { + void Design( const Spec &spec ); + }; + + //-------------------------------------------------------------------------- + + template + struct BesselLowPass : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + PoleFilterSpace::Setup( spec ); + } + }; + + //-------------------------------------------------------------------------- + + template + struct BesselHighPass : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + PoleFilterSpace::Setup( spec ); + } + }; + + //-------------------------------------------------------------------------- + + template + struct BesselBandPass : PoleFilterSpace + { + void SetupAs( CalcT centerFreq, CalcT normWidth ) + { + Spec spec; + spec.order=order; + spec.centerFreq=centerFreq; + spec.normWidth=normWidth; + spec.sampleRate=1; + PoleFilterSpace::Setup( spec ); + } + }; + + //-------------------------------------------------------------------------- + + template + struct BesselBandStop : PoleFilterSpace + { + void SetupAs( CalcT centerFreq, CalcT normWidth ) + { + Spec spec; + spec.order=order; + spec.centerFreq=centerFreq; + spec.normWidth=normWidth; + spec.sampleRate=1; + PoleFilterSpace::Setup( spec ); + } + }; + + //-------------------------------------------------------------------------- + + template + struct BesselLowShelf : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq, CalcT gainDb ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + spec.gainDb=gainDb; + PoleFilterSpace::Setup( spec ); + } + }; + + //-------------------------------------------------------------------------- + // + // Legendere + // + //-------------------------------------------------------------------------- + + // Low pass prototype + struct Legendere : Prototype + { + void Design( const Spec &spec ); + }; + + //-------------------------------------------------------------------------- + + template + struct LegendereLowPass : PoleFilterSpace + { + void SetupAs( CalcT cutoffFreq ) + { + Spec spec; + spec.order=order; + spec.cutoffFreq=cutoffFreq; + spec.sampleRate=1; + PoleFilterSpace::Setup( spec ); + } + }; + + //-------------------------------------------------------------------------- +}; + +#endif diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/.hg_archival.txt b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/.hg_archival.txt new file mode 100644 index 00000000..a713a11d --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/.hg_archival.txt @@ -0,0 +1,4 @@ +repo: d844c818f599aea64fe86745cdd2ef9b3d1910dc +node: b354a59534b0a77c43c67deb1eb1bc39eb99b487 +branch: default +tag: v130 diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/.hgignore b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/.hgignore new file mode 100644 index 00000000..978fa7f5 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/.hgignore @@ -0,0 +1,10 @@ +syntax:glob +test/bm_* +test/st_* +test/tkfc_* +test/tr_* +tools/fastconv_* +tools/fastconvr_* +tools/fft_* +*.swp +*~ diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/.hgtags b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/.hgtags new file mode 100644 index 00000000..398c1b6e --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/.hgtags @@ -0,0 +1,20 @@ +32061530d4353ced38386a4578153954f46e5f8e v04_prehg +52a9ca23c9d9600794388912103e0f7d5f4c92ee v1_2_2_prehg +68e0375761a5789d72c4baa2f8265cec26fb546e v110_prehg +69a218df7458f0269a84b07f64678392edbff941 v111_prehg +702f90b11f060d45b23d563764a3b1bd8f851b7d about2do_real_multi_d_prehg +9c3041fb0677c071d7ca2797e22a63fe681d311b b4simd_prehg +9e532c64d324a0844f1efa7c32908fc2bc0cf350 v1_2_6_prehg +a9797b79bf2c61f7c8259ec417a0189caa48b17a v1_2_3_prehg +b1b2739c82378d6e04977440b2c31b7d1b34c266 aftersimd_prehg +b8c210e0bccdeb66930423a528b11b3660491af8 v120_prehg +c16172181d6aef63712a3f7bc31ef555ce7178bd help_prehg +c16172181d6aef63712a3f7bc31ef555ce7178bd v1_2_3a_prehg +c83c1ec6a5f21c40edf3bb9cbfd8ba1f862f1e0f half_bottle_o_wine_prehg +dec01bc9c4c5f807e2e04aef3449031bc5c6b69e v1_2_5_prehg +e6a840a1383ffc83c8ba17ed4b0bf5d6aefc9a34 v127_prehg +ea3a56794d9326d5505b5bf02f98a70217a1b86d v101_prehg +edd6236ecb25904b385856ffae608548ce3df4e5 v011_prehg +f19b7bcef3f322d277d5b51b2b132657cf08c1a7 v1_2_8_prehg +f73f8d58c37d52379e9d7f13d81f5391226209c1 v1_2_1_prehg +9e0bf8478cc337da0de18b7413d51c714aa0db46 v129 diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/CHANGELOG b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/CHANGELOG new file mode 100644 index 00000000..2dd36037 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/CHANGELOG @@ -0,0 +1,123 @@ +1.3.0 2012-07-18 + removed non-standard malloc.h from kiss_fft.h + + moved -lm to end of link line + + checked various return values + + converted python Numeric code to NumPy + + fixed test of int32_t on 64 bit OS + + added padding in a couple of places to allow SIMD alignment of structs + +1.2.9 2010-05-27 + threadsafe ( including OpenMP ) + + first edition of kissfft.hh the C++ template fft engine + +1.2.8 + Changed memory.h to string.h -- apparently more standard + + Added openmp extensions. This can have fairly linear speedups for larger FFT sizes. + +1.2.7 + Shrank the real-fft memory footprint. Thanks to Galen Seitz. + +1.2.6 (Nov 14, 2006) The "thanks to GenArts" release. + Added multi-dimensional real-optimized FFT, see tools/kiss_fftndr + Thanks go to GenArts, Inc. for sponsoring the development. + +1.2.5 (June 27, 2006) The "release for no good reason" release. + Changed some harmless code to make some compilers' warnings go away. + Added some more digits to pi -- why not. + Added kiss_fft_next_fast_size() function to help people decide how much to pad. + Changed multidimensional test from 8 dimensions to only 3 to avoid testing + problems with fixed point (sorry Buckaroo Banzai). + +1.2.4 (Oct 27, 2005) The "oops, inverse fixed point real fft was borked" release. + Fixed scaling bug for inverse fixed point real fft -- also fixed test code that should've been failing. + Thanks to Jean-Marc Valin for bug report. + + Use sys/types.h for more portable types than short,int,long => int16_t,int32_t,int64_t + If your system does not have these, you may need to define them -- but at least it breaks in a + loud and easily fixable way -- unlike silently using the wrong size type. + + Hopefully tools/psdpng.c is fixed -- thanks to Steve Kellog for pointing out the weirdness. + +1.2.3 (June 25, 2005) The "you want to use WHAT as a sample" release. + Added ability to use 32 bit fixed point samples -- requires a 64 bit intermediate result, a la 'long long' + + Added ability to do 4 FFTs in parallel by using SSE SIMD instructions. This is accomplished by + using the __m128 (vector of 4 floats) as kiss_fft_scalar. Define USE_SIMD to use this. + + I know, I know ... this is drifting a bit from the "kiss" principle, but the speed advantages + make it worth it for some. Also recent gcc makes it SOO easy to use vectors of 4 floats like a POD type. + +1.2.2 (May 6, 2005) The Matthew release + Replaced fixed point division with multiply&shift. Thanks to Jean-Marc Valin for + discussions regarding. Considerable speedup for fixed-point. + + Corrected overflow protection in real fft routines when using fixed point. + Finder's Credit goes to Robert Oschler of robodance for pointing me at the bug. + This also led to the CHECK_OVERFLOW_OP macro. + +1.2.1 (April 4, 2004) + compiles cleanly with just about every -W warning flag under the sun + + reorganized kiss_fft_state so it could be read-only/const. This may be useful for embedded systems + that are willing to predeclare twiddle factors, factorization. + + Fixed C_MUL,S_MUL on 16-bit platforms. + + tmpbuf will only be allocated if input & output buffers are same + scratchbuf will only be allocated for ffts that are not multiples of 2,3,5 + + NOTE: The tmpbuf,scratchbuf changes may require synchronization code for multi-threaded apps. + + +1.2 (Feb 23, 2004) + interface change -- cfg object is forward declaration of struct instead of void* + This maintains type saftey and lets the compiler warn/error about stupid mistakes. + (prompted by suggestion from Erik de Castro Lopo) + + small speed improvements + + added psdpng.c -- sample utility that will create png spectrum "waterfalls" from an input file + ( not terribly useful yet) + +1.1.1 (Feb 1, 2004 ) + minor bug fix -- only affects odd rank, in-place, multi-dimensional FFTs + +1.1 : (Jan 30,2004) + split sample_code/ into test/ and tools/ + + Removed 2-D fft and added N-D fft (arbitrary) + + modified fftutil.c to allow multi-d FFTs + + Modified core fft routine to allow an input stride via kiss_fft_stride() + (eased support of multi-D ffts) + + Added fast convolution filtering (FIR filtering using overlap-scrap method, with tail scrap) + + Add kfc.[ch]: the KISS FFT Cache. It takes care of allocs for you ( suggested by Oscar Lesta ). + +1.0.1 (Dec 15, 2003) + fixed bug that occurred when nfft==1. Thanks to Steven Johnson. + +1.0 : (Dec 14, 2003) + changed kiss_fft function from using a single buffer, to two buffers. + If the same buffer pointer is supplied for both in and out, kiss will + manage the buffer copies. + + added kiss_fft2d and kiss_fftr as separate source files (declarations in kiss_fft.h ) + +0.4 :(Nov 4,2003) optimized for radix 2,3,4,5 + +0.3 :(Oct 28, 2003) woops, version 2 didn't actually factor out any radices other than 2. + Thanks to Steven Johnson for finding this one. + +0.2 :(Oct 27, 2003) added mixed radix, only radix 2,4 optimized versions + +0.1 :(May 19 2003) initial release, radix 2 only diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/COPYING b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/COPYING new file mode 100644 index 00000000..2fc6685a --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/COPYING @@ -0,0 +1,11 @@ +Copyright (c) 2003-2010 Mark Borgerding + +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. + * 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. diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/Makefile b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/Makefile new file mode 100644 index 00000000..96f43d3f --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/Makefile @@ -0,0 +1,33 @@ +KFVER=130 + +doc: + @echo "Start by reading the README file. If you want to build and test lots of stuff, do a 'make testall'" + @echo "but be aware that 'make testall' has dependencies that the basic kissfft software does not." + @echo "It is generally unneeded to run these tests yourself, unless you plan on changing the inner workings" + @echo "of kissfft and would like to make use of its regression tests." + +testall: + # The simd and int32_t types may or may not work on your machine + make -C test DATATYPE=simd CFLAGADD="$(CFLAGADD)" test + make -C test DATATYPE=int32_t CFLAGADD="$(CFLAGADD)" test + make -C test DATATYPE=int16_t CFLAGADD="$(CFLAGADD)" test + make -C test DATATYPE=float CFLAGADD="$(CFLAGADD)" test + make -C test DATATYPE=double CFLAGADD="$(CFLAGADD)" test + echo "all tests passed" + +tarball: clean + hg archive -r v$(KFVER) -t tgz kiss_fft$(KFVER).tar.gz + hg archive -r v$(KFVER) -t zip kiss_fft$(KFVER).zip + +clean: + cd test && make clean + cd tools && make clean + rm -f kiss_fft*.tar.gz *~ *.pyc kiss_fft*.zip + +asm: kiss_fft.s + +kiss_fft.s: kiss_fft.c kiss_fft.h _kiss_fft_guts.h + [ -e kiss_fft.s ] && mv kiss_fft.s kiss_fft.s~ || true + gcc -S kiss_fft.c -O3 -mtune=native -ffast-math -fomit-frame-pointer -unroll-loops -dA -fverbose-asm + gcc -o kiss_fft_short.s -S kiss_fft.c -O3 -mtune=native -ffast-math -fomit-frame-pointer -dA -fverbose-asm -DFIXED_POINT + [ -e kiss_fft.s~ ] && diff kiss_fft.s~ kiss_fft.s || true diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/README b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/README new file mode 100644 index 00000000..03b2e7a9 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/README @@ -0,0 +1,134 @@ +KISS FFT - A mixed-radix Fast Fourier Transform based up on the principle, +"Keep It Simple, Stupid." + + There are many great fft libraries already around. Kiss FFT is not trying +to be better than any of them. It only attempts to be a reasonably efficient, +moderately useful FFT that can use fixed or floating data types and can be +incorporated into someone's C program in a few minutes with trivial licensing. + +USAGE: + + The basic usage for 1-d complex FFT is: + + #include "kiss_fft.h" + + kiss_fft_cfg cfg = kiss_fft_alloc( nfft ,is_inverse_fft ,0,0 ); + + while ... + + ... // put kth sample in cx_in[k].r and cx_in[k].i + + kiss_fft( cfg , cx_in , cx_out ); + + ... // transformed. DC is in cx_out[0].r and cx_out[0].i + + free(cfg); + + Note: frequency-domain data is stored from dc up to 2pi. + so cx_out[0] is the dc bin of the FFT + and cx_out[nfft/2] is the Nyquist bin (if exists) + + Declarations are in "kiss_fft.h", along with a brief description of the +functions you'll need to use. + +Code definitions for 1d complex FFTs are in kiss_fft.c. + +You can do other cool stuff with the extras you'll find in tools/ + + * multi-dimensional FFTs + * real-optimized FFTs (returns the positive half-spectrum: (nfft/2+1) complex frequency bins) + * fast convolution FIR filtering (not available for fixed point) + * spectrum image creation + +The core fft and most tools/ code can be compiled to use float, double, + Q15 short or Q31 samples. The default is float. + + +BACKGROUND: + + I started coding this because I couldn't find a fixed point FFT that didn't +use assembly code. I started with floating point numbers so I could get the +theory straight before working on fixed point issues. In the end, I had a +little bit of code that could be recompiled easily to do ffts with short, float +or double (other types should be easy too). + + Once I got my FFT working, I was curious about the speed compared to +a well respected and highly optimized fft library. I don't want to criticize +this great library, so let's call it FFT_BRANDX. +During this process, I learned: + + 1. FFT_BRANDX has more than 100K lines of code. The core of kiss_fft is about 500 lines (cpx 1-d). + 2. It took me an embarrassingly long time to get FFT_BRANDX working. + 3. A simple program using FFT_BRANDX is 522KB. A similar program using kiss_fft is 18KB (without optimizing for size). + 4. FFT_BRANDX is roughly twice as fast as KISS FFT in default mode. + + It is wonderful that free, highly optimized libraries like FFT_BRANDX exist. +But such libraries carry a huge burden of complexity necessary to extract every +last bit of performance. + + Sometimes simpler is better, even if it's not better. + +FREQUENTLY ASKED QUESTIONS: + Q: Can I use kissfft in a project with a ___ license? + A: Yes. See LICENSE below. + + Q: Why don't I get the output I expect? + A: The two most common causes of this are + 1) scaling : is there a constant multiplier between what you got and what you want? + 2) mixed build environment -- all code must be compiled with same preprocessor + definitions for FIXED_POINT and kiss_fft_scalar + + Q: Will you write/debug my code for me? + A: Probably not unless you pay me. I am happy to answer pointed and topical questions, but + I may refer you to a book, a forum, or some other resource. + + +PERFORMANCE: + (on Athlon XP 2100+, with gcc 2.96, float data type) + + Kiss performed 10000 1024-pt cpx ffts in .63 s of cpu time. + For comparison, it took md5sum twice as long to process the same amount of data. + + Transforming 5 minutes of CD quality audio takes less than a second (nfft=1024). + +DO NOT: + ... use Kiss if you need the Fastest Fourier Transform in the World + ... ask me to add features that will bloat the code + +UNDER THE HOOD: + + Kiss FFT uses a time decimation, mixed-radix, out-of-place FFT. If you give it an input buffer + and output buffer that are the same, a temporary buffer will be created to hold the data. + + No static data is used. The core routines of kiss_fft are thread-safe (but not all of the tools directory). + + No scaling is done for the floating point version (for speed). + Scaling is done both ways for the fixed-point version (for overflow prevention). + + Optimized butterflies are used for factors 2,3,4, and 5. + + The real (i.e. not complex) optimization code only works for even length ffts. It does two half-length + FFTs in parallel (packed into real&imag), and then combines them via twiddling. The result is + nfft/2+1 complex frequency bins from DC to Nyquist. If you don't know what this means, search the web. + + The fast convolution filtering uses the overlap-scrap method, slightly + modified to put the scrap at the tail. + +LICENSE: + Revised BSD License, see COPYING for verbiage. + Basically, "free to use&change, give credit where due, no guarantees" + Note this license is compatible with GPL at one end of the spectrum and closed, commercial software at + the other end. See http://www.fsf.org/licensing/licenses + + A commercial license is available which removes the requirement for attribution. Contact me for details. + + +TODO: + *) Add real optimization for odd length FFTs + *) Document/revisit the input/output fft scaling + *) Make doc describing the overlap (tail) scrap fast convolution filtering in kiss_fastfir.c + *) Test all the ./tools/ code with fixed point (kiss_fastfir.c doesn't work, maybe others) + +AUTHOR: + Mark Borgerding + Mark@Borgerding.net diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/README.simd b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/README.simd new file mode 100644 index 00000000..b0fdac55 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/README.simd @@ -0,0 +1,78 @@ +If you are reading this, it means you think you may be interested in using the SIMD extensions in kissfft +to do 4 *separate* FFTs at once. + +Beware! Beyond here there be dragons! + +This API is not easy to use, is not well documented, and breaks the KISS principle. + + +Still reading? Okay, you may get rewarded for your patience with a considerable speedup +(2-3x) on intel x86 machines with SSE if you are willing to jump through some hoops. + +The basic idea is to use the packed 4 float __m128 data type as a scalar element. +This means that the format is pretty convoluted. It performs 4 FFTs per fft call on signals A,B,C,D. + +For complex data, the data is interlaced as follows: +rA0,rB0,rC0,rD0, iA0,iB0,iC0,iD0, rA1,rB1,rC1,rD1, iA1,iB1,iC1,iD1 ... +where "rA0" is the real part of the zeroth sample for signal A + +Real-only data is laid out: +rA0,rB0,rC0,rD0, rA1,rB1,rC1,rD1, ... + +Compile with gcc flags something like +-O3 -mpreferred-stack-boundary=4 -DUSE_SIMD=1 -msse + +Be aware of SIMD alignment. This is the most likely cause of segfaults. +The code within kissfft uses scratch variables on the stack. +With SIMD, these must have addresses on 16 byte boundaries. +Search on "SIMD alignment" for more info. + + + +Robin at Divide Concept was kind enough to share his code for formatting to/from the SIMD kissfft. +I have not run it -- use it at your own risk. It appears to do 4xN and Nx4 transpositions +(out of place). + +void SSETools::pack128(float* target, float* source, unsigned long size128) +{ + __m128* pDest = (__m128*)target; + __m128* pDestEnd = pDest+size128; + float* source0=source; + float* source1=source0+size128; + float* source2=source1+size128; + float* source3=source2+size128; + + while(pDest + +#define MAXFACTORS 32 +/* e.g. an fft of length 128 has 4 factors + as far as kissfft is concerned + 4*4*4*2 + */ + +struct kiss_fft_state{ + int nfft; + int inverse; + int factors[2*MAXFACTORS]; + kiss_fft_cpx twiddles[1]; +}; + +/* + Explanation of macros dealing with complex math: + + C_MUL(m,a,b) : m = a*b + C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise + C_SUB( res, a,b) : res = a - b + C_SUBFROM( res , a) : res -= a + C_ADDTO( res , a) : res += a + * */ +#ifdef FIXED_POINT +#if (FIXED_POINT==32) +# define FRACBITS 31 +# define SAMPPROD int64_t +#define SAMP_MAX 2147483647 +#else +# define FRACBITS 15 +# define SAMPPROD int32_t +#define SAMP_MAX 32767 +#endif + +#define SAMP_MIN -SAMP_MAX + +#if defined(CHECK_OVERFLOW) +# define CHECK_OVERFLOW_OP(a,op,b) \ + if ( (SAMPPROD)(a) op (SAMPPROD)(b) > SAMP_MAX || (SAMPPROD)(a) op (SAMPPROD)(b) < SAMP_MIN ) { \ + fprintf(stderr,"WARNING:overflow @ " __FILE__ "(%d): (%d " #op" %d) = %ld\n",__LINE__,(a),(b),(SAMPPROD)(a) op (SAMPPROD)(b) ); } +#endif + + +# define smul(a,b) ( (SAMPPROD)(a)*(b) ) +# define sround( x ) (kiss_fft_scalar)( ( (x) + (1<<(FRACBITS-1)) ) >> FRACBITS ) + +# define S_MUL(a,b) sround( smul(a,b) ) + +# define C_MUL(m,a,b) \ + do{ (m).r = sround( smul((a).r,(b).r) - smul((a).i,(b).i) ); \ + (m).i = sround( smul((a).r,(b).i) + smul((a).i,(b).r) ); }while(0) + +# define DIVSCALAR(x,k) \ + (x) = sround( smul( x, SAMP_MAX/k ) ) + +# define C_FIXDIV(c,div) \ + do { DIVSCALAR( (c).r , div); \ + DIVSCALAR( (c).i , div); }while (0) + +# define C_MULBYSCALAR( c, s ) \ + do{ (c).r = sround( smul( (c).r , s ) ) ;\ + (c).i = sround( smul( (c).i , s ) ) ; }while(0) + +#else /* not FIXED_POINT*/ + +# define S_MUL(a,b) ( (a)*(b) ) +#define C_MUL(m,a,b) \ + do{ (m).r = (a).r*(b).r - (a).i*(b).i;\ + (m).i = (a).r*(b).i + (a).i*(b).r; }while(0) +# define C_FIXDIV(c,div) /* NOOP */ +# define C_MULBYSCALAR( c, s ) \ + do{ (c).r *= (s);\ + (c).i *= (s); }while(0) +#endif + +#ifndef CHECK_OVERFLOW_OP +# define CHECK_OVERFLOW_OP(a,op,b) /* noop */ +#endif + +#define C_ADD( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,+,(b).r)\ + CHECK_OVERFLOW_OP((a).i,+,(b).i)\ + (res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \ + }while(0) +#define C_SUB( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,-,(b).r)\ + CHECK_OVERFLOW_OP((a).i,-,(b).i)\ + (res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \ + }while(0) +#define C_ADDTO( res , a)\ + do { \ + CHECK_OVERFLOW_OP((res).r,+,(a).r)\ + CHECK_OVERFLOW_OP((res).i,+,(a).i)\ + (res).r += (a).r; (res).i += (a).i;\ + }while(0) + +#define C_SUBFROM( res , a)\ + do {\ + CHECK_OVERFLOW_OP((res).r,-,(a).r)\ + CHECK_OVERFLOW_OP((res).i,-,(a).i)\ + (res).r -= (a).r; (res).i -= (a).i; \ + }while(0) + + +#ifdef FIXED_POINT +# define KISS_FFT_COS(phase) floor(.5+SAMP_MAX * cos (phase)) +# define KISS_FFT_SIN(phase) floor(.5+SAMP_MAX * sin (phase)) +# define HALF_OF(x) ((x)>>1) +#elif defined(USE_SIMD) +# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) ) +# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) ) +# define HALF_OF(x) ((x)*_mm_set1_ps(.5)) +#else +# define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase) +# define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase) +# define HALF_OF(x) ((x)*.5) +#endif + +#define kf_cexp(x,phase) \ + do{ \ + (x)->r = KISS_FFT_COS(phase);\ + (x)->i = KISS_FFT_SIN(phase);\ + }while(0) + + +/* a debugging function */ +#define pcpx(c)\ + fprintf(stderr,"%g + %gi\n",(double)((c)->r),(double)((c)->i) ) + + +#ifdef KISS_FFT_USE_ALLOCA +// define this to allow use of alloca instead of malloc for temporary buffers +// Temporary buffers are used in two case: +// 1. FFT sizes that have "bad" factors. i.e. not 2,3 and 5 +// 2. "in-place" FFTs. Notice the quotes, since kissfft does not really do an in-place transform. +#include +#define KISS_FFT_TMP_ALLOC(nbytes) alloca(nbytes) +#define KISS_FFT_TMP_FREE(ptr) +#else +#define KISS_FFT_TMP_ALLOC(nbytes) KISS_FFT_MALLOC(nbytes) +#define KISS_FFT_TMP_FREE(ptr) KISS_FFT_FREE(ptr) +#endif diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/kiss_fft.c b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/kiss_fft.c new file mode 100644 index 00000000..103a6b9a --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/kiss_fft.c @@ -0,0 +1,408 @@ +/* +Copyright (c) 2003-2010, Mark Borgerding + +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. + * 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. +*/ + + +#include "_kiss_fft_guts.h" +/* The guts header contains all the multiplication and addition macros that are defined for + fixed or floating point complex numbers. It also delares the kf_ internal functions. + */ + +static void kf_bfly2( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m + ) +{ + kiss_fft_cpx * Fout2; + kiss_fft_cpx * tw1 = st->twiddles; + kiss_fft_cpx t; + Fout2 = Fout + m; + do{ + C_FIXDIV(*Fout,2); C_FIXDIV(*Fout2,2); + + C_MUL (t, *Fout2 , *tw1); + tw1 += fstride; + C_SUB( *Fout2 , *Fout , t ); + C_ADDTO( *Fout , t ); + ++Fout2; + ++Fout; + }while (--m); +} + +static void kf_bfly4( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + const size_t m + ) +{ + kiss_fft_cpx *tw1,*tw2,*tw3; + kiss_fft_cpx scratch[6]; + size_t k=m; + const size_t m2=2*m; + const size_t m3=3*m; + + + tw3 = tw2 = tw1 = st->twiddles; + + do { + C_FIXDIV(*Fout,4); C_FIXDIV(Fout[m],4); C_FIXDIV(Fout[m2],4); C_FIXDIV(Fout[m3],4); + + C_MUL(scratch[0],Fout[m] , *tw1 ); + C_MUL(scratch[1],Fout[m2] , *tw2 ); + C_MUL(scratch[2],Fout[m3] , *tw3 ); + + C_SUB( scratch[5] , *Fout, scratch[1] ); + C_ADDTO(*Fout, scratch[1]); + C_ADD( scratch[3] , scratch[0] , scratch[2] ); + C_SUB( scratch[4] , scratch[0] , scratch[2] ); + C_SUB( Fout[m2], *Fout, scratch[3] ); + tw1 += fstride; + tw2 += fstride*2; + tw3 += fstride*3; + C_ADDTO( *Fout , scratch[3] ); + + if(st->inverse) { + Fout[m].r = scratch[5].r - scratch[4].i; + Fout[m].i = scratch[5].i + scratch[4].r; + Fout[m3].r = scratch[5].r + scratch[4].i; + Fout[m3].i = scratch[5].i - scratch[4].r; + }else{ + Fout[m].r = scratch[5].r + scratch[4].i; + Fout[m].i = scratch[5].i - scratch[4].r; + Fout[m3].r = scratch[5].r - scratch[4].i; + Fout[m3].i = scratch[5].i + scratch[4].r; + } + ++Fout; + }while(--k); +} + +static void kf_bfly3( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + size_t m + ) +{ + size_t k=m; + const size_t m2 = 2*m; + kiss_fft_cpx *tw1,*tw2; + kiss_fft_cpx scratch[5]; + kiss_fft_cpx epi3; + epi3 = st->twiddles[fstride*m]; + + tw1=tw2=st->twiddles; + + do{ + C_FIXDIV(*Fout,3); C_FIXDIV(Fout[m],3); C_FIXDIV(Fout[m2],3); + + C_MUL(scratch[1],Fout[m] , *tw1); + C_MUL(scratch[2],Fout[m2] , *tw2); + + C_ADD(scratch[3],scratch[1],scratch[2]); + C_SUB(scratch[0],scratch[1],scratch[2]); + tw1 += fstride; + tw2 += fstride*2; + + Fout[m].r = Fout->r - (kiss_fft_scalar) HALF_OF(scratch[3].r); + Fout[m].i = Fout->i - (kiss_fft_scalar) HALF_OF(scratch[3].i); + + C_MULBYSCALAR( scratch[0] , epi3.i ); + + C_ADDTO(*Fout,scratch[3]); + + Fout[m2].r = Fout[m].r + scratch[0].i; + Fout[m2].i = Fout[m].i - scratch[0].r; + + Fout[m].r -= scratch[0].i; + Fout[m].i += scratch[0].r; + + ++Fout; + }while(--k); +} + +static void kf_bfly5( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m + ) +{ + kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4; + int u; + kiss_fft_cpx scratch[13]; + kiss_fft_cpx * twiddles = st->twiddles; + kiss_fft_cpx *tw; + kiss_fft_cpx ya,yb; + ya = twiddles[fstride*m]; + yb = twiddles[fstride*2*m]; + + Fout0=Fout; + Fout1=Fout0+m; + Fout2=Fout0+2*m; + Fout3=Fout0+3*m; + Fout4=Fout0+4*m; + + tw=st->twiddles; + for ( u=0; ur += scratch[7].r + scratch[8].r; + Fout0->i += scratch[7].i + scratch[8].i; + + scratch[5].r = scratch[0].r + S_MUL(scratch[7].r,ya.r) + S_MUL(scratch[8].r,yb.r); + scratch[5].i = scratch[0].i + S_MUL(scratch[7].i,ya.r) + S_MUL(scratch[8].i,yb.r); + + scratch[6].r = S_MUL(scratch[10].i,ya.i) + S_MUL(scratch[9].i,yb.i); + scratch[6].i = -S_MUL(scratch[10].r,ya.i) - S_MUL(scratch[9].r,yb.i); + + C_SUB(*Fout1,scratch[5],scratch[6]); + C_ADD(*Fout4,scratch[5],scratch[6]); + + scratch[11].r = scratch[0].r + S_MUL(scratch[7].r,yb.r) + S_MUL(scratch[8].r,ya.r); + scratch[11].i = scratch[0].i + S_MUL(scratch[7].i,yb.r) + S_MUL(scratch[8].i,ya.r); + scratch[12].r = - S_MUL(scratch[10].i,yb.i) + S_MUL(scratch[9].i,ya.i); + scratch[12].i = S_MUL(scratch[10].r,yb.i) - S_MUL(scratch[9].r,ya.i); + + C_ADD(*Fout2,scratch[11],scratch[12]); + C_SUB(*Fout3,scratch[11],scratch[12]); + + ++Fout0;++Fout1;++Fout2;++Fout3;++Fout4; + } +} + +/* perform the butterfly for one stage of a mixed radix FFT */ +static void kf_bfly_generic( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m, + int p + ) +{ + int u,k,q1,q; + kiss_fft_cpx * twiddles = st->twiddles; + kiss_fft_cpx t; + int Norig = st->nfft; + + kiss_fft_cpx * scratch = (kiss_fft_cpx*)KISS_FFT_TMP_ALLOC(sizeof(kiss_fft_cpx)*p); + + for ( u=0; u=Norig) twidx-=Norig; + C_MUL(t,scratch[q] , twiddles[twidx] ); + C_ADDTO( Fout[ k ] ,t); + } + k += m; + } + } + KISS_FFT_TMP_FREE(scratch); +} + +static +void kf_work( + kiss_fft_cpx * Fout, + const kiss_fft_cpx * f, + const size_t fstride, + int in_stride, + int * factors, + const kiss_fft_cfg st + ) +{ + kiss_fft_cpx * Fout_beg=Fout; + const int p=*factors++; /* the radix */ + const int m=*factors++; /* stage's fft length/p */ + const kiss_fft_cpx * Fout_end = Fout + p*m; + +#ifdef _OPENMP + // use openmp extensions at the + // top-level (not recursive) + if (fstride==1 && p<=5) + { + int k; + + // execute the p different work units in different threads +# pragma omp parallel for + for (k=0;k floor_sqrt) + p = n; /* no more factors, skip to end */ + } + n /= p; + *facbuf++ = p; + *facbuf++ = n; + } while (n > 1); +} + +/* + * + * User-callable function to allocate all necessary storage space for the fft. + * + * The return value is a contiguous block of memory, allocated with malloc. As such, + * It can be freed with free(), rather than a kiss_fft-specific function. + * */ +kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem ) +{ + kiss_fft_cfg st=NULL; + size_t memneeded = sizeof(struct kiss_fft_state) + + sizeof(kiss_fft_cpx)*(nfft-1); /* twiddle factors*/ + + if ( lenmem==NULL ) { + st = ( kiss_fft_cfg)KISS_FFT_MALLOC( memneeded ); + }else{ + if (mem != NULL && *lenmem >= memneeded) + st = (kiss_fft_cfg)mem; + *lenmem = memneeded; + } + if (st) { + int i; + st->nfft=nfft; + st->inverse = inverse_fft; + + for (i=0;iinverse) + phase *= -1; + kf_cexp(st->twiddles+i, phase ); + } + + kf_factor(nfft,st->factors); + } + return st; +} + + +void kiss_fft_stride(kiss_fft_cfg st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int in_stride) +{ + if (fin == fout) { + //NOTE: this is not really an in-place FFT algorithm. + //It just performs an out-of-place FFT into a temp buffer + kiss_fft_cpx * tmpbuf = (kiss_fft_cpx*)KISS_FFT_TMP_ALLOC( sizeof(kiss_fft_cpx)*st->nfft); + kf_work(tmpbuf,fin,1,in_stride, st->factors,st); + memcpy(fout,tmpbuf,sizeof(kiss_fft_cpx)*st->nfft); + KISS_FFT_TMP_FREE(tmpbuf); + }else{ + kf_work( fout, fin, 1,in_stride, st->factors,st ); + } +} + +void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout) +{ + kiss_fft_stride(cfg,fin,fout,1); +} + + +void kiss_fft_cleanup(void) +{ + // nothing needed any more +} + +int kiss_fft_next_fast_size(int n) +{ + while(1) { + int m=n; + while ( (m%2) == 0 ) m/=2; + while ( (m%3) == 0 ) m/=3; + while ( (m%5) == 0 ) m/=5; + if (m<=1) + break; /* n is completely factorable by twos, threes, and fives */ + n++; + } + return n; +} diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/kiss_fft.h b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/kiss_fft.h new file mode 100644 index 00000000..64c50f4a --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/kiss_fft.h @@ -0,0 +1,124 @@ +#ifndef KISS_FFT_H +#define KISS_FFT_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + ATTENTION! + If you would like a : + -- a utility that will handle the caching of fft objects + -- real-only (no imaginary time component ) FFT + -- a multi-dimensional FFT + -- a command-line utility to perform ffts + -- a command-line utility to perform fast-convolution filtering + + Then see kfc.h kiss_fftr.h kiss_fftnd.h fftutil.c kiss_fastfir.c + in the tools/ directory. +*/ + +#ifdef USE_SIMD +# include +# define kiss_fft_scalar __m128 +#define KISS_FFT_MALLOC(nbytes) _mm_malloc(nbytes,16) +#define KISS_FFT_FREE _mm_free +#else +#define KISS_FFT_MALLOC malloc +#define KISS_FFT_FREE free +#endif + + +#ifdef FIXED_POINT +#include +# if (FIXED_POINT == 32) +# define kiss_fft_scalar int32_t +# else +# define kiss_fft_scalar int16_t +# endif +#else +# ifndef kiss_fft_scalar +/* default is float */ +# define kiss_fft_scalar float +# endif +#endif + +typedef struct { + kiss_fft_scalar r; + kiss_fft_scalar i; +}kiss_fft_cpx; + +typedef struct kiss_fft_state* kiss_fft_cfg; + +/* + * kiss_fft_alloc + * + * Initialize a FFT (or IFFT) algorithm's cfg/state buffer. + * + * typical usage: kiss_fft_cfg mycfg=kiss_fft_alloc(1024,0,NULL,NULL); + * + * The return value from fft_alloc is a cfg buffer used internally + * by the fft routine or NULL. + * + * If lenmem is NULL, then kiss_fft_alloc will allocate a cfg buffer using malloc. + * The returned value should be free()d when done to avoid memory leaks. + * + * The state can be placed in a user supplied buffer 'mem': + * If lenmem is not NULL and mem is not NULL and *lenmem is large enough, + * then the function places the cfg in mem and the size used in *lenmem + * and returns mem. + * + * If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough), + * then the function returns NULL and places the minimum cfg + * buffer size in *lenmem. + * */ + +kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem); + +/* + * kiss_fft(cfg,in_out_buf) + * + * Perform an FFT on a complex input buffer. + * for a forward FFT, + * fin should be f[0] , f[1] , ... ,f[nfft-1] + * fout will be F[0] , F[1] , ... ,F[nfft-1] + * Note that each element is complex and can be accessed like + f[k].r and f[k].i + * */ +void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); + +/* + A more generic version of the above function. It reads its input from every Nth sample. + * */ +void kiss_fft_stride(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int fin_stride); + +/* If kiss_fft_alloc allocated a buffer, it is one contiguous + buffer and can be simply free()d when no longer needed*/ +#define kiss_fft_free free + +/* + Cleans up some memory that gets managed internally. Not necessary to call, but it might clean up + your compiler output to call this before you exit. +*/ +void kiss_fft_cleanup(void); + + +/* + * Returns the smallest integer k, such that k>=n and k has only "fast" factors (2,3,5) + */ +int kiss_fft_next_fast_size(int n); + +/* for real ffts, we need an even size */ +#define kiss_fftr_next_fast_size_real(n) \ + (kiss_fft_next_fast_size( ((n)+1)>>1)<<1) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/kissfft.hh b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/kissfft.hh new file mode 100644 index 00000000..a586cb11 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/kissfft.hh @@ -0,0 +1,299 @@ +#ifndef KISSFFT_CLASS_HH +#include +#include + +namespace kissfft_utils { + +template +struct traits +{ + typedef T_scalar scalar_type; + typedef std::complex cpx_type; + void fill_twiddles( std::complex * dst ,int nfft,bool inverse) + { + T_scalar phinc = (inverse?2:-2)* acos( (T_scalar) -1) / nfft; + for (int i=0;i(0,i*phinc) ); + } + + void prepare( + std::vector< std::complex > & dst, + int nfft,bool inverse, + std::vector & stageRadix, + std::vector & stageRemainder ) + { + _twiddles.resize(nfft); + fill_twiddles( &_twiddles[0],nfft,inverse); + dst = _twiddles; + + //factorize + //start factoring out 4's, then 2's, then 3,5,7,9,... + int n= nfft; + int p=4; + do { + while (n % p) { + switch (p) { + case 4: p = 2; break; + case 2: p = 3; break; + default: p += 2; break; + } + if (p*p>n) + p=n;// no more factors + } + n /= p; + stageRadix.push_back(p); + stageRemainder.push_back(n); + }while(n>1); + } + std::vector _twiddles; + + + const cpx_type twiddle(int i) { return _twiddles[i]; } +}; + +} + +template + > +class kissfft +{ + public: + typedef T_traits traits_type; + typedef typename traits_type::scalar_type scalar_type; + typedef typename traits_type::cpx_type cpx_type; + + kissfft(int nfft,bool inverse,const traits_type & traits=traits_type() ) + :_nfft(nfft),_inverse(inverse),_traits(traits) + { + _traits.prepare(_twiddles, _nfft,_inverse ,_stageRadix, _stageRemainder); + } + + void transform(const cpx_type * src , cpx_type * dst) + { + kf_work(0, dst, src, 1,1); + } + + private: + void kf_work( int stage,cpx_type * Fout, const cpx_type * f, size_t fstride,size_t in_stride) + { + int p = _stageRadix[stage]; + int m = _stageRemainder[stage]; + cpx_type * Fout_beg = Fout; + cpx_type * Fout_end = Fout + p*m; + + if (m==1) { + do{ + *Fout = *f; + f += fstride*in_stride; + }while(++Fout != Fout_end ); + }else{ + do{ + // recursive call: + // DFT of size m*p performed by doing + // p instances of smaller DFTs of size m, + // each one takes a decimated version of the input + kf_work(stage+1, Fout , f, fstride*p,in_stride); + f += fstride*in_stride; + }while( (Fout += m) != Fout_end ); + } + + Fout=Fout_beg; + + // recombine the p smaller DFTs + switch (p) { + case 2: kf_bfly2(Fout,fstride,m); break; + case 3: kf_bfly3(Fout,fstride,m); break; + case 4: kf_bfly4(Fout,fstride,m); break; + case 5: kf_bfly5(Fout,fstride,m); break; + default: kf_bfly_generic(Fout,fstride,m,p); break; + } + } + + // these were #define macros in the original kiss_fft + void C_ADD( cpx_type & c,const cpx_type & a,const cpx_type & b) { c=a+b;} + void C_MUL( cpx_type & c,const cpx_type & a,const cpx_type & b) { c=a*b;} + void C_SUB( cpx_type & c,const cpx_type & a,const cpx_type & b) { c=a-b;} + void C_ADDTO( cpx_type & c,const cpx_type & a) { c+=a;} + void C_FIXDIV( cpx_type & ,int ) {} // NO-OP for float types + scalar_type S_MUL( const scalar_type & a,const scalar_type & b) { return a*b;} + scalar_type HALF_OF( const scalar_type & a) { return a*.5;} + void C_MULBYSCALAR(cpx_type & c,const scalar_type & a) {c*=a;} + + void kf_bfly2( cpx_type * Fout, const size_t fstride, int m) + { + for (int k=0;kreal() - HALF_OF(scratch[3].real() ) , Fout->imag() - HALF_OF(scratch[3].imag() ) ); + + C_MULBYSCALAR( scratch[0] , epi3.imag() ); + + C_ADDTO(*Fout,scratch[3]); + + Fout[m2] = cpx_type( Fout[m].real() + scratch[0].imag() , Fout[m].imag() - scratch[0].real() ); + + C_ADDTO( Fout[m] , cpx_type( -scratch[0].imag(),scratch[0].real() ) ); + ++Fout; + }while(--k); + } + + void kf_bfly5( cpx_type * Fout, const size_t fstride, const size_t m) + { + cpx_type *Fout0,*Fout1,*Fout2,*Fout3,*Fout4; + size_t u; + cpx_type scratch[13]; + cpx_type * twiddles = &_twiddles[0]; + cpx_type *tw; + cpx_type ya,yb; + ya = twiddles[fstride*m]; + yb = twiddles[fstride*2*m]; + + Fout0=Fout; + Fout1=Fout0+m; + Fout2=Fout0+2*m; + Fout3=Fout0+3*m; + Fout4=Fout0+4*m; + + tw=twiddles; + for ( u=0; u=Norig) twidx-=Norig; + C_MUL(t,scratchbuf[q] , twiddles[twidx] ); + C_ADDTO( Fout[ k ] ,t); + } + k += m; + } + } + } + + int _nfft; + bool _inverse; + std::vector _twiddles; + std::vector _stageRadix; + std::vector _stageRemainder; + traits_type _traits; +}; +#endif diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/Makefile b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/Makefile new file mode 100644 index 00000000..c204511e --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/Makefile @@ -0,0 +1,108 @@ + +WARNINGS=-W -Wall -Wstrict-prototypes -Wmissing-prototypes -Waggregate-return \ + -Wcast-align -Wcast-qual -Wnested-externs -Wshadow -Wbad-function-cast \ + -Wwrite-strings + +CFLAGS=-O3 -I.. -I../tools $(WARNINGS) +CFLAGS+=-ffast-math -fomit-frame-pointer +#CFLAGS+=-funroll-loops +#CFLAGS+=-march=prescott +#CFLAGS+= -mtune=native +# TIP: try adding -openmp or -fopenmp to enable OPENMP directives and use of multiple cores +#CFLAGS+=-fopenmp +CFLAGS+= $(CFLAGADD) + + +ifeq "$(NFFT)" "" + NFFT=1800 +endif +ifeq "$(NUMFFTS)" "" + NUMFFTS=10000 +endif + +ifeq "$(DATATYPE)" "" + DATATYPE=float +endif + +BENCHKISS=bm_kiss_$(DATATYPE) +BENCHFFTW=bm_fftw_$(DATATYPE) +SELFTEST=st_$(DATATYPE) +TESTREAL=tr_$(DATATYPE) +TESTKFC=tkfc_$(DATATYPE) +FASTFILTREAL=ffr_$(DATATYPE) +SELFTESTSRC=twotonetest.c + + +TYPEFLAGS=-Dkiss_fft_scalar=$(DATATYPE) + +ifeq "$(DATATYPE)" "int16_t" + TYPEFLAGS=-DFIXED_POINT=16 +endif + +ifeq "$(DATATYPE)" "int32_t" + TYPEFLAGS=-DFIXED_POINT=32 +endif + +ifeq "$(DATATYPE)" "simd" + TYPEFLAGS=-DUSE_SIMD=1 -msse +endif + + +ifeq "$(DATATYPE)" "float" + # fftw needs to be built with --enable-float to build this lib + FFTWLIB=-lfftw3f +else + FFTWLIB=-lfftw3 +endif + +FFTWLIBDIR=-L/usr/local/lib/ + +SRCFILES=../kiss_fft.c ../tools/kiss_fftnd.c ../tools/kiss_fftr.c pstats.c ../tools/kfc.c ../tools/kiss_fftndr.c + +all: tools $(BENCHKISS) $(SELFTEST) $(BENCHFFTW) $(TESTREAL) $(TESTKFC) + +tools: + cd ../tools && make all + + +$(SELFTEST): $(SELFTESTSRC) $(SRCFILES) + $(CC) -o $@ $(CFLAGS) $(TYPEFLAGS) $+ -lm + +$(TESTKFC): $(SRCFILES) + $(CC) -o $@ $(CFLAGS) -DKFC_TEST $(TYPEFLAGS) $+ -lm + +$(TESTREAL): test_real.c $(SRCFILES) + $(CC) -o $@ $(CFLAGS) $(TYPEFLAGS) $+ -lm + +$(BENCHKISS): benchkiss.c $(SRCFILES) + $(CC) -o $@ $(CFLAGS) $(TYPEFLAGS) $+ -lm + +$(BENCHFFTW): benchfftw.c pstats.c + @echo "======attempting to build FFTW benchmark" + @$(CC) -o $@ $(CFLAGS) -DDATATYPE$(DATATYPE) $+ $(FFTWLIB) $(FFTWLIBDIR) -lm || echo "FFTW not available for comparison" + +test: all + @./$(TESTKFC) + @echo "======1d & 2-d complex fft self test (type= $(DATATYPE) )" + @./$(SELFTEST) + @echo "======real FFT (type= $(DATATYPE) )" + @./$(TESTREAL) + @echo "======timing test (type=$(DATATYPE))" + @./$(BENCHKISS) -x $(NUMFFTS) -n $(NFFT) + @[ -x ./$(BENCHFFTW) ] && ./$(BENCHFFTW) -x $(NUMFFTS) -n $(NFFT) ||true + @echo "======higher dimensions type=$(DATATYPE))" + @./testkiss.py + +selftest.c: + ./mk_test.py 10 12 14 > selftest.c +selftest_short.c: + ./mk_test.py -s 10 12 14 > selftest_short.c + + +CXXFLAGS=-O3 -ffast-math -fomit-frame-pointer -I.. -I../tools -W -Wall +testcpp: testcpp.cc ../kissfft.hh + $(CXX) -o $@ $(CXXFLAGS) testcpp.cc -lm + + +clean: + rm -f *~ bm_* st_* tr_* kf_* tkfc_* ff_* ffr_* *.pyc *.pyo *.dat testcpp diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/benchfftw.c b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/benchfftw.c new file mode 100644 index 00000000..8824d195 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/benchfftw.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include "pstats.h" + +#ifdef DATATYPEdouble + +#define CPXTYPE fftw_complex +#define PLAN fftw_plan +#define FFTMALLOC fftw_malloc +#define MAKEPLAN fftw_plan_dft_1d +#define DOFFT fftw_execute +#define DESTROYPLAN fftw_destroy_plan +#define FFTFREE fftw_free + +#elif defined(DATATYPEfloat) + +#define CPXTYPE fftwf_complex +#define PLAN fftwf_plan +#define FFTMALLOC fftwf_malloc +#define MAKEPLAN fftwf_plan_dft_1d +#define DOFFT fftwf_execute +#define DESTROYPLAN fftwf_destroy_plan +#define FFTFREE fftwf_free + +#endif + +#ifndef CPXTYPE +int main(void) +{ + fprintf(stderr,"Datatype not available in FFTW\n" ); + return 0; +} +#else +int main(int argc,char ** argv) +{ + int nfft=1024; + int isinverse=0; + int numffts=1000,i; + + CPXTYPE * in=NULL; + CPXTYPE * out=NULL; + PLAN p; + + pstats_init(); + + while (1) { + int c = getopt (argc, argv, "n:ix:h"); + if (c == -1) + break; + switch (c) { + case 'n': + nfft = atoi (optarg); + break; + case 'x': + numffts = atoi (optarg); + break; + case 'i': + isinverse = 1; + break; + case 'h': + case '?': + default: + fprintf(stderr,"options:\n-n N: complex fft length\n-i: inverse\n-x N: number of ffts to compute\n" + ""); + } + } + + in=FFTMALLOC(sizeof(CPXTYPE) * nfft); + out=FFTMALLOC(sizeof(CPXTYPE) * nfft); + for (i=0;i +#include +#include +#include +#include "kiss_fft.h" +#include "kiss_fftr.h" +#include "kiss_fftnd.h" +#include "kiss_fftndr.h" + +#include "pstats.h" + +static +int getdims(int * dims, char * arg) +{ + char *s; + int ndims=0; + while ( (s=strtok( arg , ",") ) ) { + dims[ndims++] = atoi(s); + //printf("%s=%d\n",s,dims[ndims-1]); + arg=NULL; + } + return ndims; +} + +int main(int argc,char ** argv) +{ + int k; + int nfft[32]; + int ndims = 1; + int isinverse=0; + int numffts=1000,i; + kiss_fft_cpx * buf; + kiss_fft_cpx * bufout; + int real = 0; + + nfft[0] = 1024;// default + + while (1) { + int c = getopt (argc, argv, "n:ix:r"); + if (c == -1) + break; + switch (c) { + case 'r': + real = 1; + break; + case 'n': + ndims = getdims(nfft, optarg ); + if (nfft[0] != kiss_fft_next_fast_size(nfft[0]) ) { + int ng = kiss_fft_next_fast_size(nfft[0]); + fprintf(stderr,"warning: %d might be a better choice for speed than %d\n",ng,nfft[0]); + } + break; + case 'x': + numffts = atoi (optarg); + break; + case 'i': + isinverse = 1; + break; + } + } + int nbytes = sizeof(kiss_fft_cpx); + for (k=0;k + +#include "kiss_fft.h" +#include "kiss_fftnd.h" +#include "kiss_fftr.h" + +BEGIN_BENCH_DOC +BENCH_DOC("name", "kissfft") +BENCH_DOC("version", "1.0.1") +BENCH_DOC("year", "2004") +BENCH_DOC("author", "Mark Borgerding") +BENCH_DOC("language", "C") +BENCH_DOC("url", "http://sourceforge.net/projects/kissfft/") +BENCH_DOC("copyright", +"Copyright (c) 2003,4 Mark Borgerding\n" +"\n" +"All rights reserved.\n" +"\n" +"Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n" +"\n" +" * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n" +" * 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.\n" +" * 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.\n" +"\n" + "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.\n") +END_BENCH_DOC + +int can_do(struct problem *p) +{ + if (p->rank == 1) { + if (p->kind == PROBLEM_REAL) { + return (p->n[0] & 1) == 0; /* only even real is okay */ + } else { + return 1; + } + } else { + return p->kind == PROBLEM_COMPLEX; + } +} + +static kiss_fft_cfg cfg=NULL; +static kiss_fftr_cfg cfgr=NULL; +static kiss_fftnd_cfg cfgnd=NULL; + +#define FAILIF( c ) \ + if ( c ) do {\ + fprintf(stderr,\ + "kissfft: " #c " (file=%s:%d errno=%d %s)\n",\ + __FILE__,__LINE__ , errno,strerror( errno ) ) ;\ + exit(1);\ + }while(0) + + + +void setup(struct problem *p) +{ + size_t i; + + /* + fprintf(stderr,"%s %s %d-d ", + (p->sign == 1)?"Inverse":"Forward", + p->kind == PROBLEM_COMPLEX?"Complex":"Real", + p->rank); + */ + if (p->rank == 1) { + if (p->kind == PROBLEM_COMPLEX) { + cfg = kiss_fft_alloc (p->n[0], (p->sign == 1), 0, 0); + FAILIF(cfg==NULL); + }else{ + cfgr = kiss_fftr_alloc (p->n[0], (p->sign == 1), 0, 0); + FAILIF(cfgr==NULL); + } + }else{ + int dims[5]; + for (i=0;irank;++i){ + dims[i] = p->n[i]; + } + /* multi-dimensional */ + if (p->kind == PROBLEM_COMPLEX) { + cfgnd = kiss_fftnd_alloc( dims , p->rank, (p->sign == 1), 0, 0 ); + FAILIF(cfgnd==NULL); + } + } +} + +void doit(int iter, struct problem *p) +{ + int i; + void *in = p->in; + void *out = p->out; + + if (p->in_place) + out = p->in; + + if (p->rank == 1) { + if (p->kind == PROBLEM_COMPLEX){ + for (i = 0; i < iter; ++i) + kiss_fft (cfg, in, out); + } else { + /* PROBLEM_REAL */ + if (p->sign == -1) /* FORWARD */ + for (i = 0; i < iter; ++i) + kiss_fftr (cfgr, in, out); + else + for (i = 0; i < iter; ++i) + kiss_fftri (cfgr, in, out); + } + }else{ + /* multi-dimensional */ + for (i = 0; i < iter; ++i) + kiss_fftnd(cfgnd,in,out); + } +} + +void done(struct problem *p) +{ + free(cfg); + cfg=NULL; + free(cfgr); + cfgr=NULL; + free(cfgnd); + cfgnd=NULL; + UNUSED(p); +} diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/fastfir.py b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/fastfir.py new file mode 100644 index 00000000..5ff432a3 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/fastfir.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python + +from Numeric import * +from FFT import * + +def make_random(len): + import random + res=[] + for i in range(int(len)): + r=random.uniform(-1,1) + i=random.uniform(-1,1) + res.append( complex(r,i) ) + return res + +def slowfilter(sig,h): + translen = len(h)-1 + return convolve(sig,h)[translen:-translen] + +def nextpow2(x): + return 2 ** math.ceil(math.log(x)/math.log(2)) + +def fastfilter(sig,h,nfft=None): + if nfft is None: + nfft = int( nextpow2( 2*len(h) ) ) + H = fft( h , nfft ) + scraplen = len(h)-1 + keeplen = nfft-scraplen + res=[] + isdone = 0 + lastidx = nfft + idx0 = 0 + while not isdone: + idx1 = idx0 + nfft + if idx1 >= len(sig): + idx1 = len(sig) + lastidx = idx1-idx0 + if lastidx <= scraplen: + break + isdone = 1 + Fss = fft(sig[idx0:idx1],nfft) + fm = Fss * H + m = inverse_fft(fm) + res.append( m[scraplen:lastidx] ) + idx0 += keeplen + return concatenate( res ) + +def main(): + import sys + from getopt import getopt + opts,args = getopt(sys.argv[1:],'rn:l:') + opts=dict(opts) + + siglen = int(opts.get('-l',1e4 ) ) + hlen =50 + + nfft = int(opts.get('-n',128) ) + usereal = opts.has_key('-r') + + print 'nfft=%d'%nfft + # make a signal + sig = make_random( siglen ) + # make an impulse response + h = make_random( hlen ) + #h=[1]*2+[0]*3 + if usereal: + sig=[c.real for c in sig] + h=[c.real for c in h] + + # perform MAC filtering + yslow = slowfilter(sig,h) + #print '',yslow,'' + #yfast = fastfilter(sig,h,nfft) + yfast = utilfastfilter(sig,h,nfft,usereal) + #print yfast + print 'len(yslow)=%d'%len(yslow) + print 'len(yfast)=%d'%len(yfast) + diff = yslow-yfast + snr = 10*log10( abs( vdot(yslow,yslow) / vdot(diff,diff) ) ) + print 'snr=%s' % snr + if snr < 10.0: + print 'h=',h + print 'sig=',sig[:5],'...' + print 'yslow=',yslow[:5],'...' + print 'yfast=',yfast[:5],'...' + +def utilfastfilter(sig,h,nfft,usereal): + import compfft + import os + open( 'sig.dat','w').write( compfft.dopack(sig,'f',not usereal) ) + open( 'h.dat','w').write( compfft.dopack(h,'f',not usereal) ) + if usereal: + util = './fastconvr' + else: + util = './fastconv' + cmd = 'time %s -n %d -i sig.dat -h h.dat -o out.dat' % (util, nfft) + print cmd + ec = os.system(cmd) + print 'exited->',ec + return compfft.dounpack(open('out.dat').read(),'f',not usereal) + +if __name__ == "__main__": + main() diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/fft.py b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/fft.py new file mode 100644 index 00000000..2705f71f --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/fft.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python + +import math +import sys +import random + +pi=math.pi +e=math.e +j=complex(0,1) + +def fft(f,inv): + n=len(f) + if n==1: + return f + + for p in 2,3,5: + if n%p==0: + break + else: + raise Exception('%s not factorable ' % n) + + m = n/p + Fout=[] + for q in range(p): # 0,1 + fp = f[q::p] # every p'th time sample + Fp = fft( fp ,inv) + Fout.extend( Fp ) + + for u in range(m): + scratch = Fout[u::m] # u to end in strides of m + for q1 in range(p): + k = q1*m + u # indices to Fout above that became scratch + Fout[ k ] = scratch[0] # cuz e**0==1 in loop below + for q in range(1,p): + if inv: + t = e ** ( j*2*pi*k*q/n ) + else: + t = e ** ( -j*2*pi*k*q/n ) + Fout[ k ] += scratch[q] * t + + return Fout + +def rifft(F): + N = len(F) - 1 + Z = [0] * (N) + for k in range(N): + Fek = ( F[k] + F[-k-1].conjugate() ) + Fok = ( F[k] - F[-k-1].conjugate() ) * e ** (j*pi*k/N) + Z[k] = Fek + j*Fok + + fp = fft(Z , 1) + + f = [] + for c in fp: + f.append(c.real) + f.append(c.imag) + return f + +def real_fft( f,inv ): + if inv: + return rifft(f) + + N = len(f) / 2 + + res = f[::2] + ims = f[1::2] + + fp = [ complex(r,i) for r,i in zip(res,ims) ] + print 'fft input ', fp + Fp = fft( fp ,0 ) + print 'fft output ', Fp + + F = [ complex(0,0) ] * ( N+1 ) + + F[0] = complex( Fp[0].real + Fp[0].imag , 0 ) + + for k in range(1,N/2+1): + tw = e ** ( -j*pi*(.5+float(k)/N ) ) + + F1k = Fp[k] + Fp[N-k].conjugate() + F2k = Fp[k] - Fp[N-k].conjugate() + F2k *= tw + F[k] = ( F1k + F2k ) * .5 + F[N-k] = ( F1k - F2k ).conjugate() * .5 + #F[N-k] = ( F1kp + e ** ( -j*pi*(.5+float(N-k)/N ) ) * F2kp ) * .5 + #F[N-k] = ( F1k.conjugate() - tw.conjugate() * F2k.conjugate() ) * .5 + + F[N] = complex( Fp[0].real - Fp[0].imag , 0 ) + return F + +def main(): + #fft_func = fft + fft_func = real_fft + + tvec = [0.309655,0.815653,0.768570,0.591841,0.404767,0.637617,0.007803,0.012665] + Ftvec = [ complex(r,i) for r,i in zip( + [3.548571,-0.378761,-0.061950,0.188537,-0.566981,0.188537,-0.061950,-0.378761], + [0.000000,-1.296198,-0.848764,0.225337,0.000000,-0.225337,0.848764,1.296198] ) ] + + F = fft_func( tvec,0 ) + + nerrs= 0 + for i in range(len(Ftvec)/2 + 1): + if abs( F[i] - Ftvec[i] )> 1e-5: + print 'F[%d]: %s != %s' % (i,F[i],Ftvec[i]) + nerrs += 1 + + print '%d errors in forward fft' % nerrs + if nerrs: + return + + trec = fft_func( F , 1 ) + + for i in range(len(trec) ): + trec[i] /= len(trec) + + for i in range(len(tvec) ): + if abs( trec[i] - tvec[i] )> 1e-5: + print 't[%d]: %s != %s' % (i,tvec[i],trec[i]) + nerrs += 1 + + print '%d errors in reverse fft' % nerrs + + +def make_random(dims=[1]): + import Numeric + res = [] + for i in range(dims[0]): + if len(dims)==1: + r=random.uniform(-1,1) + i=random.uniform(-1,1) + res.append( complex(r,i) ) + else: + res.append( make_random( dims[1:] ) ) + return Numeric.array(res) + +def flatten(x): + import Numeric + ntotal = Numeric.product(Numeric.shape(x)) + return Numeric.reshape(x,(ntotal,)) + +def randmat( ndims ): + dims=[] + for i in range( ndims ): + curdim = int( random.uniform(2,4) ) + dims.append( curdim ) + return make_random(dims ) + +def test_fftnd(ndims=3): + import FFT + import Numeric + + x=randmat( ndims ) + print 'dimensions=%s' % str( Numeric.shape(x) ) + #print 'x=%s' %str(x) + xver = FFT.fftnd(x) + x2=myfftnd(x) + err = xver - x2 + errf = flatten(err) + xverf = flatten(xver) + errpow = Numeric.vdot(errf,errf)+1e-10 + sigpow = Numeric.vdot(xverf,xverf)+1e-10 + snr = 10*math.log10(abs(sigpow/errpow) ) + if snr<80: + print xver + print x2 + print 'SNR=%sdB' % str( snr ) + +def myfftnd(x): + import Numeric + xf = flatten(x) + Xf = fftndwork( xf , Numeric.shape(x) ) + return Numeric.reshape(Xf,Numeric.shape(x) ) + +def fftndwork(x,dims): + import Numeric + dimprod=Numeric.product( dims ) + + for k in range( len(dims) ): + cur_dim=dims[ k ] + stride=dimprod/cur_dim + next_x = [complex(0,0)]*len(x) + for i in range(stride): + next_x[i*cur_dim:(i+1)*cur_dim] = fft(x[i:(i+cur_dim)*stride:stride],0) + x = next_x + return x + +if __name__ == "__main__": + try: + nd = int(sys.argv[1]) + except: + nd=None + if nd: + test_fftnd( nd ) + else: + sys.exit(0) diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/mk_test.py b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/mk_test.py new file mode 100644 index 00000000..998b730f --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/mk_test.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python + +import FFT +import sys +import random +import re +j=complex(0,1) + +def randvec(n,iscomplex): + if iscomplex: + return [ + int(random.uniform(-32768,32767) ) + j*int(random.uniform(-32768,32767) ) + for i in range(n) ] + else: + return [ int(random.uniform(-32768,32767) ) for i in range(n) ] + +def c_format(v,round=0): + if round: + return ','.join( [ '{%d,%d}' %(int(c.real),int(c.imag) ) for c in v ] ) + else: + s= ','.join( [ '{%.60f ,%.60f }' %(c.real,c.imag) for c in v ] ) + return re.sub(r'\.?0+ ',' ',s) + +def test_cpx( n,inverse ,short): + v = randvec(n,1) + scale = 1 + if short: + minsnr=30 + else: + minsnr=100 + + if inverse: + tvecout = FFT.inverse_fft(v) + if short: + scale = 1 + else: + scale = len(v) + else: + tvecout = FFT.fft(v) + if short: + scale = 1.0/len(v) + + tvecout = [ c * scale for c in tvecout ] + + + s="""#define NFFT %d""" % len(v) + """ + { + double snr; + kiss_fft_cpx test_vec_in[NFFT] = { """ + c_format(v) + """}; + kiss_fft_cpx test_vec_out[NFFT] = {""" + c_format( tvecout ) + """}; + kiss_fft_cpx testbuf[NFFT]; + void * cfg = kiss_fft_alloc(NFFT,%d,0,0);""" % inverse + """ + + kiss_fft(cfg,test_vec_in,testbuf); + snr = snr_compare(test_vec_out,testbuf,NFFT); + printf("DATATYPE=" xstr(kiss_fft_scalar) ", FFT n=%d, inverse=%d, snr = %g dB\\n",NFFT,""" + str(inverse) + """,snr); + if (snr<""" + str(minsnr) + """) + exit_code++; + free(cfg); + } +#undef NFFT +""" + return s + +def compare_func(): + s=""" +#define xstr(s) str(s) +#define str(s) #s +double snr_compare( kiss_fft_cpx * test_vec_out,kiss_fft_cpx * testbuf, int n) +{ + int k; + double sigpow,noisepow,err,snr,scale=0; + kiss_fft_cpx err; + sigpow = noisepow = .000000000000000000000000000001; + + for (k=0;k +#include +#include +#include +#include + +#include "pstats.h" + +static struct tms tms_beg; +static struct tms tms_end; +static int has_times = 0; + + +void pstats_init(void) +{ + has_times = times(&tms_beg) != -1; +} + +static void tms_report(void) +{ + double cputime; + if (! has_times ) + return; + times(&tms_end); + cputime = ( ((float)tms_end.tms_utime + tms_end.tms_stime + tms_end.tms_cutime + tms_end.tms_cstime ) - + ((float)tms_beg.tms_utime + tms_beg.tms_stime + tms_beg.tms_cutime + tms_beg.tms_cstime ) ) + / sysconf(_SC_CLK_TCK); + fprintf(stderr,"\tcputime=%.3f\n" , cputime); +} + +static void ps_report(void) +{ + char buf[1024]; +#ifdef __APPLE__ /* MAC OS X */ + sprintf(buf,"ps -o command,majflt,minflt,rss,pagein,vsz -p %d 1>&2",getpid() ); +#else /* GNU/Linux */ + sprintf(buf,"ps -o comm,majflt,minflt,rss,drs,pagein,sz,trs,vsz %d 1>&2",getpid() ); +#endif + if (system( buf )==-1) { + perror("system call to ps failed"); + } +} + +void pstats_report() +{ + ps_report(); + tms_report(); +} + diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/pstats.h b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/pstats.h new file mode 100644 index 00000000..71ff02a4 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/pstats.h @@ -0,0 +1,7 @@ +#ifndef PSTATS_H +#define PSTATS_H + +void pstats_init(void); +void pstats_report(void); + +#endif diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/tailscrap.m b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/tailscrap.m new file mode 100644 index 00000000..abf90469 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/tailscrap.m @@ -0,0 +1,26 @@ +function maxabsdiff=tailscrap() +% test code for circular convolution with the scrapped portion +% at the tail of the buffer, rather than the front +% +% The idea is to rotate the zero-padded h (impulse response) buffer +% to the left nh-1 samples, rotating the junk samples as well. +% This could be very handy in avoiding buffer copies during fast filtering. +nh=10; +nfft=256; + +h=rand(1,nh); +x=rand(1,nfft); + +hpad=[ h(nh) zeros(1,nfft-nh) h(1:nh-1) ]; + +% baseline comparison +y1 = filter(h,1,x); +y1_notrans = y1(nh:nfft); + +% fast convolution +y2 = ifft( fft(hpad) .* fft(x) ); +y2_notrans=y2(1:nfft-nh+1); + +maxabsdiff = max(abs(y2_notrans - y1_notrans)) + +end diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/test_real.c b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/test_real.c new file mode 100644 index 00000000..36a0b086 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/test_real.c @@ -0,0 +1,172 @@ +#include "kiss_fftr.h" +#include "_kiss_fft_guts.h" +#include +#include +#include + +static double cputime(void) +{ + struct tms t; + times(&t); + return (double)(t.tms_utime + t.tms_stime)/ sysconf(_SC_CLK_TCK) ; +} + +static +kiss_fft_scalar rand_scalar(void) +{ +#ifdef USE_SIMD + return _mm_set1_ps(rand()-RAND_MAX/2); +#else + kiss_fft_scalar s = (kiss_fft_scalar)(rand() -RAND_MAX/2); + return s/2; +#endif +} + +static +double snr_compare( kiss_fft_cpx * vec1,kiss_fft_cpx * vec2, int n) +{ + int k; + double sigpow=1e-10,noisepow=1e-10,err,snr,scale=0; + +#ifdef USE_SIMD + float *fv1 = (float*)vec1; + float *fv2 = (float*)vec2; + for (k=0;k<8*n;++k) { + sigpow += *fv1 * *fv1; + err = *fv1 - *fv2; + noisepow += err*err; + ++fv1; + ++fv2; + } +#else + for (k=0;k1) + nfft = atoi(argv[1]); + kiss_fft_cpx cin[nfft]; + kiss_fft_cpx cout[nfft]; + kiss_fft_cpx sout[nfft]; + kiss_fft_cfg kiss_fft_state; + kiss_fftr_cfg kiss_fftr_state; + + kiss_fft_scalar rin[nfft+2]; + kiss_fft_scalar rout[nfft+2]; + kiss_fft_scalar zero; + memset(&zero,0,sizeof(zero) ); // ugly way of setting short,int,float,double, or __m128 to zero + + srand(time(0)); + + for (i=0;i1) { + int k; + for (k=1;k +#include +#include + +#include +static inline +double curtime(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (double)tv.tv_sec + (double)tv.tv_usec*.000001; +} + +using namespace std; + +template +void dotest(int nfft) +{ + typedef kissfft FFT; + typedef std::complex cpx_type; + + cout << "type:" << typeid(T).name() << " nfft:" << nfft; + + FFT fft(nfft,false); + + vector inbuf(nfft); + vector outbuf(nfft); + for (int k=0;k acc = 0; + long double phinc = 2*k0* M_PIl / nfft; + for (int k1=0;k1 x(inbuf[k1].real(),inbuf[k1].imag()); + acc += x * exp( complex(0,-k1*phinc) ); + } + totalpower += norm(acc); + complex x(outbuf[k0].real(),outbuf[k0].imag()); + complex dif = acc - x; + difpower += norm(dif); + } + cout << " RMSE:" << sqrt(difpower/totalpower) << "\t"; + + double t0 = curtime(); + int nits=20e6/nfft; + for (int k=0;k1) { + for (int k=1;k(nfft); dotest(nfft); dotest(nfft); + } + }else{ + dotest(32); dotest(32); dotest(32); + dotest(1024); dotest(1024); dotest(1024); + dotest(840); dotest(840); dotest(840); + } + return 0; +} diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/testkiss.py b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/testkiss.py new file mode 100644 index 00000000..af750654 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/test/testkiss.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python + +import math +import sys +import os +import random +import struct +import popen2 +import getopt +import numpy + +pi=math.pi +e=math.e +j=complex(0,1) + +doreal=0 + +datatype = os.environ.get('DATATYPE','float') + +util = '../tools/fft_' + datatype +minsnr=90 +if datatype == 'double': + fmt='d' +elif datatype=='int16_t': + fmt='h' + minsnr=10 +elif datatype=='int32_t': + fmt='i' +elif datatype=='simd': + fmt='4f' + sys.stderr.write('testkiss.py does not yet test simd') + sys.exit(0) +elif datatype=='float': + fmt='f' +else: + sys.stderr.write('unrecognized datatype %s\n' % datatype) + sys.exit(1) + + +def dopack(x,cpx=1): + x = numpy.reshape( x, ( numpy.size(x),) ) + + if cpx: + s = ''.join( [ struct.pack(fmt*2,c.real,c.imag) for c in x ] ) + else: + s = ''.join( [ struct.pack(fmt,c.real) for c in x ] ) + return s + +def dounpack(x,cpx): + uf = fmt * ( len(x) / struct.calcsize(fmt) ) + s = struct.unpack(uf,x) + if cpx: + return numpy.array(s[::2]) + numpy.array( s[1::2] )*j + else: + return numpy.array(s ) + +def make_random(dims=[1]): + res = [] + for i in range(dims[0]): + if len(dims)==1: + r=random.uniform(-1,1) + if doreal: + res.append( r ) + else: + i=random.uniform(-1,1) + res.append( complex(r,i) ) + else: + res.append( make_random( dims[1:] ) ) + return numpy.array(res) + +def flatten(x): + ntotal = numpy.size(x) + return numpy.reshape(x,(ntotal,)) + +def randmat( ndims ): + dims=[] + for i in range( ndims ): + curdim = int( random.uniform(2,5) ) + if doreal and i==(ndims-1): + curdim = int(curdim/2)*2 # force even last dimension if real + dims.append( curdim ) + return make_random(dims ) + +def test_fft(ndims): + x=randmat( ndims ) + + + if doreal: + xver = numpy.fft.rfftn(x) + else: + xver = numpy.fft.fftn(x) + + open('/tmp/fftexp.dat','w').write(dopack( flatten(xver) , True ) ) + + x2=dofft(x,doreal) + err = xver - x2 + errf = flatten(err) + xverf = flatten(xver) + errpow = numpy.vdot(errf,errf)+1e-10 + sigpow = numpy.vdot(xverf,xverf)+1e-10 + snr = 10*math.log10(abs(sigpow/errpow) ) + print 'SNR (compared to NumPy) : %.1fdB' % float(snr) + + if snr +#include +#include +#include "kiss_fft.h" +#include "kiss_fftr.h" +#include + + +static +double two_tone_test( int nfft, int bin1,int bin2) +{ + kiss_fftr_cfg cfg = NULL; + kiss_fft_cpx *kout = NULL; + kiss_fft_scalar *tbuf = NULL; + + int i; + double f1 = bin1*2*M_PI/nfft; + double f2 = bin2*2*M_PI/nfft; + double sigpow=0; + double noisepow=0; +#if FIXED_POINT==32 + long maxrange = LONG_MAX; +#else + long maxrange = SHRT_MAX;/* works fine for float too*/ +#endif + + cfg = kiss_fftr_alloc(nfft , 0, NULL, NULL); + tbuf = KISS_FFT_MALLOC(nfft * sizeof(kiss_fft_scalar)); + kout = KISS_FFT_MALLOC(nfft * sizeof(kiss_fft_cpx)); + + /* generate a signal with two tones*/ + for (i = 0; i < nfft; i++) { +#ifdef USE_SIMD + tbuf[i] = _mm_set1_ps( (maxrange>>1)*cos(f1*i) + + (maxrange>>1)*cos(f2*i) ); +#else + tbuf[i] = (maxrange>>1)*cos(f1*i) + + (maxrange>>1)*cos(f2*i); +#endif + } + + kiss_fftr(cfg, tbuf, kout); + + for (i=0;i < (nfft/2+1);++i) { +#ifdef USE_SIMD + double tmpr = (double)*(float*)&kout[i].r / (double)maxrange; + double tmpi = (double)*(float*)&kout[i].i / (double)maxrange; +#else + double tmpr = (double)kout[i].r / (double)maxrange; + double tmpi = (double)kout[i].i / (double)maxrange; +#endif + double mag2 = tmpr*tmpr + tmpi*tmpi; + if (i!=0 && i!= nfft/2) + mag2 *= 2; /* all bins except DC and Nyquist have symmetric counterparts implied*/ + + /* if there is power in one of the expected bins, it is signal, otherwise noise*/ + if ( i!=bin1 && i != bin2 ) + noisepow += mag2; + else + sigpow += mag2; + } + kiss_fft_cleanup(); + /*printf("TEST %d,%d,%d noise @ %fdB\n",nfft,bin1,bin2,10*log10(noisepow/sigpow +1e-30) );*/ + return 10*log10(sigpow/(noisepow+1e-50) ); +} + +int main(int argc,char ** argv) +{ + int nfft = 4*2*2*3*5; + if (argc>1) nfft = atoi(argv[1]); + + int i,j; + double minsnr = 500; + double maxsnr = -500; + double snr; + for (i=0;i>4)+1) { + for (j=i;j>4)+7) { + snr = two_tone_test(nfft,i,j); + if (snrmaxsnr) { + maxsnr=snr; + } + } + } + snr = two_tone_test(nfft,nfft/2,nfft/2); + if (snrmaxsnr) maxsnr=snr; + + printf("TwoToneTest: snr ranges from %ddB to %ddB\n",(int)minsnr,(int)maxsnr); + printf("sizeof(kiss_fft_scalar) = %d\n",(int)sizeof(kiss_fft_scalar) ); + return 0; +} diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/Makefile b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/Makefile new file mode 100644 index 00000000..ae7646b8 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/Makefile @@ -0,0 +1,62 @@ +WARNINGS=-W -Wall -Wstrict-prototypes -Wmissing-prototypes -Waggregate-return \ + -Wcast-align -Wcast-qual -Wnested-externs -Wshadow -Wbad-function-cast \ + -Wwrite-strings + +ifeq "$(DATATYPE)" "" + DATATYPE=float +endif + +ifeq "$(DATATYPE)" "int32_t" + TYPEFLAGS=-DFIXED_POINT=32 +endif + +ifeq "$(DATATYPE)" "int16_t" + TYPEFLAGS=-DFIXED_POINT=16 +endif + +ifeq "$(DATATYPE)" "simd" + TYPEFLAGS=-DUSE_SIMD=1 -msse +endif + +ifeq "$(TYPEFLAGS)" "" + TYPEFLAGS=-Dkiss_fft_scalar=$(DATATYPE) +endif + +ifneq ("$(KISS_FFT_USE_ALLOCA)","") + CFLAGS+= -DKISS_FFT_USE_ALLOCA=1 +endif +CFLAGS+= $(CFLAGADD) + + +FFTUTIL=fft_$(DATATYPE) +FASTFILT=fastconv_$(DATATYPE) +FASTFILTREAL=fastconvr_$(DATATYPE) +PSDPNG=psdpng_$(DATATYPE) +DUMPHDR=dumphdr_$(DATATYPE) + +all: $(FFTUTIL) $(FASTFILT) $(FASTFILTREAL) +# $(PSDPNG) +# $(DUMPHDR) + +#CFLAGS=-Wall -O3 -pedantic -march=pentiumpro -ffast-math -fomit-frame-pointer $(WARNINGS) +# If the above flags do not work, try the following +CFLAGS=-Wall -O3 $(WARNINGS) +# tip: try -openmp or -fopenmp to use multiple cores + +$(FASTFILTREAL): ../kiss_fft.c kiss_fastfir.c kiss_fftr.c + $(CC) -o $@ $(CFLAGS) -I.. $(TYPEFLAGS) -DREAL_FASTFIR $+ -DFAST_FILT_UTIL -lm + +$(FASTFILT): ../kiss_fft.c kiss_fastfir.c + $(CC) -o $@ $(CFLAGS) -I.. $(TYPEFLAGS) $+ -DFAST_FILT_UTIL -lm + +$(FFTUTIL): ../kiss_fft.c fftutil.c kiss_fftnd.c kiss_fftr.c kiss_fftndr.c + $(CC) -o $@ $(CFLAGS) -I.. $(TYPEFLAGS) $+ -lm + +$(PSDPNG): ../kiss_fft.c psdpng.c kiss_fftr.c + $(CC) -o $@ $(CFLAGS) -I.. $(TYPEFLAGS) $+ -lpng -lm + +$(DUMPHDR): ../kiss_fft.c dumphdr.c + $(CC) -o $@ $(CFLAGS) -I.. $(TYPEFLAGS) $+ -lm + +clean: + rm -f *~ fft fft_* fastconv fastconv_* fastconvr fastconvr_* psdpng psdpng_* diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/fftutil.c b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/fftutil.c new file mode 100644 index 00000000..db5a8151 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/fftutil.c @@ -0,0 +1,208 @@ +/* +Copyright (c) 2003-2004, Mark Borgerding + +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. + * 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. +*/ + +#include +#include +#include +#include +#include + +#include "kiss_fft.h" +#include "kiss_fftndr.h" + +static +void fft_file(FILE * fin,FILE * fout,int nfft,int isinverse) +{ + kiss_fft_cfg st; + kiss_fft_cpx * buf; + kiss_fft_cpx * bufout; + + buf = (kiss_fft_cpx*)malloc(sizeof(kiss_fft_cpx) * nfft ); + bufout = (kiss_fft_cpx*)malloc(sizeof(kiss_fft_cpx) * nfft ); + st = kiss_fft_alloc( nfft ,isinverse ,0,0); + + while ( fread( buf , sizeof(kiss_fft_cpx) * nfft ,1, fin ) > 0 ) { + kiss_fft( st , buf ,bufout); + fwrite( bufout , sizeof(kiss_fft_cpx) , nfft , fout ); + } + free(st); + free(buf); + free(bufout); +} + +static +void fft_filend(FILE * fin,FILE * fout,int *dims,int ndims,int isinverse) +{ + kiss_fftnd_cfg st; + kiss_fft_cpx *buf; + int dimprod=1,i; + for (i=0;i 0) { + kiss_fftnd (st, buf, buf); + fwrite (buf, sizeof (kiss_fft_cpx), dimprod, fout); + } + free (st); + free (buf); +} + + + +static +void fft_filend_real(FILE * fin,FILE * fout,int *dims,int ndims,int isinverse) +{ + int dimprod=1,i; + kiss_fftndr_cfg st; + void *ibuf; + void *obuf; + int insize,outsize; // size in bytes + + for (i=0;i 0) { + if (isinverse) { + kiss_fftndri(st, + (kiss_fft_cpx*)ibuf, + (kiss_fft_scalar*)obuf); + }else{ + kiss_fftndr(st, + (kiss_fft_scalar*)ibuf, + (kiss_fft_cpx*)obuf); + } + fwrite (obuf, sizeof(kiss_fft_scalar), outsize,fout); + } + free(st); + free(ibuf); + free(obuf); +} + +static +void fft_file_real(FILE * fin,FILE * fout,int nfft,int isinverse) +{ + kiss_fftr_cfg st; + kiss_fft_scalar * rbuf; + kiss_fft_cpx * cbuf; + + rbuf = (kiss_fft_scalar*)malloc(sizeof(kiss_fft_scalar) * nfft ); + cbuf = (kiss_fft_cpx*)malloc(sizeof(kiss_fft_cpx) * (nfft/2+1) ); + st = kiss_fftr_alloc( nfft ,isinverse ,0,0); + + if (isinverse==0) { + while ( fread( rbuf , sizeof(kiss_fft_scalar) * nfft ,1, fin ) > 0 ) { + kiss_fftr( st , rbuf ,cbuf); + fwrite( cbuf , sizeof(kiss_fft_cpx) , (nfft/2 + 1) , fout ); + } + }else{ + while ( fread( cbuf , sizeof(kiss_fft_cpx) * (nfft/2+1) ,1, fin ) > 0 ) { + kiss_fftri( st , cbuf ,rbuf); + fwrite( rbuf , sizeof(kiss_fft_scalar) , nfft , fout ); + } + } + free(st); + free(rbuf); + free(cbuf); +} + +static +int get_dims(char * arg,int * dims) +{ + char *p0; + int ndims=0; + + do{ + p0 = strchr(arg,','); + if (p0) + *p0++ = '\0'; + dims[ndims++] = atoi(arg); +// fprintf(stderr,"dims[%d] = %d\n",ndims-1,dims[ndims-1]); + arg = p0; + }while (p0); + return ndims; +} + +int main(int argc,char ** argv) +{ + int isinverse=0; + int isreal=0; + FILE *fin=stdin; + FILE *fout=stdout; + int ndims=1; + int dims[32]; + dims[0] = 1024; /*default fft size*/ + + while (1) { + int c=getopt(argc,argv,"n:iR"); + if (c==-1) break; + switch (c) { + case 'n': + ndims = get_dims(optarg,dims); + break; + case 'i':isinverse=1;break; + case 'R':isreal=1;break; + case '?': + fprintf(stderr,"usage options:\n" + "\t-n d1[,d2,d3...]: fft dimension(s)\n" + "\t-i : inverse\n" + "\t-R : real input samples, not complex\n"); + exit (1); + default:fprintf(stderr,"bad %c\n",c);break; + } + } + + if ( optind < argc ) { + if (strcmp("-",argv[optind]) !=0) + fin = fopen(argv[optind],"rb"); + ++optind; + } + + if ( optind < argc ) { + if ( strcmp("-",argv[optind]) !=0 ) + fout = fopen(argv[optind],"wb"); + ++optind; + } + + if (ndims==1) { + if (isreal) + fft_file_real(fin,fout,dims[0],isinverse); + else + fft_file(fin,fout,dims[0],isinverse); + }else{ + if (isreal) + fft_filend_real(fin,fout,dims,ndims,isinverse); + else + fft_filend(fin,fout,dims,ndims,isinverse); + } + + if (fout!=stdout) fclose(fout); + if (fin!=stdin) fclose(fin); + + return 0; +} diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kfc.c b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kfc.c new file mode 100644 index 00000000..d94d1240 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kfc.c @@ -0,0 +1,116 @@ +#include "kfc.h" + +/* +Copyright (c) 2003-2004, Mark Borgerding + +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. + * 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. +*/ + + +typedef struct cached_fft *kfc_cfg; + +struct cached_fft +{ + int nfft; + int inverse; + kiss_fft_cfg cfg; + kfc_cfg next; +}; + +static kfc_cfg cache_root=NULL; +static int ncached=0; + +static kiss_fft_cfg find_cached_fft(int nfft,int inverse) +{ + size_t len; + kfc_cfg cur=cache_root; + kfc_cfg prev=NULL; + while ( cur ) { + if ( cur->nfft == nfft && inverse == cur->inverse ) + break;/*found the right node*/ + prev = cur; + cur = prev->next; + } + if (cur== NULL) { + /* no cached node found, need to create a new one*/ + kiss_fft_alloc(nfft,inverse,0,&len); +#ifdef USE_SIMD + int padding = (16-sizeof(struct cached_fft)) & 15; + // make sure the cfg aligns on a 16 byte boundary + len += padding; +#endif + cur = (kfc_cfg)KISS_FFT_MALLOC((sizeof(struct cached_fft) + len )); + if (cur == NULL) + return NULL; + cur->cfg = (kiss_fft_cfg)(cur+1); +#ifdef USE_SIMD + cur->cfg = (kiss_fft_cfg) ((char*)(cur+1)+padding); +#endif + kiss_fft_alloc(nfft,inverse,cur->cfg,&len); + cur->nfft=nfft; + cur->inverse=inverse; + cur->next = NULL; + if ( prev ) + prev->next = cur; + else + cache_root = cur; + ++ncached; + } + return cur->cfg; +} + +void kfc_cleanup(void) +{ + kfc_cfg cur=cache_root; + kfc_cfg next=NULL; + while (cur){ + next = cur->next; + free(cur); + cur=next; + } + ncached=0; + cache_root = NULL; +} +void kfc_fft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout) +{ + kiss_fft( find_cached_fft(nfft,0),fin,fout ); +} + +void kfc_ifft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout) +{ + kiss_fft( find_cached_fft(nfft,1),fin,fout ); +} + +#ifdef KFC_TEST +static void check(int nc) +{ + if (ncached != nc) { + fprintf(stderr,"ncached should be %d,but it is %d\n",nc,ncached); + exit(1); + } +} + +int main(void) +{ + kiss_fft_cpx buf1[1024],buf2[1024]; + memset(buf1,0,sizeof(buf1)); + check(0); + kfc_fft(512,buf1,buf2); + check(1); + kfc_fft(512,buf1,buf2); + check(1); + kfc_ifft(512,buf1,buf2); + check(2); + kfc_cleanup(); + check(0); + return 0; +} +#endif diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kfc.h b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kfc.h new file mode 100644 index 00000000..9b5fd677 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kfc.h @@ -0,0 +1,46 @@ +#ifndef KFC_H +#define KFC_H +#include "kiss_fft.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* +KFC -- Kiss FFT Cache + +Not needing to deal with kiss_fft_alloc and a config +object may be handy for a lot of programs. + +KFC uses the underlying KISS FFT functions, but caches the config object. +The first time kfc_fft or kfc_ifft for a given FFT size, the cfg +object is created for it. All subsequent calls use the cached +configuration object. + +NOTE: +You should probably not use this if your program will be using a lot +of various sizes of FFTs. There is a linear search through the +cached objects. If you are only using one or two FFT sizes, this +will be negligible. Otherwise, you may want to use another method +of managing the cfg objects. + + There is no automated cleanup of the cached objects. This could lead +to large memory usage in a program that uses a lot of *DIFFERENT* +sized FFTs. If you want to force all cached cfg objects to be freed, +call kfc_cleanup. + + */ + +/*forward complex FFT */ +void kfc_fft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout); +/*reverse complex FFT */ +void kfc_ifft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout); + +/*free all cached objects*/ +void kfc_cleanup(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fastfir.c b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fastfir.c new file mode 100644 index 00000000..4560aa37 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fastfir.c @@ -0,0 +1,470 @@ +/* +Copyright (c) 2003-2004, Mark Borgerding + +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. + * 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. +*/ + +#include "_kiss_fft_guts.h" + + +/* + Some definitions that allow real or complex filtering +*/ +#ifdef REAL_FASTFIR +#define MIN_FFT_LEN 2048 +#include "kiss_fftr.h" +typedef kiss_fft_scalar kffsamp_t; +typedef kiss_fftr_cfg kfcfg_t; +#define FFT_ALLOC kiss_fftr_alloc +#define FFTFWD kiss_fftr +#define FFTINV kiss_fftri +#else +#define MIN_FFT_LEN 1024 +typedef kiss_fft_cpx kffsamp_t; +typedef kiss_fft_cfg kfcfg_t; +#define FFT_ALLOC kiss_fft_alloc +#define FFTFWD kiss_fft +#define FFTINV kiss_fft +#endif + +typedef struct kiss_fastfir_state *kiss_fastfir_cfg; + + + +kiss_fastfir_cfg kiss_fastfir_alloc(const kffsamp_t * imp_resp,size_t n_imp_resp, + size_t * nfft,void * mem,size_t*lenmem); + +/* see do_file_filter for usage */ +size_t kiss_fastfir( kiss_fastfir_cfg cfg, kffsamp_t * inbuf, kffsamp_t * outbuf, size_t n, size_t *offset); + + + +static int verbose=0; + + +struct kiss_fastfir_state{ + size_t nfft; + size_t ngood; + kfcfg_t fftcfg; + kfcfg_t ifftcfg; + kiss_fft_cpx * fir_freq_resp; + kiss_fft_cpx * freqbuf; + size_t n_freq_bins; + kffsamp_t * tmpbuf; +}; + + +kiss_fastfir_cfg kiss_fastfir_alloc( + const kffsamp_t * imp_resp,size_t n_imp_resp, + size_t *pnfft, /* if <= 0, an appropriate size will be chosen */ + void * mem,size_t*lenmem) +{ + kiss_fastfir_cfg st = NULL; + size_t len_fftcfg,len_ifftcfg; + size_t memneeded = sizeof(struct kiss_fastfir_state); + char * ptr; + size_t i; + size_t nfft=0; + float scale; + int n_freq_bins; + if (pnfft) + nfft=*pnfft; + + if (nfft<=0) { + /* determine fft size as next power of two at least 2x + the impulse response length*/ + i=n_imp_resp-1; + nfft=2; + do{ + nfft<<=1; + }while (i>>=1); +#ifdef MIN_FFT_LEN + if ( nfft < MIN_FFT_LEN ) + nfft=MIN_FFT_LEN; +#endif + } + if (pnfft) + *pnfft = nfft; + +#ifdef REAL_FASTFIR + n_freq_bins = nfft/2 + 1; +#else + n_freq_bins = nfft; +#endif + /*fftcfg*/ + FFT_ALLOC (nfft, 0, NULL, &len_fftcfg); + memneeded += len_fftcfg; + /*ifftcfg*/ + FFT_ALLOC (nfft, 1, NULL, &len_ifftcfg); + memneeded += len_ifftcfg; + /* tmpbuf */ + memneeded += sizeof(kffsamp_t) * nfft; + /* fir_freq_resp */ + memneeded += sizeof(kiss_fft_cpx) * n_freq_bins; + /* freqbuf */ + memneeded += sizeof(kiss_fft_cpx) * n_freq_bins; + + if (lenmem == NULL) { + st = (kiss_fastfir_cfg) malloc (memneeded); + } else { + if (*lenmem >= memneeded) + st = (kiss_fastfir_cfg) mem; + *lenmem = memneeded; + } + if (!st) + return NULL; + + st->nfft = nfft; + st->ngood = nfft - n_imp_resp + 1; + st->n_freq_bins = n_freq_bins; + ptr=(char*)(st+1); + + st->fftcfg = (kfcfg_t)ptr; + ptr += len_fftcfg; + + st->ifftcfg = (kfcfg_t)ptr; + ptr += len_ifftcfg; + + st->tmpbuf = (kffsamp_t*)ptr; + ptr += sizeof(kffsamp_t) * nfft; + + st->freqbuf = (kiss_fft_cpx*)ptr; + ptr += sizeof(kiss_fft_cpx) * n_freq_bins; + + st->fir_freq_resp = (kiss_fft_cpx*)ptr; + ptr += sizeof(kiss_fft_cpx) * n_freq_bins; + + FFT_ALLOC (nfft,0,st->fftcfg , &len_fftcfg); + FFT_ALLOC (nfft,1,st->ifftcfg , &len_ifftcfg); + + memset(st->tmpbuf,0,sizeof(kffsamp_t)*nfft); + /*zero pad in the middle to left-rotate the impulse response + This puts the scrap samples at the end of the inverse fft'd buffer */ + st->tmpbuf[0] = imp_resp[ n_imp_resp - 1 ]; + for (i=0;itmpbuf[ nfft - n_imp_resp + 1 + i ] = imp_resp[ i ]; + } + + FFTFWD(st->fftcfg,st->tmpbuf,st->fir_freq_resp); + + /* TODO: this won't work for fixed point */ + scale = 1.0 / st->nfft; + + for ( i=0; i < st->n_freq_bins; ++i ) { +#ifdef USE_SIMD + st->fir_freq_resp[i].r *= _mm_set1_ps(scale); + st->fir_freq_resp[i].i *= _mm_set1_ps(scale); +#else + st->fir_freq_resp[i].r *= scale; + st->fir_freq_resp[i].i *= scale; +#endif + } + return st; +} + +static void fastconv1buf(const kiss_fastfir_cfg st,const kffsamp_t * in,kffsamp_t * out) +{ + size_t i; + /* multiply the frequency response of the input signal by + that of the fir filter*/ + FFTFWD( st->fftcfg, in , st->freqbuf ); + for ( i=0; in_freq_bins; ++i ) { + kiss_fft_cpx tmpsamp; + C_MUL(tmpsamp,st->freqbuf[i],st->fir_freq_resp[i]); + st->freqbuf[i] = tmpsamp; + } + + /* perform the inverse fft*/ + FFTINV(st->ifftcfg,st->freqbuf,out); +} + +/* n : the size of inbuf and outbuf in samples + return value: the number of samples completely processed + n-retval samples should be copied to the front of the next input buffer */ +static size_t kff_nocopy( + kiss_fastfir_cfg st, + const kffsamp_t * inbuf, + kffsamp_t * outbuf, + size_t n) +{ + size_t norig=n; + while (n >= st->nfft ) { + fastconv1buf(st,inbuf,outbuf); + inbuf += st->ngood; + outbuf += st->ngood; + n -= st->ngood; + } + return norig - n; +} + +static +size_t kff_flush(kiss_fastfir_cfg st,const kffsamp_t * inbuf,kffsamp_t * outbuf,size_t n) +{ + size_t zpad=0,ntmp; + + ntmp = kff_nocopy(st,inbuf,outbuf,n); + n -= ntmp; + inbuf += ntmp; + outbuf += ntmp; + + zpad = st->nfft - n; + memset(st->tmpbuf,0,sizeof(kffsamp_t)*st->nfft ); + memcpy(st->tmpbuf,inbuf,sizeof(kffsamp_t)*n ); + + fastconv1buf(st,st->tmpbuf,st->tmpbuf); + + memcpy(outbuf,st->tmpbuf,sizeof(kffsamp_t)*( st->ngood - zpad )); + return ntmp + st->ngood - zpad; +} + +size_t kiss_fastfir( + kiss_fastfir_cfg vst, + kffsamp_t * inbuf, + kffsamp_t * outbuf, + size_t n_new, + size_t *offset) +{ + size_t ntot = n_new + *offset; + if (n_new==0) { + return kff_flush(vst,inbuf,outbuf,ntot); + }else{ + size_t nwritten = kff_nocopy(vst,inbuf,outbuf,ntot); + *offset = ntot - nwritten; + /*save the unused or underused samples at the front of the input buffer */ + memcpy( inbuf , inbuf+nwritten , *offset * sizeof(kffsamp_t) ); + return nwritten; + } +} + +#ifdef FAST_FILT_UTIL +#include +#include +#include +#include + +static +void direct_file_filter( + FILE * fin, + FILE * fout, + const kffsamp_t * imp_resp, + size_t n_imp_resp) +{ + size_t nlag = n_imp_resp - 1; + + const kffsamp_t *tmph; + kffsamp_t *buf, *circbuf; + kffsamp_t outval; + size_t nread; + size_t nbuf; + size_t oldestlag = 0; + size_t k, tap; +#ifndef REAL_FASTFIR + kffsamp_t tmp; +#endif + + nbuf = 4096; + buf = (kffsamp_t *) malloc ( sizeof (kffsamp_t) * nbuf); + circbuf = (kffsamp_t *) malloc (sizeof (kffsamp_t) * nlag); + if (!circbuf || !buf) { + perror("circbuf allocation"); + exit(1); + } + + if ( fread (circbuf, sizeof (kffsamp_t), nlag, fin) != nlag ) { + perror ("insufficient data to overcome transient"); + exit (1); + } + + do { + nread = fread (buf, sizeof (kffsamp_t), nbuf, fin); + if (nread <= 0) + break; + + for (k = 0; k < nread; ++k) { + tmph = imp_resp+nlag; +#ifdef REAL_FASTFIR +# ifdef USE_SIMD + outval = _mm_set1_ps(0); +#else + outval = 0; +#endif + for (tap = oldestlag; tap < nlag; ++tap) + outval += circbuf[tap] * *tmph--; + for (tap = 0; tap < oldestlag; ++tap) + outval += circbuf[tap] * *tmph--; + outval += buf[k] * *tmph; +#else +# ifdef USE_SIMD + outval.r = outval.i = _mm_set1_ps(0); +#else + outval.r = outval.i = 0; +#endif + for (tap = oldestlag; tap < nlag; ++tap){ + C_MUL(tmp,circbuf[tap],*tmph); + --tmph; + C_ADDTO(outval,tmp); + } + + for (tap = 0; tap < oldestlag; ++tap) { + C_MUL(tmp,circbuf[tap],*tmph); + --tmph; + C_ADDTO(outval,tmp); + } + C_MUL(tmp,buf[k],*tmph); + C_ADDTO(outval,tmp); +#endif + + circbuf[oldestlag++] = buf[k]; + buf[k] = outval; + + if (oldestlag == nlag) + oldestlag = 0; + } + + if (fwrite (buf, sizeof (buf[0]), nread, fout) != nread) { + perror ("short write"); + exit (1); + } + } while (nread); + free (buf); + free (circbuf); +} + +static +void do_file_filter( + FILE * fin, + FILE * fout, + const kffsamp_t * imp_resp, + size_t n_imp_resp, + size_t nfft ) +{ + int fdout; + size_t n_samps_buf; + + kiss_fastfir_cfg cfg; + kffsamp_t *inbuf,*outbuf; + int nread,nwrite; + size_t idx_inbuf; + + fdout = fileno(fout); + + cfg=kiss_fastfir_alloc(imp_resp,n_imp_resp,&nfft,0,0); + + /* use length to minimize buffer shift*/ + n_samps_buf = 8*4096/sizeof(kffsamp_t); + n_samps_buf = nfft + 4*(nfft-n_imp_resp+1); + + if (verbose) fprintf(stderr,"bufsize=%d\n",(int)(sizeof(kffsamp_t)*n_samps_buf) ); + + + /*allocate space and initialize pointers */ + inbuf = (kffsamp_t*)malloc(sizeof(kffsamp_t)*n_samps_buf); + outbuf = (kffsamp_t*)malloc(sizeof(kffsamp_t)*n_samps_buf); + + idx_inbuf=0; + do{ + /* start reading at inbuf[idx_inbuf] */ + nread = fread( inbuf + idx_inbuf, sizeof(kffsamp_t), n_samps_buf - idx_inbuf,fin ); + + /* If nread==0, then this is a flush. + The total number of samples in input is idx_inbuf + nread . */ + nwrite = kiss_fastfir(cfg, inbuf, outbuf,nread,&idx_inbuf) * sizeof(kffsamp_t); + /* kiss_fastfir moved any unused samples to the front of inbuf and updated idx_inbuf */ + + if ( write(fdout, outbuf, nwrite) != nwrite ) { + perror("short write"); + exit(1); + } + }while ( nread ); + free(cfg); + free(inbuf); + free(outbuf); +} + +int main(int argc,char**argv) +{ + kffsamp_t * h; + int use_direct=0; + size_t nh,nfft=0; + FILE *fin=stdin; + FILE *fout=stdout; + FILE *filtfile=NULL; + while (1) { + int c=getopt(argc,argv,"n:h:i:o:vd"); + if (c==-1) break; + switch (c) { + case 'v': + verbose=1; + break; + case 'n': + nfft=atoi(optarg); + break; + case 'i': + fin = fopen(optarg,"rb"); + if (fin==NULL) { + perror(optarg); + exit(1); + } + break; + case 'o': + fout = fopen(optarg,"w+b"); + if (fout==NULL) { + perror(optarg); + exit(1); + } + break; + case 'h': + filtfile = fopen(optarg,"rb"); + if (filtfile==NULL) { + perror(optarg); + exit(1); + } + break; + case 'd': + use_direct=1; + break; + case '?': + fprintf(stderr,"usage options:\n" + "\t-n nfft: fft size to use\n" + "\t-d : use direct FIR filtering, not fast convolution\n" + "\t-i filename: input file\n" + "\t-o filename: output(filtered) file\n" + "\t-n nfft: fft size to use\n" + "\t-h filename: impulse response\n"); + exit (1); + default:fprintf(stderr,"bad %c\n",c);break; + } + } + if (filtfile==NULL) { + fprintf(stderr,"You must supply the FIR coeffs via -h\n"); + exit(1); + } + fseek(filtfile,0,SEEK_END); + nh = ftell(filtfile) / sizeof(kffsamp_t); + if (verbose) fprintf(stderr,"%d samples in FIR filter\n",(int)nh); + h = (kffsamp_t*)malloc(sizeof(kffsamp_t)*nh); + fseek(filtfile,0,SEEK_SET); + if (fread(h,sizeof(kffsamp_t),nh,filtfile) != nh) + fprintf(stderr,"short read on filter file\n"); + + fclose(filtfile); + + if (use_direct) + direct_file_filter( fin, fout, h,nh); + else + do_file_filter( fin, fout, h,nh,nfft); + + if (fout!=stdout) fclose(fout); + if (fin!=stdin) fclose(fin); + + return 0; +} +#endif diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftnd.c b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftnd.c new file mode 100644 index 00000000..d6c91243 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftnd.c @@ -0,0 +1,193 @@ + + +/* +Copyright (c) 2003-2004, Mark Borgerding + +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. + * 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. +*/ + +#include "kiss_fftnd.h" +#include "_kiss_fft_guts.h" + +struct kiss_fftnd_state{ + int dimprod; /* dimsum would be mighty tasty right now */ + int ndims; + int *dims; + kiss_fft_cfg *states; /* cfg states for each dimension */ + kiss_fft_cpx * tmpbuf; /*buffer capable of hold the entire input */ +}; + +kiss_fftnd_cfg kiss_fftnd_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem) +{ + kiss_fftnd_cfg st = NULL; + int i; + int dimprod=1; + size_t memneeded = sizeof(struct kiss_fftnd_state); + char * ptr; + + for (i=0;istates[i] */ + dimprod *= dims[i]; + } + memneeded += sizeof(int) * ndims;/* st->dims */ + memneeded += sizeof(void*) * ndims;/* st->states */ + memneeded += sizeof(kiss_fft_cpx) * dimprod; /* st->tmpbuf */ + + if (lenmem == NULL) {/* allocate for the caller*/ + st = (kiss_fftnd_cfg) malloc (memneeded); + } else { /* initialize supplied buffer if big enough */ + if (*lenmem >= memneeded) + st = (kiss_fftnd_cfg) mem; + *lenmem = memneeded; /*tell caller how big struct is (or would be) */ + } + if (!st) + return NULL; /*malloc failed or buffer too small */ + + st->dimprod = dimprod; + st->ndims = ndims; + ptr=(char*)(st+1); + + st->states = (kiss_fft_cfg *)ptr; + ptr += sizeof(void*) * ndims; + + st->dims = (int*)ptr; + ptr += sizeof(int) * ndims; + + st->tmpbuf = (kiss_fft_cpx*)ptr; + ptr += sizeof(kiss_fft_cpx) * dimprod; + + for (i=0;idims[i] = dims[i]; + kiss_fft_alloc (st->dims[i], inverse_fft, NULL, &len); + st->states[i] = kiss_fft_alloc (st->dims[i], inverse_fft, ptr,&len); + ptr += len; + } + /* +Hi there! + +If you're looking at this particular code, it probably means you've got a brain-dead bounds checker +that thinks the above code overwrites the end of the array. + +It doesn't. + +-- Mark + +P.S. +The below code might give you some warm fuzzies and help convince you. + */ + if ( ptr - (char*)st != (int)memneeded ) { + fprintf(stderr, + "################################################################################\n" + "Internal error! Memory allocation miscalculation\n" + "################################################################################\n" + ); + } + return st; +} + +/* + This works by tackling one dimension at a time. + + In effect, + Each stage starts out by reshaping the matrix into a DixSi 2d matrix. + A Di-sized fft is taken of each column, transposing the matrix as it goes. + +Here's a 3-d example: +Take a 2x3x4 matrix, laid out in memory as a contiguous buffer + [ [ [ a b c d ] [ e f g h ] [ i j k l ] ] + [ [ m n o p ] [ q r s t ] [ u v w x ] ] ] + +Stage 0 ( D=2): treat the buffer as a 2x12 matrix + [ [a b ... k l] + [m n ... w x] ] + + FFT each column with size 2. + Transpose the matrix at the same time using kiss_fft_stride. + + [ [ a+m a-m ] + [ b+n b-n] + ... + [ k+w k-w ] + [ l+x l-x ] ] + + Note fft([x y]) == [x+y x-y] + +Stage 1 ( D=3) treats the buffer (the output of stage D=2) as an 3x8 matrix, + [ [ a+m a-m b+n b-n c+o c-o d+p d-p ] + [ e+q e-q f+r f-r g+s g-s h+t h-t ] + [ i+u i-u j+v j-v k+w k-w l+x l-x ] ] + + And perform FFTs (size=3) on each of the columns as above, transposing + the matrix as it goes. The output of stage 1 is + (Legend: ap = [ a+m e+q i+u ] + am = [ a-m e-q i-u ] ) + + [ [ sum(ap) fft(ap)[0] fft(ap)[1] ] + [ sum(am) fft(am)[0] fft(am)[1] ] + [ sum(bp) fft(bp)[0] fft(bp)[1] ] + [ sum(bm) fft(bm)[0] fft(bm)[1] ] + [ sum(cp) fft(cp)[0] fft(cp)[1] ] + [ sum(cm) fft(cm)[0] fft(cm)[1] ] + [ sum(dp) fft(dp)[0] fft(dp)[1] ] + [ sum(dm) fft(dm)[0] fft(dm)[1] ] ] + +Stage 2 ( D=4) treats this buffer as a 4*6 matrix, + [ [ sum(ap) fft(ap)[0] fft(ap)[1] sum(am) fft(am)[0] fft(am)[1] ] + [ sum(bp) fft(bp)[0] fft(bp)[1] sum(bm) fft(bm)[0] fft(bm)[1] ] + [ sum(cp) fft(cp)[0] fft(cp)[1] sum(cm) fft(cm)[0] fft(cm)[1] ] + [ sum(dp) fft(dp)[0] fft(dp)[1] sum(dm) fft(dm)[0] fft(dm)[1] ] ] + + Then FFTs each column, transposing as it goes. + + The resulting matrix is the 3d FFT of the 2x3x4 input matrix. + + Note as a sanity check that the first element of the final + stage's output (DC term) is + sum( [ sum(ap) sum(bp) sum(cp) sum(dp) ] ) + , i.e. the summation of all 24 input elements. + +*/ +void kiss_fftnd(kiss_fftnd_cfg st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout) +{ + int i,k; + const kiss_fft_cpx * bufin=fin; + kiss_fft_cpx * bufout; + + /*arrange it so the last bufout == fout*/ + if ( st->ndims & 1 ) { + bufout = fout; + if (fin==fout) { + memcpy( st->tmpbuf, fin, sizeof(kiss_fft_cpx) * st->dimprod ); + bufin = st->tmpbuf; + } + }else + bufout = st->tmpbuf; + + for ( k=0; k < st->ndims; ++k) { + int curdim = st->dims[k]; + int stride = st->dimprod / curdim; + + for ( i=0 ; istates[k], bufin+i , bufout+i*curdim, stride ); + + /*toggle back and forth between the two buffers*/ + if (bufout == st->tmpbuf){ + bufout = fout; + bufin = st->tmpbuf; + }else{ + bufout = st->tmpbuf; + bufin = fout; + } + } +} diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftnd.h b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftnd.h new file mode 100644 index 00000000..42e7df5b --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftnd.h @@ -0,0 +1,18 @@ +#ifndef KISS_FFTND_H +#define KISS_FFTND_H + +#include "kiss_fft.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct kiss_fftnd_state * kiss_fftnd_cfg; + +kiss_fftnd_cfg kiss_fftnd_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem); +void kiss_fftnd(kiss_fftnd_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftndr.c b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftndr.c new file mode 100644 index 00000000..ba550dd1 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftndr.c @@ -0,0 +1,118 @@ +/* +Copyright (c) 2003-2004, Mark Borgerding + +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. + * 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. +*/ + +#include "kiss_fftndr.h" +#include "_kiss_fft_guts.h" +#define MAX(x,y) ( ( (x)<(y) )?(y):(x) ) + +struct kiss_fftndr_state +{ + int dimReal; + int dimOther; + kiss_fftr_cfg cfg_r; + kiss_fftnd_cfg cfg_nd; + void * tmpbuf; +}; + +static int prod(const int *dims, int ndims) +{ + int x=1; + while (ndims--) + x *= *dims++; + return x; +} + +kiss_fftndr_cfg kiss_fftndr_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem) +{ + kiss_fftndr_cfg st = NULL; + size_t nr=0 , nd=0,ntmp=0; + int dimReal = dims[ndims-1]; + int dimOther = prod(dims,ndims-1); + size_t memneeded; + + (void)kiss_fftr_alloc(dimReal,inverse_fft,NULL,&nr); + (void)kiss_fftnd_alloc(dims,ndims-1,inverse_fft,NULL,&nd); + ntmp = + MAX( 2*dimOther , dimReal+2) * sizeof(kiss_fft_scalar) // freq buffer for one pass + + dimOther*(dimReal+2) * sizeof(kiss_fft_scalar); // large enough to hold entire input in case of in-place + + memneeded = sizeof( struct kiss_fftndr_state ) + nr + nd + ntmp; + + if (lenmem==NULL) { + st = (kiss_fftndr_cfg) malloc(memneeded); + }else{ + if (*lenmem >= memneeded) + st = (kiss_fftndr_cfg)mem; + *lenmem = memneeded; + } + if (st==NULL) + return NULL; + memset( st , 0 , memneeded); + + st->dimReal = dimReal; + st->dimOther = dimOther; + st->cfg_r = kiss_fftr_alloc( dimReal,inverse_fft,st+1,&nr); + st->cfg_nd = kiss_fftnd_alloc(dims,ndims-1,inverse_fft, ((char*) st->cfg_r)+nr,&nd); + st->tmpbuf = (char*)st->cfg_nd + nd; + + return st; +} + +void kiss_fftndr(kiss_fftndr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata) +{ + int k1,k2; + int dimReal = st->dimReal; + int dimOther = st->dimOther; + int nrbins = dimReal/2+1; + + kiss_fft_cpx * tmp1 = (kiss_fft_cpx*)st->tmpbuf; + kiss_fft_cpx * tmp2 = tmp1 + MAX(nrbins,dimOther); + + // timedata is N0 x N1 x ... x Nk real + + // take a real chunk of data, fft it and place the output at correct intervals + for (k1=0;k1cfg_r, timedata + k1*dimReal , tmp1 ); // tmp1 now holds nrbins complex points + for (k2=0;k2cfg_nd, tmp2+k2*dimOther, tmp1); // tmp1 now holds dimOther complex points + for (k1=0;k1dimReal; + int dimOther = st->dimOther; + int nrbins = dimReal/2+1; + kiss_fft_cpx * tmp1 = (kiss_fft_cpx*)st->tmpbuf; + kiss_fft_cpx * tmp2 = tmp1 + MAX(nrbins,dimOther); + + for (k2=0;k2cfg_nd, tmp1, tmp2+k2*dimOther); + } + + for (k1=0;k1cfg_r,tmp1,timedata + k1*dimReal); + } +} diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftndr.h b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftndr.h new file mode 100644 index 00000000..38ec3ab0 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftndr.h @@ -0,0 +1,47 @@ +#ifndef KISS_NDR_H +#define KISS_NDR_H + +#include "kiss_fft.h" +#include "kiss_fftr.h" +#include "kiss_fftnd.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct kiss_fftndr_state *kiss_fftndr_cfg; + + +kiss_fftndr_cfg kiss_fftndr_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem); +/* + dims[0] must be even + + If you don't care to allocate space, use mem = lenmem = NULL +*/ + + +void kiss_fftndr( + kiss_fftndr_cfg cfg, + const kiss_fft_scalar *timedata, + kiss_fft_cpx *freqdata); +/* + input timedata has dims[0] X dims[1] X ... X dims[ndims-1] scalar points + output freqdata has dims[0] X dims[1] X ... X dims[ndims-1]/2+1 complex points +*/ + +void kiss_fftndri( + kiss_fftndr_cfg cfg, + const kiss_fft_cpx *freqdata, + kiss_fft_scalar *timedata); +/* + input and output dimensions are the exact opposite of kiss_fftndr +*/ + + +#define kiss_fftr_free free + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftr.c b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftr.c new file mode 100644 index 00000000..35448037 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftr.c @@ -0,0 +1,159 @@ +/* +Copyright (c) 2003-2004, Mark Borgerding + +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. + * 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. +*/ + +#include "kiss_fftr.h" +#include "_kiss_fft_guts.h" + +struct kiss_fftr_state{ + kiss_fft_cfg substate; + kiss_fft_cpx * tmpbuf; + kiss_fft_cpx * super_twiddles; +#ifdef USE_SIMD + void * pad; +#endif +}; + +kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem) +{ + int i; + kiss_fftr_cfg st = NULL; + size_t subsize, memneeded; + + if (nfft & 1) { + fprintf(stderr,"Real FFT optimization must be even.\n"); + return NULL; + } + nfft >>= 1; + + kiss_fft_alloc (nfft, inverse_fft, NULL, &subsize); + memneeded = sizeof(struct kiss_fftr_state) + subsize + sizeof(kiss_fft_cpx) * ( nfft * 3 / 2); + + if (lenmem == NULL) { + st = (kiss_fftr_cfg) KISS_FFT_MALLOC (memneeded); + } else { + if (*lenmem >= memneeded) + st = (kiss_fftr_cfg) mem; + *lenmem = memneeded; + } + if (!st) + return NULL; + + st->substate = (kiss_fft_cfg) (st + 1); /*just beyond kiss_fftr_state struct */ + st->tmpbuf = (kiss_fft_cpx *) (((char *) st->substate) + subsize); + st->super_twiddles = st->tmpbuf + nfft; + kiss_fft_alloc(nfft, inverse_fft, st->substate, &subsize); + + for (i = 0; i < nfft/2; ++i) { + double phase = + -3.14159265358979323846264338327 * ((double) (i+1) / nfft + .5); + if (inverse_fft) + phase *= -1; + kf_cexp (st->super_twiddles+i,phase); + } + return st; +} + +void kiss_fftr(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata) +{ + /* input buffer timedata is stored row-wise */ + int k,ncfft; + kiss_fft_cpx fpnk,fpk,f1k,f2k,tw,tdc; + + if ( st->substate->inverse) { + fprintf(stderr,"kiss fft usage error: improper alloc\n"); + exit(1); + } + + ncfft = st->substate->nfft; + + /*perform the parallel fft of two real signals packed in real,imag*/ + kiss_fft( st->substate , (const kiss_fft_cpx*)timedata, st->tmpbuf ); + /* The real part of the DC element of the frequency spectrum in st->tmpbuf + * contains the sum of the even-numbered elements of the input time sequence + * The imag part is the sum of the odd-numbered elements + * + * The sum of tdc.r and tdc.i is the sum of the input time sequence. + * yielding DC of input time sequence + * The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1... + * yielding Nyquist bin of input time sequence + */ + + tdc.r = st->tmpbuf[0].r; + tdc.i = st->tmpbuf[0].i; + C_FIXDIV(tdc,2); + CHECK_OVERFLOW_OP(tdc.r ,+, tdc.i); + CHECK_OVERFLOW_OP(tdc.r ,-, tdc.i); + freqdata[0].r = tdc.r + tdc.i; + freqdata[ncfft].r = tdc.r - tdc.i; +#ifdef USE_SIMD + freqdata[ncfft].i = freqdata[0].i = _mm_set1_ps(0); +#else + freqdata[ncfft].i = freqdata[0].i = 0; +#endif + + for ( k=1;k <= ncfft/2 ; ++k ) { + fpk = st->tmpbuf[k]; + fpnk.r = st->tmpbuf[ncfft-k].r; + fpnk.i = - st->tmpbuf[ncfft-k].i; + C_FIXDIV(fpk,2); + C_FIXDIV(fpnk,2); + + C_ADD( f1k, fpk , fpnk ); + C_SUB( f2k, fpk , fpnk ); + C_MUL( tw , f2k , st->super_twiddles[k-1]); + + freqdata[k].r = (kiss_fft_scalar)HALF_OF(f1k.r + tw.r); + freqdata[k].i = (kiss_fft_scalar) HALF_OF(f1k.i + tw.i); + freqdata[ncfft-k].r = (kiss_fft_scalar) HALF_OF(f1k.r - tw.r); + freqdata[ncfft-k].i = (kiss_fft_scalar) HALF_OF(tw.i - f1k.i); + } +} + +void kiss_fftri(kiss_fftr_cfg st,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata) +{ + /* input buffer timedata is stored row-wise */ + int k, ncfft; + + if (st->substate->inverse == 0) { + fprintf (stderr, "kiss fft usage error: improper alloc\n"); + exit (1); + } + + ncfft = st->substate->nfft; + + st->tmpbuf[0].r = freqdata[0].r + freqdata[ncfft].r; + st->tmpbuf[0].i = freqdata[0].r - freqdata[ncfft].r; + C_FIXDIV(st->tmpbuf[0],2); + + for (k = 1; k <= ncfft / 2; ++k) { + kiss_fft_cpx fk, fnkc, fek, fok, tmp; + fk = freqdata[k]; + fnkc.r = freqdata[ncfft - k].r; + fnkc.i = -freqdata[ncfft - k].i; + C_FIXDIV( fk , 2 ); + C_FIXDIV( fnkc , 2 ); + + C_ADD (fek, fk, fnkc); + C_SUB (tmp, fk, fnkc); + C_MUL (fok, tmp, st->super_twiddles[k-1]); + C_ADD (st->tmpbuf[k], fek, fok); + C_SUB (st->tmpbuf[ncfft - k], fek, fok); +#ifdef USE_SIMD + st->tmpbuf[ncfft - k].i *= _mm_set1_ps(-1.0); +#else + st->tmpbuf[ncfft - k].i *= -1; +#endif + } + kiss_fft (st->substate, st->tmpbuf, (kiss_fft_cpx *) timedata); +} diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftr.h b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftr.h new file mode 100644 index 00000000..72e5a577 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/kiss_fftr.h @@ -0,0 +1,46 @@ +#ifndef KISS_FTR_H +#define KISS_FTR_H + +#include "kiss_fft.h" +#ifdef __cplusplus +extern "C" { +#endif + + +/* + + Real optimized version can save about 45% cpu time vs. complex fft of a real seq. + + + + */ + +typedef struct kiss_fftr_state *kiss_fftr_cfg; + + +kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem, size_t * lenmem); +/* + nfft must be even + + If you don't care to allocate space, use mem = lenmem = NULL +*/ + + +void kiss_fftr(kiss_fftr_cfg cfg,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata); +/* + input timedata has nfft scalar points + output freqdata has nfft/2+1 complex points +*/ + +void kiss_fftri(kiss_fftr_cfg cfg,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata); +/* + input freqdata has nfft/2+1 complex points + output timedata has nfft scalar points +*/ + +#define kiss_fftr_free free + +#ifdef __cplusplus +} +#endif +#endif diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/psdpng.c b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/psdpng.c new file mode 100644 index 00000000..d11a54fd --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/third-party/kiss_fft130/tools/psdpng.c @@ -0,0 +1,235 @@ +/* +Copyright (c) 2003-2004, Mark Borgerding + +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. + * 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. +*/ + +#include +#include +#include +#include +#include +#include + +#include "kiss_fft.h" +#include "kiss_fftr.h" + +int nfft=1024; +FILE * fin=NULL; +FILE * fout=NULL; + +int navg=20; +int remove_dc=0; +int nrows=0; +float * vals=NULL; +int stereo=0; + +static +void config(int argc,char** argv) +{ + while (1) { + int c = getopt (argc, argv, "n:r:as"); + if (c == -1) + break; + switch (c) { + case 'n': nfft=(int)atoi(optarg);break; + case 'r': navg=(int)atoi(optarg);break; + case 'a': remove_dc=1;break; + case 's': stereo=1;break; + case '?': + fprintf (stderr, "usage options:\n" + "\t-n d: fft dimension(s) [1024]\n" + "\t-r d: number of rows to average [20]\n" + "\t-a : remove average from each fft buffer\n" + "\t-s : input is stereo, channels will be combined before fft\n" + "16 bit machine format real input is assumed\n" + ); + default: + fprintf (stderr, "bad %c\n", c); + exit (1); + break; + } + } + if ( optind < argc ) { + if (strcmp("-",argv[optind]) !=0) + fin = fopen(argv[optind],"rb"); + ++optind; + } + + if ( optind < argc ) { + if ( strcmp("-",argv[optind]) !=0 ) + fout = fopen(argv[optind],"wb"); + ++optind; + } + if (fin==NULL) + fin=stdin; + if (fout==NULL) + fout=stdout; +} + +#define CHECKNULL(p) if ( (p)==NULL ) do { fprintf(stderr,"CHECKNULL failed @ %s(%d): %s\n",__FILE__,__LINE__,#p );exit(1);} while(0) + +typedef struct +{ + png_byte r; + png_byte g; + png_byte b; +} rgb_t; + +static +void val2rgb(float x,rgb_t *p) +{ + const double pi = 3.14159265358979; + p->g = (int)(255*sin(x*pi)); + p->r = (int)(255*abs(sin(x*pi*3/2))); + p->b = (int)(255*abs(sin(x*pi*5/2))); + //fprintf(stderr,"%.2f : %d,%d,%d\n",x,(int)p->r,(int)p->g,(int)p->b); +} + +static +void cpx2pixels(rgb_t * res,const float * fbuf,size_t n) +{ + size_t i; + float minval,maxval,valrange; + minval=maxval=fbuf[0]; + + for (i = 0; i < n; ++i) { + if (fbuf[i] > maxval) maxval = fbuf[i]; + if (fbuf[i] < minval) minval = fbuf[i]; + } + + fprintf(stderr,"min ==%f,max=%f\n",minval,maxval); + valrange = maxval-minval; + if (valrange == 0) { + fprintf(stderr,"min == max == %f\n",minval); + exit (1); + } + + for (i = 0; i < n; ++i) + val2rgb( (fbuf[i] - minval)/valrange , res+i ); +} + +static +void transform_signal(void) +{ + short *inbuf; + kiss_fftr_cfg cfg=NULL; + kiss_fft_scalar *tbuf; + kiss_fft_cpx *fbuf; + float *mag2buf; + int i; + int n; + int avgctr=0; + + int nfreqs=nfft/2+1; + + CHECKNULL( cfg=kiss_fftr_alloc(nfft,0,0,0) ); + CHECKNULL( inbuf=(short*)malloc(sizeof(short)*2*nfft ) ); + CHECKNULL( tbuf=(kiss_fft_scalar*)malloc(sizeof(kiss_fft_scalar)*nfft ) ); + CHECKNULL( fbuf=(kiss_fft_cpx*)malloc(sizeof(kiss_fft_cpx)*nfreqs ) ); + CHECKNULL( mag2buf=(float*)malloc(sizeof(float)*nfreqs ) ); + + memset(mag2buf,0,sizeof(mag2buf)*nfreqs); + + while (1) { + if (stereo) { + n = fread(inbuf,sizeof(short)*2,nfft,fin); + if (n != nfft ) + break; + for (i=0;i + + +/** For Tremolo: lets you smoothly adjust phase and skew +* turns a ramp into a continuously variable dual slope ramp: +* at one extreme it is increasing ramp +* at other extreme it is decreasing +* in between it it triangle +*/ + +class AsymRampShaperParams +{ +public: + float k; //when input < k we are in the rising phase + float a1; // in rising phase, f(x) = a1 * x; + float a2; + float b2; // in falling phase, f(x) = a2 * x + b2 + float phaseOffset; // between 0 and 1 + + bool valid() + { + bool ret = true; + ret &= (k > 0 && k < 1); + ret &= (phaseOffset >= 0 && phaseOffset <= 1); + return ret; + } +}; + + +class AsymRampShaper +{ +public: + + /* skew = -1..1, zero is triangle + * phase = -1..1, zero is no shift + */ + static void setup(AsymRampShaperParams& params, float skew, float phase) + { + // first deal with phase + if (phase < 0) phase += 1; // adjust phase into range convenient for us + // (it all wraps around two pi anyway) + + assert(phase >= 0 && phase <= 1); + params.phaseOffset = phase; + + // now skew + assert(skew > -1 && skew < 1); + params.k = (skew + 1) / (float) 2; // between 0 and 1 + + params.a1 = float(1.0 / params.k); // as x goes 0..k, f(x) 0..1 + params.a2 = float(1.0 / (params.k - 1.0)); + params.b2 = -params.a2; + + assert(params.valid()); + //char buf[512]; + //sprintf(buf, "set k=%f a1=%f a2=%f b2=%f\n", params.k, params.a1, params.a2, params.b2); + //DebugUtil::trace(buf); + } + + static float proc_1(AsymRampShaperParams& params, float input) + { + assert(params.valid()); + input += params.phaseOffset; + if (input > 1) input -= 1; + + float ret = 0; + if (input < params.k) { + ret = input * params.a1; + } else { + ret = params.b2 + input * params.a2; + } + assert(ret >= 0 && ret <= 1); + return ret; + } + +}; + + diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/utils/AudioMath.cpp b/plugins/community/repos/squinkylabs-plug1/dsp/utils/AudioMath.cpp new file mode 100644 index 00000000..caad2afc --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/utils/AudioMath.cpp @@ -0,0 +1,87 @@ +#include +#include + +#include "AudioMath.h" +#include "LookupTable.h" +#include "ObjectCache.h" + +const double AudioMath::Pi = 3.1415926535897932384626433832795028841971; +const double AudioMath::Pi_2 = 1.5707963267948966192313216916397514420986; +const double AudioMath::Ln2 = 0.693147180559945309417; +const double AudioMath::Ln10 = 2.30258509299404568402; +const double AudioMath::E = 2.71828182845904523536; + +std::function AudioMath::makeFunc_Sin() +{ + return [](double x) { + return std::sin(x * 2 * Pi); + }; +} + +std::function AudioMath::makeFunc_Exp(double xMin, double xMax, double yMin, double yMax) +{ + const double a = (std::log(yMax) - log(yMin)) / (xMax - xMin); + const double b = log(yMin) - a * xMin; + return [a, b](double d) { + return std::exp(a * d + b); + }; +} + +std::function AudioMath::makeFunc_AudioTaper(double dbAtten) +{ + assert(dbAtten < 0); + + const double gainAtQuarter = gainFromDb(dbAtten); + std::function linearFunc; + std::function expFunc; + + { + // for linear part at bottom + const double x0 = 0; + const double x1 = .25; + const double y0 = 0; + const double y1 = gainAtQuarter; + const double a = (y1 - y0) / (x1 - x0); + const double b = y0 - a * x0; + linearFunc = [a, b](double d) { + return a * d + b; + }; + } + + { + // for exp part on top + const double xMin = .25; + const double yMin = gainAtQuarter; + const double xMax = 1; + const double yMax = 1; + expFunc = makeFunc_Exp(xMin, xMax, yMin, yMax); + } + + return [linearFunc, expFunc](double d) { + return (d <= .25) ? linearFunc(d) : expFunc(d); + }; +} + +AudioMath::ScaleFun AudioMath::makeBipolarAudioScaler(float y0, float y1) +{ + // Use a cached singleton for the lookup table - don't need to have unique copies + std::shared_ptr> lookup = ObjectCache::getBipolarAudioTaper(); + const float x0 = -5; + const float x1 = 5; + const float a = (y1 - y0) / (x1 - x0); + const float b = y0 - a * x0; + + // Notice how lambda captures the smart pointer. Now + // lambda owns a reference to it. + return [a, b, lookup](float cv, float knob, float trim) { + auto mappedTrim = LookupTable::lookup(*lookup, trim); + float x = cv * mappedTrim + knob; + x = std::max(-5.0f, x); + x = std::min(5.0f, x); + return a * x + b; + }; +} + + // declare some test variables here +int _numLookupParams = 0; +int _numBiquads = 0; \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/utils/AudioMath.h b/plugins/community/repos/squinkylabs-plug1/dsp/utils/AudioMath.h new file mode 100644 index 00000000..fa9cc2e8 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/utils/AudioMath.h @@ -0,0 +1,92 @@ +#pragma once +#include +#include +#include + +class AudioMath +{ +public: + AudioMath() = delete; // we are only static + static const double Pi; + static const double Pi_2; // Pi / 2 + static const double Ln2; + static const double Ln10; + static const double E; + + static bool closeTo(double x, double y, double tolerance) + { + const bool ret = std::abs(x - y) < tolerance; + return ret; + } + + static double db(double g) + { + return 20 * log(g) / Ln10; + } + + static double gainFromDb(double db) + { + return std::exp(Ln10 * db / 20.0); + } + + /** + * Returns a function that generates one period of sin for x = {0..1}. + * Range (output) is -1 to 1. + */ + static std::function makeFunc_Sin(); + + /* + * Returns a function that generates an exponential defined by two points + * At input = xMin, output will be yMin. + * At input = xMax, output will be yMax. + */ + static std::function makeFunc_Exp(double xMin, double xMax, double yMin, double yMax); + + /** + * Returns a function for an "audio taper" attenuator gain. + * function is pure exponential for x > .25, linear for x < .25 + * At input 1, output is 1 + * At input .25, output is the gain corresponding to adAtten + * At input 0, the output is zero. + */ + static std::function makeFunc_AudioTaper(double dbAtten); + + /** + * ScaleFun is a function the combines CV, knob, and trim into a voltage. + * Typically a ScaleFun is like an "attenuverter" + */ + template + using ScaleFun = std::function; + + /** + * Create a ScaleFun with the following properties: + * 1) The values are combined with the typical formula: x = cv * trim + knob; + * 2) x is clipped between -5 and 5 + * 3) range is then interpolated between y0, and y1. + * + * This particular function is used when knobs are -5..5, + * and CV range is -5..5. + */ + template + static ScaleFun makeLinearScaler(T y0, T y1) + { + const T x0 = -5; + const T x1 = 5; + const T a = (y1 - y0) / (x1 - x0); + const T b = y0 - a * x0; + return [a, b](T cv, T knob, T trim) { + T x = cv * trim + knob; + x = std::max(-5.0f, x); + x = std::min(5.0f, x); + return a * x + b; + }; + } + + /** + * Generates a scale function for an audio taper attenuverter. + * Details the same as makeLinearScaler except that the CV + * scaling will be exponential for most values, becoming + * linear near zero. + */ + static ScaleFun makeBipolarAudioScaler(float y0, float y1); +}; diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/utils/LookupTable.h b/plugins/community/repos/squinkylabs-plug1/dsp/utils/LookupTable.h new file mode 100644 index 00000000..6359681b --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/utils/LookupTable.h @@ -0,0 +1,233 @@ +#pragma once + +#include +#include +#include +#include + +#include "AudioMath.h" + +template class LookupTableParams; +/* Lookup table with evenly spaced lookup "bins" + * Uses linear interpolation + */ + + // TODO: templatize on size? +template +class LookupTable +{ + +public: + LookupTable() = delete; // we are only static + + /** + * lookup does the table lookup. + * input must be in the range specified at table creation time, otherwise + * it will be limited to the range. + * If allowOutsideDomain, will assert when input must be limited. + */ + static T lookup(const LookupTableParams& params, T input, bool allowOutsideDomain = false); + + /** + * init will create the entries in the lookup table + * bins is the number of entries desired in the lookup table. + * more bins means greater accuracy, but greater memory usage also. + * xMin is the minimum input that will be passed to lookup() + * xMax is the maximum input that will be passed to lookup(). + * xMin..xMax is the domain of the function. + * f is a continuous function that the lookup table will approximate. + */ + static void init(LookupTableParams& params, int bins, T xMin, T xMax, std::function f); + + /** + * initDiscrete will make a table that interpolates between discrete values. + * numEntries is the number of "points" that will be interpolated. + * entries are the discrete y values to be interpolated. + * Very important: x values are assumed to be 0..numEntries-1. That's because + * this lookup table only works with uniform x value. + */ + static void initDiscrete(LookupTableParams& params, int numEntries, const T * yEntries); +private: + static int cvtt(T *); + +#ifdef _DEBUG + static void checkInput(const LookupTableParams& params, const T *in, int sampleFrames) + { + for (int i = 0; i < sampleFrames; ++i) { + f_t input = in[i]; + assert(input >= params.xMin && input <= params.xMax); + } + } +#else +#define checkInput __noop +#endif + +}; + +template +inline T LookupTable::lookup(const LookupTableParams& params, T input, bool allowOutsideDomain) +{ + assert(allowOutsideDomain || (input >= params.xMin && input <= params.xMax)); + // won't happen in the field, + // as assertions are disabled for release. + input = std::min(input, params.xMax); + input = std::max(input, params.xMin); + assert(params.isValid()); + assert(input >= params.xMin && input <= params.xMax); + + // need to scale by bins + T scaledInput = input * params.a + params.b; + assert(params.a != 0); + + int input_int = cvtt(&scaledInput); + T input_float = scaledInput - input_int; + + // Unfortunately, when we use float instead of doubles, + // numeric precision issues get us "out of range". So + // here we clamp to range. It would be more efficient if we didn't do this. + // Perhaps the calculation of a and b could be done so this can't happen? + if (input_float < 0) { + input_float = 0; + } else if (input_float > 1) { + input_float = 1; + } + + assert(input_float >= 0 && input_float <= 1); + assert(input_int >= 0 && input_int <= params.numBins_i); + + T * entry = params.entries + (2 * input_int); + T x = entry[0]; + x += input_float * entry[1]; + return x; +} + +template +inline void LookupTable::init(LookupTableParams& params, + int bins, T x0In, T x1In, std::function f) +{ + params.alloc(bins); + + // f(x0 = ax + 0 == index + // f(x0) = 0 + // f(x1) = bins + params.a = (T) bins / (x1In - x0In); + params.b = -params.a * x0In; + + if (x0In == 0) assert(params.b == 0); + + { + assert(AudioMath::closeTo((params.a * x0In + params.b), 0, .0001)); + assert(AudioMath::closeTo((params.a * x1In + params.b), bins, .0001)); + } + + for (int i = 0; i <= bins; ++i) { + double x0 = (i - params.b) / params.a; + double x1 = ((i + 1) - params.b) / params.a; + + double y0 = f(x0); + double y1 = f(x1); + double slope = y1 - y0; + T * entry = params.entries + (2 * i); + entry[0] = (T) y0; + entry[1] = (T) slope; + } + params.xMin = x0In; + params.xMax = x1In; +} + +template +inline void LookupTable::initDiscrete(LookupTableParams& params, int numEntries, const T * entries) +{ + params.alloc(numEntries); + // Since this version assumes x = 0, 1, 2 .... + // We don't need an interpolation to find which bin we are in + params.a = 1; + params.b = 0; + + for (int i = 0; i < numEntries; ++i) { + int x0 = i; + int x1 = i + 1; + + // Ugh - this will need to get resolved when we "officially" make + // all table support x values outside their range. But for now we + // have a problem: if x == Xmax (numEntries), we need an extra "bin" + // at the end to look up that value, so here we make a flat bit of xMax. + if (i == (numEntries - 1)) { + --x1; + } + + double y0 = entries[x0]; + double y1 = entries[x1]; + double slope = y1 - y0; + + T * entry = params.entries + (2 * i); + entry[0] = (T) y0; + entry[1] = (T) slope; + } + params.xMin = 0; + params.xMax = T(numEntries - 1); +} + +template<> +inline int LookupTable::cvtt(double* input) +{ + auto x = _mm_load_sd(input); + return _mm_cvttsd_si32(x); +} + +template<> +inline int LookupTable::cvtt(float* input) +{ + auto x = _mm_load_ss(input); + return _mm_cvttss_si32(x); +} + +/***************************************************************************/ +extern int _numLookupParams; + +template +class LookupTableParams +{ +public: + int numBins_i = 0; // numBins will be number of entries (pairs of values) + T a = 0, b = 0; // lookup index = a * x + b, so domain of x can be whatever we want + + T * entries = 0; // each entry is value, slope + T xMin = 0; // minimum x value we will accept as input + T xMax = 0; // max x value we will accept as input + + LookupTableParams() + { + ++_numLookupParams; + } + LookupTableParams(const LookupTableParams&) = delete; + LookupTableParams& operator=(const LookupTableParams&) = delete; + + ~LookupTableParams() + { + free(entries); + --_numLookupParams; + } + + bool isValid() const + { + return ((entries != 0) && + (xMax > xMin) && + (numBins_i > 0) + ); + } + + void alloc(int bins) + { + if (entries) free(entries); + // allocate one extra, so we can index all the way to the end... + entries = (T *) malloc((bins + 1) * 2 * sizeof(T)); + numBins_i = bins; + a = 0; + b = 0; + } + +}; + + + diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/utils/LookupTableFactory.h b/plugins/community/repos/squinkylabs-plug1/dsp/utils/LookupTableFactory.h new file mode 100644 index 00000000..6ef5b4b4 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/utils/LookupTableFactory.h @@ -0,0 +1,64 @@ +#pragma once + +#include "LookupTable.h" + +// TODO: this class should not be templatized. the functions should +template +class LookupTableFactory +{ +public: + static void makeBipolarAudioTaper(LookupTableParams& params); + static double audioTaperKnee() + { + return -24; + } + /** + * Factory methods for exp base 2 + * domain = 0..10 + * range = 20..20k (for now). but should be .001 to 1.0? + */ + static void makeExp2(LookupTableParams& params); + static double expYMin() + { + return 4; + } + static double expYMax() + { + return 40000; + } + static double expXMin() + { + return std::log2(expYMin()); + } + static double expXMax() + { + return std::log2(expYMax()); + } +}; + + +template +inline void LookupTableFactory::makeExp2(LookupTableParams& params) +{ + // 128 not enough for one cent + const int bins = 256; + const T xMin = (T) std::log2(expYMin()); + const T xMax = (T) std::log2(expYMax()); + assert(xMin < xMax); + LookupTable::init(params, bins, xMin, xMax, [](double x) { + return std::pow(2, x); + }); +} + +template +inline void LookupTableFactory::makeBipolarAudioTaper(LookupTableParams& params) +{ + const int bins = 32; + std::function audioTaper = AudioMath::makeFunc_AudioTaper(audioTaperKnee()); + const T xMin = -1; + const T xMax = 1; + LookupTable::init(params, bins, xMin, xMax, [audioTaper](double x) { + return (x >= 0) ? audioTaper(x) : -audioTaper(-x); + }); + +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/utils/ObjectCache.cpp b/plugins/community/repos/squinkylabs-plug1/dsp/utils/ObjectCache.cpp new file mode 100644 index 00000000..2fee4982 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/utils/ObjectCache.cpp @@ -0,0 +1,95 @@ + +#include + +#include "AudioMath.h" +#include "LookupTableFactory.h" +#include "ObjectCache.h" + +template +std::shared_ptr> ObjectCache::getBipolarAudioTaper() +{ + std::shared_ptr< LookupTableParams> ret = bipolarAudioTaper.lock(); + if (!ret) { + ret = std::make_shared>(); + LookupTableFactory::makeBipolarAudioTaper(*ret); + bipolarAudioTaper = ret; + } + return ret; +} + +template +std::shared_ptr> ObjectCache::getSinLookup() +{ + std::shared_ptr< LookupTableParams> ret = sinLookupTable.lock(); + if (!ret) { + ret = std::make_shared>(); + std::function f = AudioMath::makeFunc_Sin(); + // Used to use 4096, but 256 gives about 80db snr, so let's save memory + LookupTable::init(*ret, 256, 0, 1, f); + sinLookupTable = ret; + } + return ret; +} + +template +std::shared_ptr> ObjectCache::getExp2() +{ + std::shared_ptr< LookupTableParams> ret = exp2.lock(); + if (!ret) { + ret = std::make_shared>(); + LookupTableFactory::makeExp2(*ret); + exp2 = ret; + } + return ret; +} + + + +template +std::shared_ptr> ObjectCache::getDb2Gain() +{ + std::shared_ptr< LookupTableParams> ret = db2Gain.lock(); + if (!ret) { + ret = std::make_shared>(); + LookupTable::init(*ret, 32, -80, 20, [](double x) { + return AudioMath::gainFromDb(x); + }); + db2Gain = ret; + } + return ret; +} + + +template +std::shared_ptr> ObjectCache::getTanh5() +{ + std::shared_ptr< LookupTableParams> ret = tanh5.lock(); + if (!ret) { + ret = std::make_shared>(); + LookupTable::init(*ret, 256, -5, 5, [](double x) { + return std::tanh(x); + }); + tanh5 = ret; + } + return ret; +} + +// The weak pointer that hold our singletons. +template +std::weak_ptr> ObjectCache::bipolarAudioTaper; + +template +std::weak_ptr> ObjectCache::sinLookupTable; + +template +std::weak_ptr> ObjectCache::exp2; + +template +std::weak_ptr> ObjectCache::db2Gain; + +template +std::weak_ptr> ObjectCache::tanh5; + +// Explicit instantiation, so we can put implementation into .cpp file +template class ObjectCache; +template class ObjectCache; diff --git a/plugins/community/repos/squinkylabs-plug1/dsp/utils/ObjectCache.h b/plugins/community/repos/squinkylabs-plug1/dsp/utils/ObjectCache.h new file mode 100644 index 00000000..78c63568 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/dsp/utils/ObjectCache.h @@ -0,0 +1,50 @@ +#pragma once + + +#include "LookupTable.h" + +/** + * This class creates objects and caches them. + * Objects in the cache only stay alive as long as there is a reference to the object, + * If all refs go away, the object will be deleted. + * + * All accessors return shared pointers to make the lifetime management easy. + * Clients are free to use the shared_ptr directly, or may use the raw pointer, + * as long as the client holds onto the reference. + */ + +template +class ObjectCache +{ +public: + static std::shared_ptr> getBipolarAudioTaper(); + static std::shared_ptr> getSinLookup(); + + /** + * 2 ** x, not scaled or shifted in any manner, but tables + * selected to span a "reasonable" range when used as frequencies + (4 Hz to 40kHz) + * Exp2 lookup is 2 ** x. + * Domain = {2 .. 15(+)} + * Range = {4 .. 40000} + * accuracy = 1 cent (1V/octave) + */ + static std::shared_ptr> getExp2(); + static std::shared_ptr> getDb2Gain(); + + /** + * tanh, unscaled, from -5 to 5 + */ + static std::shared_ptr> getTanh5(); + +private: + /** + * Cache uses weak pointers. This allows the cached objects to be + * freed when the last client reference goes away. + */ + static std::weak_ptr> bipolarAudioTaper; + static std::weak_ptr> sinLookupTable; + static std::weak_ptr> exp2; + static std::weak_ptr> db2Gain; + static std::weak_ptr> tanh5; +}; diff --git a/plugins/community/repos/squinkylabs-plug1/gfx/ThreadBoost.xd b/plugins/community/repos/squinkylabs-plug1/gfx/ThreadBoost.xd new file mode 100644 index 00000000..2f9fcbfd Binary files /dev/null and b/plugins/community/repos/squinkylabs-plug1/gfx/ThreadBoost.xd differ diff --git a/plugins/community/repos/squinkylabs-plug1/gfx/booty-panel-design.xd b/plugins/community/repos/squinkylabs-plug1/gfx/booty-panel-design.xd new file mode 100644 index 00000000..80da6266 Binary files /dev/null and b/plugins/community/repos/squinkylabs-plug1/gfx/booty-panel-design.xd differ diff --git a/plugins/community/repos/squinkylabs-plug1/gfx/chopper-panel-design.xd b/plugins/community/repos/squinkylabs-plug1/gfx/chopper-panel-design.xd new file mode 100644 index 00000000..f27a2e25 Binary files /dev/null and b/plugins/community/repos/squinkylabs-plug1/gfx/chopper-panel-design.xd differ diff --git a/plugins/community/repos/squinkylabs-plug1/gfx/formants_panel.xd b/plugins/community/repos/squinkylabs-plug1/gfx/formants_panel.xd new file mode 100644 index 00000000..53c45a4f Binary files /dev/null and b/plugins/community/repos/squinkylabs-plug1/gfx/formants_panel.xd differ diff --git a/plugins/community/repos/squinkylabs-plug1/gfx/noun_938401.svg b/plugins/community/repos/squinkylabs-plug1/gfx/noun_938401.svg new file mode 100644 index 00000000..8e711ed4 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/gfx/noun_938401.svg @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/gfx/vocal-anim-panel.xd b/plugins/community/repos/squinkylabs-plug1/gfx/vocal-anim-panel.xd new file mode 100644 index 00000000..ddd12e99 Binary files /dev/null and b/plugins/community/repos/squinkylabs-plug1/gfx/vocal-anim-panel.xd differ diff --git a/plugins/community/repos/squinkylabs-plug1/make.objects b/plugins/community/repos/squinkylabs-plug1/make.objects new file mode 100644 index 00000000..38a9a264 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/make.objects @@ -0,0 +1,24 @@ +ALL_OBJ= \ + dsp/fft/FFT.o \ + dsp/fft/FFTCrossFader.o \ + dsp/fft/FFTData.o \ + dsp/filters/ButterworthFilterDesigner.o \ + dsp/filters/FormantTables2.o \ + dsp/filters/HilbertFilterDesigner.o \ + dsp/third-party/falco/DspFilter.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/BootyModule.o \ + src/CPU_Hog.o \ + src/ColoredNoiseModule.o \ + src/Squinky.o \ + src/ThreadBoost.o \ + src/TremoloModule.o \ + src/VocalFilterModule.o \ + src/VocalModule.o + +# pboost/pboost.o diff --git a/plugins/community/repos/squinkylabs-plug1/makefile.msvc b/plugins/community/repos/squinkylabs-plug1/makefile.msvc new file mode 100644 index 00000000..64283b1e --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/makefile.msvc @@ -0,0 +1,9 @@ +SLUG=squinkylabs-plug1 + +include ../../../../dep/yac/install_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/ + +include make.objects + +include ../../../build_plugin.mk diff --git a/plugins/community/repos/squinkylabs-plug1/pboost/Makefile b/plugins/community/repos/squinkylabs-plug1/pboost/Makefile new file mode 100644 index 00000000..db28bc01 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/pboost/Makefile @@ -0,0 +1,9 @@ + +LDFLAGS += -static -lws2_32 -lPsapi + +pboost.exe : build/pboost.obj + $(CXX) -o $@ $^ $(LDFLAGS) + +build/pboost.obj : pboost.cpp + mkdir build -p + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/plugins/community/repos/squinkylabs-plug1/pboost/pboost.cpp b/plugins/community/repos/squinkylabs-plug1/pboost/pboost.cpp new file mode 100644 index 00000000..838dda00 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/pboost/pboost.cpp @@ -0,0 +1,260 @@ +/** + * This is an unfinished experiment. + * It's a windows only command line program that + * find the Rack process in memory, and sets its + * priority class to realtime. + */ + +#include +#include +#include +#include + +class ProcessNameAndHandle +{ +public: + std::string name; + HANDLE handle = INVALID_HANDLE_VALUE; +}; + +ProcessNameAndHandle getProcessNameAndHandle(DWORD pid); +bool setRealtimePriority(HANDLE pHandle); +bool enablePrivilege(HANDLE hProcess, const char * privilege); +std::string GetLastErrorAsString(); + +int main(int argc, char** argv) +{ + + HANDLE realHandle = GetCurrentProcess(); + bool b = enablePrivilege(realHandle, SE_DEBUG_NAME); + printf("try set debug on us: %d\n", b); + if (!b) { + // TODO: we should re-run here + printf("can't get debug right from system. Try running as admin\n"); + fflush(stdout); + + // Spawn a copy of ourselves, via ShellExecuteEx(). + // The "runas" verb is important because that's what + // internally triggers Windows to open up a UAC prompt. + // HANDLE child = ShellExecuteEx(argc, argv, "runas"); + SHELLEXECUTEINFO sinfo; + memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO)); + sinfo.cbSize = sizeof(SHELLEXECUTEINFO); + sinfo.fMask = SEE_MASK_FLAG_DDEWAIT | + SEE_MASK_NOCLOSEPROCESS; + sinfo.hwnd = NULL; + sinfo.lpFile = argv[0]; + sinfo.lpParameters = ""; + sinfo.lpVerb = "runas"; // <<-- this is what makes a UAC prompt show up + sinfo.nShow = SW_SHOWMAXIMIZED; + + // The only way to get a UAC prompt to show up + // is by calling ShellExecuteEx() with the correct + // SHELLEXECUTEINFO struct. Non privlidged applications + // cannot open/start a UAC prompt by simply spawning + // a process that has the correct XML manifest. + BOOL result = ShellExecuteEx(&sinfo); + if (!result) { + printf("re-exec as admin failed\n"); + return -1; + } + + // HINSTANCE appInstance = sinfo.hInstApp; + + + printf("exec worked. hp=%x\n", sinfo.hProcess); + fflush(stdout); + // User accepted UAC prompt (gave permission). + // The unprivileged parent should wait for + // the privileged child to finish. + WaitForSingleObject(sinfo.hProcess, INFINITE); + printf("orig proc finished waiting\n"); + + DWORD exitCode=666; + GetExitCodeProcess(sinfo.hProcess, &exitCode); + printf("EXIT CODE %d\n", exitCode); + CloseHandle(sinfo.hProcess); + } + + // here were able to set debug + DWORD aProcesses[1024], cbNeeded, cProcesses; + unsigned int i; + + if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded)) { + printf("enum proc failed\n"); + return 1; + } + + + // Calculate how many process identifiers were returned. + cProcesses = cbNeeded / sizeof(DWORD); + + // find the pid for Rack. + HANDLE rackProcessHandle = INVALID_HANDLE_VALUE; + std::string rackName("Rack.exe"); + for (i = 0; i < cProcesses; i++) { + if (aProcesses[i] != 0) { + //PrintProcessNameAndID( aProcesses[i] ); + ProcessNameAndHandle proc = getProcessNameAndHandle(aProcesses[i]); + if (proc.name == rackName) { + rackProcessHandle = proc.handle; + } else { + CloseHandle(proc.handle); + } + } + } + + printf("rack pid = %d\n", rackProcessHandle); + if (rackProcessHandle == INVALID_HANDLE_VALUE) { + printf("could not find rack process\n"); + return -1; + } + + bool bSet = setRealtimePriority(rackProcessHandle); + CloseHandle(rackProcessHandle); +} + + + +/** + * + */ +ProcessNameAndHandle getProcessNameAndHandle(DWORD processID) +{ + TCHAR szProcessName[MAX_PATH] = TEXT(""); + + // Get a handle to the process. + HANDLE hProcess = OpenProcess(PROCESS_SET_INFORMATION | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + FALSE, processID); + + // Get the process name + if (NULL != hProcess) { + HMODULE hMod; + DWORD cbNeeded; + + if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), + &cbNeeded)) { + GetModuleBaseName(hProcess, hMod, szProcessName, + sizeof(szProcessName) / sizeof(TCHAR)); + } else printf("could not enum proc\n"); + } else { + return {}; + } + + // Release the handle to the process. + // CloseHandle(hProcess); + return {szProcessName, hProcess}; +} + +#if 0 +// Returns the last Win32 error, in string format. Returns an empty string if there is no error.//Returns +std::string GetLastErrorAsString() +{ + //Get the error message, if any. + DWORD errorMessageID = ::GetLastError(); + if (errorMessageID == 0) + return std::string(); //No error message has been recorded + + LPSTR messageBuffer = nullptr; + size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR) &messageBuffer, 0, NULL); + + std::string message(messageBuffer, size); + + //Free the buffer. + LocalFree(messageBuffer); + + return message; +} +#endif + +bool setClassRealtime(HANDLE h) +{ + auto x = GetPriorityClass(h); + auto b = SetPriorityClass(h, REALTIME_PRIORITY_CLASS); + auto c = GetPriorityClass(h); + +#if 0 + printf("setrealtime(%d) %d, %d, %d\n", REALTIME_PRIORITY_CLASS, x, b, c); + if (!b) { + printf("SetP call failed with %d (%s)\n", GetLastError(), GetLastErrorAsString().c_str()); + } + #endif + + return c == REALTIME_PRIORITY_CLASS; +} + + +bool enablePrivilege(HANDLE hProcess, const char * privilege) +{ + printf("called ep with %s\n", privilege); + struct + { + DWORD Count; + LUID_AND_ATTRIBUTES Privilege[1]; + } Info; + + HANDLE Token; + BOOL Result; + + // Open the token. + + Result = OpenProcessToken(hProcess, + TOKEN_ADJUST_PRIVILEGES, + &Token); + + if (Result != TRUE) { + printf("ep Cannot open process token.\n"); + return FALSE; + } + + + Info.Count = 1; + Info.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED; + + + // Get the LUID. + Result = LookupPrivilegeValue(NULL, + privilege, + &(Info.Privilege[0].Luid)); + + if (Result != TRUE) { + printf("ep Cannot get privilege for %s.\n", privilege); + return FALSE; + } + + // Adjust the privilege. + Result = AdjustTokenPrivileges(Token, FALSE, + (PTOKEN_PRIVILEGES) &Info, + 0, NULL, NULL); + + CloseHandle(Token); + + // Check the result. + + if (Result != TRUE) { + printf("ep Cannot adjust token privileges (%u)\n", GetLastError()); + return FALSE; + } else { + if (GetLastError() != ERROR_SUCCESS) { + // printf("getlasterror = %d, %s\n", GetLastError(), GetLastErrorAsString().c_str()); + printf("Cannot enable the %s privilege; ", privilege + ); + printf("please check the local policy.\n"); + return FALSE; + } + } + + return TRUE; +} + +/** + * + */ +bool setRealtimePriority(HANDLE hRackProcess) +{ + auto set = setClassRealtime(hRackProcess); + printf("set pri class at start ret %d\n", set); + return set; +} + diff --git a/plugins/community/repos/squinkylabs-plug1/projects/vs_windows/Squinky.sln b/plugins/community/repos/squinkylabs-plug1/projects/vs_windows/Squinky.sln new file mode 100644 index 00000000..f83ebbf3 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/projects/vs_windows/Squinky.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2024 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Squinky", "Squinky.vcxproj", "{5DAF445F-56A2-5B5C-EB47-1D24590CF72A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5DAF445F-56A2-5B5C-EB47-1D24590CF72A}.Debug|Win32.ActiveCfg = Debug|Win32 + {5DAF445F-56A2-5B5C-EB47-1D24590CF72A}.Debug|Win32.Build.0 = Debug|Win32 + {5DAF445F-56A2-5B5C-EB47-1D24590CF72A}.Debug|x64.ActiveCfg = Debug|x64 + {5DAF445F-56A2-5B5C-EB47-1D24590CF72A}.Debug|x64.Build.0 = Debug|x64 + {5DAF445F-56A2-5B5C-EB47-1D24590CF72A}.Release|Win32.ActiveCfg = Release|Win32 + {5DAF445F-56A2-5B5C-EB47-1D24590CF72A}.Release|Win32.Build.0 = Release|Win32 + {5DAF445F-56A2-5B5C-EB47-1D24590CF72A}.Release|x64.ActiveCfg = Release|x64 + {5DAF445F-56A2-5B5C-EB47-1D24590CF72A}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {26DE0C55-7FEF-4C87-892A-C36583FB4783} + EndGlobalSection +EndGlobal diff --git a/plugins/community/repos/squinkylabs-plug1/projects/vs_windows/Squinky.vcxproj b/plugins/community/repos/squinkylabs-plug1/projects/vs_windows/Squinky.vcxproj new file mode 100644 index 00000000..6c369054 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/projects/vs_windows/Squinky.vcxproj @@ -0,0 +1,226 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Win32Proj + {5DAF445F-56A2-5B5C-EB47-1D24590CF72A} + 10.0.16299.0 + + + + Application + true + v141 + + + Application + true + v141 + + + Application + false + v141 + + + Application + false + v141 + + + + + + + + + + + + + + + + + + + true + + + true + + + true + + + true + + + + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + ProgramDatabase + Disabled + ..\..\dsp\third-party\falco;..\..\dsp\utils;..\..\dsp\generators;..\..\dsp\filters;%(AdditionalIncludeDirectories) + stdcpp14 + + + MachineX86 + true + Console + + + + + WIN32;ARCH_WIN;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + ProgramDatabase + Disabled + ..\..\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) + stdcpp14 + + + true + Console + winmm.lib;%(AdditionalDependencies) + + + + + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + + + MachineX86 + true + Console + true + true + + + + + WIN32;ARCH_WIN;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + ..\..\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) + + + true + Console + true + true + winmm.lib;%(AdditionalDependencies) + + + + + + \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/projects/vs_windows/Squinky.vcxproj.filters b/plugins/community/repos/squinkylabs-plug1/projects/vs_windows/Squinky.vcxproj.filters new file mode 100644 index 00000000..b646a4d4 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/projects/vs_windows/Squinky.vcxproj.filters @@ -0,0 +1,336 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + {f5dea203-4bd6-45b8-ac87-b6b9a4877380} + + + {6cd4392e-16ed-4f15-9196-dbe57a9f5472} + + + {4ff35a32-d2ce-4426-aaad-d9ef70138cf4} + + + {f5a31e57-cc6b-443c-abf4-46970f118fe1} + + + {c2b8cfc5-30d0-4c94-9424-75409757ee7e} + + + {0ab36cfd-9f38-416a-b356-59180724dbf6} + + + {095df52d-c60a-44cb-9e6f-ff148b9f2730} + + + {95f83ea1-2663-4fa7-86e2-5bb532d82198} + + + {c53ee123-95ef-4304-8445-69048886956b} + + + {4f96103b-8d78-4aec-b054-b4f72c382195} + + + {20dd77d1-e20f-4a4f-8086-472afb19d4e2} + + + {d358068d-664e-470d-93b2-2bb5db2b41e8} + + + {6f955dd1-2a2c-4506-bfc2-4bb55734eb72} + + + {69f82fd1-904a-40e1-8a26-c2cd63f1e979} + + + {dde575bc-41ce-4770-9fd3-411c945a262b} + + + {09a864b6-3479-4e18-b9de-499c30b220a4} + + + {53848339-2c75-4ddc-8933-607d2353adee} + + + {80fd0025-498b-4fd2-98ac-cdae460339fc} + + + {ea30d980-17c6-4bf8-91a7-847e331330c9} + + + {19f721d6-97d9-4952-a547-52e6e418fbf4} + + + {98bc8bb8-3835-46b9-be11-d534f001cac5} + + + {d88fef08-3b09-4979-98d0-41863e55201d} + + + {2919976b-3c5e-48c7-9bc1-70b34d905594} + + + {6c381890-e472-4d98-a18a-8c768ec7a670} + + + + + Source Files\dsp\third-party + + + Source Files\test + + + Source Files\test + + + Source Files\test + + + Source Files\test + + + Source Files\test + + + Source Files\dsp\filters + + + Source Files\dsp\utils + + + Source Files\test + + + Source Files\dsp\filters + + + Source Files\test + + + Source Files\test + + + Source Files\test + + + Source Files\test + + + Source Files\test + + + Source Files\test + + + Source Files\dsp\filters + + + Source Files\dsp\utils + + + Source Files\test + + + Source Files\test + + + Source Files\dsp\fft + + + Source Files\dsp\third-party\kiss_fft + + + Source Files\dsp\third-party\kiss_fft + + + Source Files\sqsrc\thread + + + Source Files\sqsrc\thread + + + Source Files\sqsrc\thread + + + Source Files\test + + + Source Files\dsp\fft + + + Source Files\test + + + Source Files\test + + + Source Files\test + + + Source Files\dsp\fft + + + Source Files\test + + + Source Files\test + + + Source Files\sqsrc\clock + + + Source Files\test + + + Source Files\test + + + + + Header Files\dsp\third-party\falco + + + Header Files\dsp\filters + + + Header Files\dsp\filters + + + Header Files\dsp\filters + + + Header Files\dsp\filters + + + Header Files\dsp\generators + + + Header Files\dsp\generators + + + Header Files\dsp\utils + + + Header Files\dsp\utils + + + Header Files\dsp\filters + + + Header Files\test + + + Header Files\composites + + + Header Files\composites + + + Header Files\composites + + + Header Files\test + + + Header Files\test + + + Header Files\dsp\filters + + + Header Files\test + + + Header Files\dsp\generators + + + Header Files\composites + + + Header Files\composites + + + Header Files\dsp\filters + + + Header Files\test + + + Header Files\test + + + Header Files\dsp\utils + + + Header Files\dsp\utils + + + Header Files\dsp\fft + + + Header Files\dsp\fft + + + Header Files\dsp\third-party\kiss_fft + + + Header Files\dsp\third-party\kiss_fft + + + Header Files\sqsrc\thread + + + Header Files\sqsrc\thread + + + Header Files\sqsrc\thread + + + Header Files\sqsrc\util + + + Header Files\sqsrc\util + + + Header Files\composites + + + Header Files\dsp\fft + + + Header Files\sqsrc\clock + + + Header Files\dsp\utils + + + Header Files\composites + + + Header Files\sqsrc\util + + + Header Files\sqsrc\util + + + Header Files\sqsrc\util + + + Header Files\sqsrc\thread + + + \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/res/blank_panel.svg b/plugins/community/repos/squinkylabs-plug1/res/blank_panel.svg new file mode 100644 index 00000000..4e30c038 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/res/blank_panel.svg @@ -0,0 +1,17 @@ + + + + + + + panel-6b-flat + + + + + + + + + + diff --git a/plugins/community/repos/squinkylabs-plug1/res/booty_panel.svg b/plugins/community/repos/squinkylabs-plug1/res/booty_panel.svg new file mode 100644 index 00000000..1e0183d6 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/res/booty_panel.svg @@ -0,0 +1,102 @@ + + + + + + + panel-6b-flat + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/squinkylabs-plug1/res/colors_panel.svg b/plugins/community/repos/squinkylabs-plug1/res/colors_panel.svg new file mode 100644 index 00000000..1c4a661f --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/res/colors_panel.svg @@ -0,0 +1,45 @@ + + + + + + + colors-panel-2-ai + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/squinkylabs-plug1/res/cpu_hog_panel.svg b/plugins/community/repos/squinkylabs-plug1/res/cpu_hog_panel.svg new file mode 100644 index 00000000..f2e31cda --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/res/cpu_hog_panel.svg @@ -0,0 +1,40 @@ + + + + + + + cpu_hog_panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/squinkylabs-plug1/res/formants_panel.svg b/plugins/community/repos/squinkylabs-plug1/res/formants_panel.svg new file mode 100644 index 00000000..de591576 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/res/formants_panel.svg @@ -0,0 +1,44 @@ + + + + + + + formants_panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/squinkylabs-plug1/res/thread_booster_panel.svg b/plugins/community/repos/squinkylabs-plug1/res/thread_booster_panel.svg new file mode 100644 index 00000000..c73c52ef --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/res/thread_booster_panel.svg @@ -0,0 +1,47 @@ + + + + + + + thread_booster_panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/squinkylabs-plug1/res/trem_panel.svg b/plugins/community/repos/squinkylabs-plug1/res/trem_panel.svg new file mode 100644 index 00000000..eb70aaa7 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/res/trem_panel.svg @@ -0,0 +1,46 @@ + + + + + + + trem_panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/squinkylabs-plug1/res/vocal_animator_panel.svg b/plugins/community/repos/squinkylabs-plug1/res/vocal_animator_panel.svg new file mode 100644 index 00000000..a567e16f --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/res/vocal_animator_panel.svg @@ -0,0 +1,88 @@ + + + + + + + vocal_animator_panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/squinkylabs-plug1/sqsrc/clock/ClockMult.cpp b/plugins/community/repos/squinkylabs-plug1/sqsrc/clock/ClockMult.cpp new file mode 100644 index 00000000..82e8b695 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/sqsrc/clock/ClockMult.cpp @@ -0,0 +1,101 @@ + +#include +#include "ClockMult.h" +#include + + +void ClockMult::sampleClock() +{ + if (isFreeRun()) { + sampleClockFreeRunMode(); + } else { + sampleClockLockedMode(); + } +} + +void ClockMult::sampleClockFreeRunMode() +{ + sawPhase += freeRunFreq; + if (sawPhase >= 1) { + sawPhase -= 1; + // TODO: do we care about clock out? probably... + } +} + + +void ClockMult::sampleClockLockedMode() +{ + // printf("sampleClock: state=%d saw=%f\n", state, sawPhase); + switch (state) { + case State::INIT: + break; + case State::TRAINING: + ++trainingCounter; + break; + case State::RUNNING: + ++trainingCounter; // we are still training, even while running + sawPhase += learnedFrequency; + if (sawPhase >= 1) { + sawPhase -= 1.f; + } + if (clockOutTimer > 0) { + clockOutTimer--; + } else { + clockOutValue = false; + // printf("clock out one-shot timed out, going low\n"); + } + + break; + + default: + assert(false); + } + // printf("leave sampleClock: state=%d saw=%f\n", state, sawPhase); +} + +/** +* Sends one reference tick to the multiplier +*/ +void ClockMult::refClock() +{ + if (isFreeRun()) { + return; + } + // printf("refClock: state=%d\n", state); + switch (state) { + case State::INIT:// + state = State::TRAINING; + trainingCounter = 0; + // printf("refClock moved from INIT to TRAINIG\n"); + break; + case State::TRAINING: + case State::RUNNING: + // printf("got end train with ctr = %d\n", trainingCounter); + learnedPeriod = trainingCounter; + trainingCounter = 0; + learnedFrequency = (float) freqMultFactor / learnedPeriod; + state = State::RUNNING; + + startNewClock(); + // printf("refClock moved from TRAINING to RUNNING. period = %d freq=%f clockOut=%d\n", learnedPeriod, learnedFrequency, clockOutValue); + break; + + default: + assert(0); + + } + //printf("leave refClock: state=%d\n", state); +} + +void ClockMult::startNewClock() +{ + sawPhase = 0; + clockOutValue = true; + clockOutTimer = 10; // TODO: constants +} + + +void ClockMult::setMultiplier(int x) +{ + freqMultFactor = x; +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/sqsrc/clock/ClockMult.h b/plugins/community/repos/squinkylabs-plug1/sqsrc/clock/ClockMult.h new file mode 100644 index 00000000..931e1f39 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/sqsrc/clock/ClockMult.h @@ -0,0 +1,78 @@ +#pragma once + + +class ClockMult +{ +public: + /** + * Clocks the multiplier by one sample. + * Returns 0 or more high speed clocks. + */ + void sampleClock(); + + /** + * Sends one reference tick to the multiplier. This is + * the "input" to the multiplier. + */ + void refClock(); + + /** + * 0..1 saw at the same rate as multiplier output. + */ + float getSaw() const + { + return sawPhase; + } + + /** + * The binary pulse output of the clock multiplier + * (note: doesn't work yet) + */ + bool getMultipliedClock() const + { + return clockOutValue; + } + + /** + * @param x >= 1 is the multiplier factor + * x == 0 : free run + */ + void setMultiplier(int x); + + float _getFreq() const + { + return learnedFrequency; + } + + void setFreeRunFreq(float f) + { + freeRunFreq = f; + } +private: + enum class State + { + INIT, + TRAINING, + RUNNING + }; + int trainingCounter = 12345; + int learnedPeriod = 999; + float learnedFrequency = 0; + int freqMultFactor = 0; // if 0, free run, else mult by n. + State state = State::INIT; + + bool clockOutValue = 0; + int clockOutTimer = 0; + + float sawPhase = 0; + float freeRunFreq = 0; + + void startNewClock(); + bool isFreeRun() const + { + return freqMultFactor == 0; + } + void sampleClockFreeRunMode(); + void sampleClockLockedMode(); + +}; \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadClient.cpp b/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadClient.cpp new file mode 100644 index 00000000..61c542ce --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadClient.cpp @@ -0,0 +1,39 @@ + +#include "ThreadClient.h" +#include "ThreadServer.h" +#include "ThreadSharedState.h" + +#include + +ThreadClient::ThreadClient(std::shared_ptr state, + std::unique_ptr server) : sharedState(state), _server(std::move(server)) +{ + assert(!sharedState->serverRunning); + _server->start(); + while (!sharedState->serverRunning) { + } +} + +ThreadClient::~ThreadClient() +{ + sharedState->client_askServerToStop(); + sharedState->serverStopRequested.store(true); // ask server to stop + + while (sharedState->serverRunning) { + static bool did = false; + if (!did) { + did = true; + } + } +} + +ThreadMessage * ThreadClient::getMessage() +{ + return sharedState->client_pollMessage(); +} + + +bool ThreadClient::sendMessage(ThreadMessage * message) +{ + return sharedState->client_trySendMessage(message); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadClient.h b/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadClient.h new file mode 100644 index 00000000..575cbd40 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadClient.h @@ -0,0 +1,43 @@ +#pragma once +#include + +class ThreadSharedState; +class ThreadServer; +class ThreadMessage; + +/** + * This class is meant to be used directly (not sub-classed) + * All APIs are non-blocking (polled), so that they may be called + * from an audio render thread without fear of priority inversion. + * See ThreadSharedState and ThreadSerever for more info. + */ +class ThreadClient +{ +public: + ThreadClient(std::shared_ptr state, std::unique_ptr server); + ~ThreadClient(); + + /** + * Poll to see if a message has come back from the server. + * Will return null if no message waiting. + */ + ThreadMessage * getMessage(); + + /** + * Try to send a message. + * Returns true if message sent. + * + * Message might not be sent for various reasons: + * - Mailbox still has previous message. + * - Unable to lock semaphore without blocking. + */ + bool sendMessage(ThreadMessage *); + + + + const ThreadClient& operator= (const ThreadClient&) = delete; + ThreadClient(const ThreadClient&) = delete; +private: + std::shared_ptr sharedState; + std::unique_ptr _server; +}; \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadPriority.h b/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadPriority.h new file mode 100644 index 00000000..77153b21 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadPriority.h @@ -0,0 +1,208 @@ +#pragma once + +#ifdef ARCH_WIN +#include +#endif + +#ifdef ARCH_LIN +#include +#include +#include +#include +#include +#include +#endif + +class ThreadPriority +{ +public: + /** + * Attempts to boost priority of current thread, but not all + * the way to a "realtime" priority. In genera, should not require + * admin rights. + */ + static bool boostNormal(); + + /** + * Attempts to boost priority of current thread all the way to + * a "realtime" priority. Will require admin rights + */ + static bool boostRealtime(); + + static void restore(); + +#ifdef ARCH_WIN + static bool boostRealtimeWindows(); +#endif +private: + + static bool boostNormalPthread(); + static bool boostRealtimePthread(); + static void restorePthread(); +#ifdef ARCH_LIN + static bool boostNormalLinux(); +#endif +}; + +#ifdef ARCH_WIN +inline bool ThreadPriority::boostRealtimeWindows() +{ + HANDLE h = GetCurrentProcess(); + auto x = GetPriorityClass(h); + printf("cur priority class = %lx, realtime=%x", x, REALTIME_PRIORITY_CLASS); + + SetPriorityClass(h, HIGH_PRIORITY_CLASS); + printf("set pri class to %x is now %lx\n", HIGH_PRIORITY_CLASS, GetPriorityClass(h)); + + bool b = SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); + int y = GetThreadPriority(GetCurrentThread()); + printf("set pri ret %d, is now %d want %d err=%ld\n", + b, + y, + THREAD_PRIORITY_TIME_CRITICAL, GetLastError()); + fflush(stdout); + return y == THREAD_PRIORITY_TIME_CRITICAL; + +} +#endif + +// Inside Visual Studio test we don't try to link in PThreads, +// So they can't be used here. But they will work on all command line +// test builds, including Windows. +#if !defined(_MSC_VER) || defined(_VSCODE) +inline bool ThreadPriority::boostNormal() +{ +#ifdef ARCH_LIN + return boostNormalLinux(); +#else + return boostNormalPthread(); +#endif +} + +inline bool ThreadPriority::boostRealtime() +{ +#ifdef ARCH_WIN + return boostRealtimeWindows(); +#else + return boostRealtimePthread(); +#endif +} + +inline void ThreadPriority::restore() +{ + restorePthread(); +} + +inline void ThreadPriority::restorePthread() +{ + const pthread_t threadHandle = pthread_self(); + struct sched_param params; + params.sched_priority = 0; // Note that on mac, this is not the default. + // fix this. + const int newPolicy = SCHED_OTHER; + int x = pthread_setschedparam(threadHandle, newPolicy, ¶ms); + if (x != 0) { + printf("failed to set reset sched %d\n", x); + } +} + + +/** + * Linux doesn't support pthread priorities, so + * we use the common hack of setting niceness. + */ +#ifdef ARCH_LIN +inline bool ThreadPriority::boostNormalLinux() +{ + pid_t tid = syscall(SYS_gettid); + const int priority = -20; + int ret = setpriority(PRIO_PROCESS, tid, priority); + if (ret != 0) { + printf("set pri failed, errno = %d\n", errno); + printf("EACCESS=%d\n", EACCES); + } + printf("priority now %d\n", getpriority(PRIO_PROCESS, tid)); + return ret == 0; +} +#endif + +inline bool ThreadPriority::boostNormalPthread() +{ + struct sched_param params; + const pthread_t threadHandle = pthread_self(); + int initPolicy = -10; + pthread_getschedparam(threadHandle, &initPolicy, ¶ms); + printf("in boost, policy was %d, pri was %d otherp=%d\n", initPolicy, params.sched_priority, SCHED_OTHER); + + const int initPriority = params.sched_priority; + + const int newPolicy = SCHED_OTHER; + const int maxPriority = sched_get_priority_max(newPolicy); +#if 1 + { + printf("for OTHER, pri range = %d,%d\n", + sched_get_priority_min(newPolicy), + sched_get_priority_max(newPolicy)); + } +#endif + + if (maxPriority <= initPriority) { + // Linux seems to only offer one priority for SCHED_OTHER; + printf("SCHED_OTHER only supports priority %d\n", maxPriority); + return false; + } + + params.sched_priority = maxPriority; + int x = pthread_setschedparam(threadHandle, newPolicy, ¶ms); + if (x != 0) { + printf("failed to set pri %d for SCHED_OTHER. error= %d\n", maxPriority, x); + } + fflush(stdout); + return x == 0; +} + +inline bool ThreadPriority::boostRealtimePthread() +{ + struct sched_param params; + const pthread_t threadHandle = pthread_self(); + const int newPolicy = SCHED_RR; + const int maxPriority = sched_get_priority_max(newPolicy); + const int minPriority = sched_get_priority_min(newPolicy); + + +#if 0 + if ((maxPriority <= 0) || (minPriority < 0)) { + printf("algorithm doesn't work with rt %d, %d\n", minPriority, maxPriority); + return false; + } +#endif + + // use the mean of min and max. These should all be higher than "non realtime" + // on mac the mean was not as good as elevating as other, to let's try max/ + //const int newPriority = (maxPriority + minPriority) / 2; + const int newPriority = maxPriority; + + printf("realtime min = %d max = %d will use %d\n", minPriority, maxPriority, newPriority); + params.sched_priority = newPriority; + int x = pthread_setschedparam(threadHandle, newPolicy, ¶ms); + if (x != 0) { + printf("failed to set pri %d for SCHED_RR. error= %d\n", newPriority, x); + } + return x == 0; +} + +#else +inline bool ThreadPriority::boostNormal() +{ + return true; +} + +inline bool ThreadPriority::boostRealtime() +{ + return true; +} +inline void ThreadPriority::restore() +{ + +} +#endif diff --git a/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadServer.cpp b/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadServer.cpp new file mode 100644 index 00000000..0d9c15e7 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadServer.cpp @@ -0,0 +1,60 @@ + +#include +#include "ThreadServer.h" +#include "ThreadSharedState.h" + +int ThreadServer::_count = 0; +ThreadServer::ThreadServer(std::shared_ptr state) : + sharedState(state) +{ + ++_count; +} + +ThreadServer::~ThreadServer() +{ + --_count; +} + +void ThreadServer::start() +{ + auto startupFunc = [this]() { + this->threadFunction(); + }; + std::unique_ptr th(new std::thread(startupFunc)); + thread = std::move(th); +} + +void ThreadServer::threadFunction() +{ + sharedState->serverRunning = true; + for (bool done = false; !done; ) { + if (sharedState->serverStopRequested.load()) { + done = true; + } else { + // if msg is null, stop was requested (that's not true anyh more, is it?) + ThreadMessage* msg = sharedState->server_waitForMessageOrShutdown(); + if (msg) { + procMessage(msg); + } + } + } + + thread->detach(); + sharedState->serverRunning = false; +} + +//TODO: get rid of this function +void ThreadServer::procMessage(ThreadMessage* msg) +{ + handleMessage(msg); +} + +void ThreadServer::handleMessage(ThreadMessage* ) +{ + assert(false); // derived must override. +} + +void ThreadServer::sendMessageToClient(ThreadMessage* msg) +{ + sharedState->server_sendMessage(msg); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadServer.h b/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadServer.h new file mode 100644 index 00000000..83154db1 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadServer.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +class ThreadSharedState; +class ThreadMessage; + +/** + * ThreadServer implements a worker thread that can do work + * off of the audio thread in a plugin. + * To do useful work with Thread server: + * Derive a class from ThreadServer, and override handleMessage. + * Define at least one message by deriving from ThreadMessage. + * Control ThreadServer with ThreadClient. + * For more info, refer to ThreadSharedState + */ +class ThreadServer +{ +public: + ThreadServer(std::shared_ptr state); + virtual ~ThreadServer(); + void start(); + + const ThreadServer& operator= (const ThreadServer&) = delete; + ThreadServer(const ThreadServer&) = delete; + static int _count; +protected: + /** + * Derived thread servers must override this to get messages. + * Message is not const, as server is allowed to modify it and + * send it back. + */ + virtual void handleMessage(ThreadMessage*); + + /** + * Utility for sending replies back to the client. + * Will causes an error if there is a message in the mailbox already. + */ + void sendMessageToClient(ThreadMessage*); + + std::shared_ptr sharedState; + std::unique_ptr thread; +private: + + // Message based clients should not override this + virtual void threadFunction(); + + /** + * + * TODO: get rid of proc and handle, if possible + */ + void procMessage(ThreadMessage*); +}; \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadSharedState.cpp b/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadSharedState.cpp new file mode 100644 index 00000000..de8f2cee --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadSharedState.cpp @@ -0,0 +1,89 @@ + +#include +#include "ThreadSharedState.h" + +std::atomic ThreadSharedState::_dbgCount; +std::atomic ThreadMessage::_dbgCount; + +#include +#include +#include + +ThreadMessage* ThreadSharedState::server_waitForMessageOrShutdown() +{ + // printf("wait\n"); fflush(stdout); + + std::unique_lock guard(mailboxMutex); // grab the mutex that protects condition + ThreadMessage* returnMessage = nullptr; + for (bool done = false; !done; ) { + if (serverStopRequested.load()) { + done = true; + } else { + returnMessage = mailboxClient2Server.load(); + if (returnMessage) { + done = true; + } + } + if (!done) { + mailboxCondition.wait(guard); + } + } + mailboxClient2Server.store(nullptr); + return returnMessage; +} + +void ThreadSharedState::client_askServerToStop() +{ + serverStopRequested.store(true); // ask server to stop + std::unique_lock guard(mailboxMutex); // grab the mutex + mailboxCondition.notify_all(); // wake up server +} + +ThreadMessage* ThreadSharedState::client_pollMessage() +{ + ThreadMessage* msg = nullptr; + + // grab lock + std::unique_lock guard(mailboxMutex); + msg = mailboxServer2Client.load(); + if (msg) { + mailboxServer2Client.store(nullptr); + } + return msg; +} + +// signal in lock +bool ThreadSharedState::client_trySendMessage(ThreadMessage* msg) +{ + assert(serverRunning.load()); + // If the client tries to send a message before the previous one is read, the + // call will fail and the client must try again. + if (mailboxClient2Server.load()) { + return false; + } + + // Write to mailbox (condition) in lock + + std::unique_lock guard(mailboxMutex, std::defer_lock); + // We must use a try_lock here, as calling regular lock() could cause a priority inversion. + bool didLock = guard.try_lock(); + if (!didLock) { + return false; + } + assert(guard.owns_lock()); + + assert(!mailboxClient2Server.load()); // if there is still a message there we are out of sync + mailboxClient2Server.store(msg); + + mailboxCondition.notify_all(); + return true; +} + +void ThreadSharedState::server_sendMessage(ThreadMessage* msg) +{ + std::unique_lock guard(mailboxMutex); + assert(mailboxServer2Client.load() == nullptr); + mailboxServer2Client.store(msg); +} + + diff --git a/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadSharedState.h b/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadSharedState.h new file mode 100644 index 00000000..01ad27f2 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/sqsrc/thread/ThreadSharedState.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include + +/** + * Messaging protocol between client and server. + * + * This protocol has the following goals: + * It is easy to implement. + * It is easy to understand. + * It is thread safe. + * The client's thread will never block. + * + * Basics of the protocol: + * Client initiates all communication. + * For every message sent client -> server, the server will send once back. + * The message objects are owned by whoever created them. Passing + * a message does not transfer ownership. + * Only one message may be "in play" at a time. Until the client + * receives a reply from the server, it may not send another message. + */ + + +/** + * Base class for messages passed between client and server threads. + * Receivers of message will typically examine the "type", and down-cast + * based on that. + */ +class ThreadMessage +{ +public: + enum class Type + { + TEST1, + TEST2, + NOISE // used by ColoredNoise + }; + ThreadMessage(Type t) : type(t) + { + ++_dbgCount; + } + virtual ~ThreadMessage() + { + --_dbgCount; + } + + const Type type; + static std::atomic _dbgCount; +}; + +/** + * ThreadServer and ThreadClient do not refer to each other directly. + * Instead, they both maintain pointers to ThreadSharedState. + * All communication between thread goes through here. + */ +class ThreadSharedState +{ +public: + ThreadSharedState() + { + ++_dbgCount; + serverRunning.store(false); + serverStopRequested.store(false); + mailboxClient2Server.store(nullptr); + mailboxServer2Client.store(nullptr); + } + ~ThreadSharedState() + { + --_dbgCount; + } + std::atomic serverRunning; + std::atomic serverStopRequested; + static std::atomic _dbgCount; + + /** + * If return false, message not sent. + * otherwise message send, and msg may be reused. + */ + bool client_trySendMessage(ThreadMessage* msg); + + ThreadMessage* client_pollMessage(); + void client_askServerToStop(); + + void server_sendMessage(ThreadMessage* msg); + + /** + * returned message is a pointer to a message that we "own" + * temporarily (sender may modify it, but won't delete it). + * + * if null returned, a shutdown has been requested + */ + ThreadMessage* server_waitForMessageOrShutdown(); + + +private: + + /** + * This mutex protects all the private state + */ + std::mutex mailboxMutex; + + /** The message in the mailbox. + * This is an object by whoever created it. Ownership of message + * is not passed. + * TODO: given that a mutex protects us, we have no reason to use atomics here + */ + std::atomic mailboxClient2Server; + std::atomic mailboxServer2Client; + + std::condition_variable mailboxCondition; +}; \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/sqsrc/util/Constants.h b/plugins/community/repos/squinkylabs-plug1/sqsrc/util/Constants.h new file mode 100644 index 00000000..917f436c --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/sqsrc/util/Constants.h @@ -0,0 +1,12 @@ +#pragma once + +/** + * Gate inputs will be considered "low" if they are below this + */ +const float cGateLow = .8f; + +/** +* Gate inputs will be considered "high" if they are above this +*/ +const float cGateHi = 1.6f; + diff --git a/plugins/community/repos/squinkylabs-plug1/sqsrc/util/GateTrigger.h b/plugins/community/repos/squinkylabs-plug1/sqsrc/util/GateTrigger.h new file mode 100644 index 00000000..74e19e7f --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/sqsrc/util/GateTrigger.h @@ -0,0 +1,65 @@ +#pragma once + +#include "SchmidtTrigger.h" +#include + + +class GateTrigger +{ +public: + GateTrigger() : + _gate(false), + _trigger(false), + _reset(true) + { + } + + void go(float v) + { + const bool newGate = _sc.go(v); + if (_reset) { + if (newGate) // in reset state need to wait for low + return; + else + _reset = false; + } + _trigger = newGate && !_gate; + _gate = newGate; + } + + void reset() + { + _gate = false; + _trigger = false; + _reset = true; + } + + bool gate() const + { + return _gate; + } + + bool trigger() const + { + return _trigger; + } + + float thhi() const + { + return _sc.thhi(); + } + + float thlo() const + { + return _sc.thlo(); + } + +private: + SchmidtTrigger _sc; + bool _gate; + bool _trigger; + bool _reset; // just reset - gate must go low before high to trigger +}; + + + diff --git a/plugins/community/repos/squinkylabs-plug1/sqsrc/util/ManagedPool.h b/plugins/community/repos/squinkylabs-plug1/sqsrc/util/ManagedPool.h new file mode 100644 index 00000000..339ab7cc --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/sqsrc/util/ManagedPool.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include + +#include "RingBuffer.h" + +/** + * A very specialized container. Made for holding one free + * work buffers, and making sure they are destroyed. + * + * At construction time, objects are created to fill the pool. + * pop the objects to get one, push back to return when done. + * + * Destructor will delete all the objects, even if they are not in the pool + * at the time. + * + * Note that unlike RingBuffer, ManagePool manages T*, not T. + * + * All public functions are no-blocking, so may be called from the audio thread + * without danger. Of course the constructor and destructor are exceptions - they may block. + */ +template +class ManagedPool +{ +public: + ManagedPool(); + + + /** Accessors from RingBuffer + */ + void push(T*); + T* pop(); + bool full() const; + bool empty() const; +private: + /** + * this ring buffer is where the raw T* are kept. + * client pops and pushes here + */ + RingBuffer ringBuffer; + std::vector< std::unique_ptr> lifetimeManager; +}; + +template +inline ManagedPool::ManagedPool() +{ + // Manufacture the items here + for (int i = 0; i < SIZE; ++i) { + T * item = new T(); + ringBuffer.push(item); // put the raw pointer in ring buffer for client to use. + + // Also add managed pointers to delete the objects on destroy. + lifetimeManager.push_back(std::unique_ptr(item)); + } +} + +template +inline void ManagedPool::push(T* value) +{ + ringBuffer.push(value); +} + + +template +inline T* ManagedPool::pop() +{ + return ringBuffer.pop(); +} + +template +inline bool ManagedPool::full() const +{ + return ringBuffer.full(); +} + +template +inline bool ManagedPool::empty() const +{ + return ringBuffer.empty(); +} + + diff --git a/plugins/community/repos/squinkylabs-plug1/sqsrc/util/RingBuffer.h b/plugins/community/repos/squinkylabs-plug1/sqsrc/util/RingBuffer.h new file mode 100644 index 00000000..120250c6 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/sqsrc/util/RingBuffer.h @@ -0,0 +1,79 @@ +#pragma once +#include + +/** + * A simple ring buffer. + * Template arguments are for numeric type stored, and for size. + * Not thread safe. + * Guaranteed to be non-blocking. Adding or removing items will never + * allocate or free memory. + * Objects in RingBuffer are not owned by RingBuffer - they will not be destroyed. + */ +template +class RingBuffer +{ +public: + RingBuffer() + { + for (int i = 0; i < SIZE; ++i) { + memory[i] = 0; + } + } + + void push(T); + T pop(); + bool full() const; + bool empty() const; + +private: + T memory[SIZE]; + bool couldBeFull = false; // full and empty are ambiguous, both are in--out + int inIndex = 0; + int outIndex = 0; + + /** Move up 'p' (a buffer index), wrap around if we hit the end + * (this is the core of the circular ring buffer). + */ + void advance(int &p); +}; + +template +inline void RingBuffer::push(T value) +{ + assert(!full()); + memory[inIndex] = value; + advance(inIndex); + couldBeFull = true; +} + + +template +inline T RingBuffer::pop() +{ + assert(!empty()); + T value = memory[outIndex]; + advance(outIndex); + couldBeFull = false; + return value; +} + +template +inline bool RingBuffer::full() const +{ + return (inIndex == outIndex) && couldBeFull; +} + +template +inline bool RingBuffer::empty() const +{ + return (inIndex == outIndex) && !couldBeFull; +} + +template +inline void RingBuffer::advance(int &p) +{ + if (++p >= SIZE) p = 0; +} + + + diff --git a/plugins/community/repos/squinkylabs-plug1/sqsrc/util/SchmidtTrigger.h b/plugins/community/repos/squinkylabs-plug1/sqsrc/util/SchmidtTrigger.h new file mode 100644 index 00000000..d84612d5 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/sqsrc/util/SchmidtTrigger.h @@ -0,0 +1,44 @@ + +#pragma once + +#include "Constants.h" + +class SchmidtTrigger +{ +public: + SchmidtTrigger(float thLo = cGateLow, float thHi = cGateHi) : + _thLo(thLo), _thHi(thHi), _lastOut(false) + { + } + + bool go(float input) + { + if (_lastOut) // if we were true last time + { + if (input < _thLo) { + _lastOut = false; + } + } else { + if (input > _thHi) { + _lastOut = true; + } + } + return _lastOut; + } + + float thhi() const + { + return _thHi; + } + float thlo() const + { + return _thLo; + } + +private: + const float _thLo; + const float _thHi; + bool _lastOut; +}; + + diff --git a/plugins/community/repos/squinkylabs-plug1/src/BootyModule.cpp b/plugins/community/repos/squinkylabs-plug1/src/BootyModule.cpp new file mode 100644 index 00000000..6e0b1891 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/src/BootyModule.cpp @@ -0,0 +1,219 @@ +#include "global_pre.hpp" +#include "Squinky.hpp" +#include "FrequencyShifter.h" +#include "WidgetComposite.h" +#include "global_ui.hpp" + +/** + * Implementation class for BootyModule + */ +struct BootyModule : Module +{ + BootyModule(); + + /** + * Overrides of Module functions + */ + void step() override; + json_t *toJson() override; + void fromJson(json_t *rootJ) override; + void onSampleRateChange() override; + + FrequencyShifter shifter; +private: + typedef float T; +public: + ChoiceButton * rangeChoice; +}; + +extern float values[]; +extern const char* ranges[]; + +BootyModule::BootyModule() : Module(shifter.NUM_PARAMS, shifter.NUM_INPUTS, shifter.NUM_OUTPUTS, shifter.NUM_LIGHTS), +shifter(this) +{ + // TODO: can we assume onSampleRateChange() gets called first, so this is unnecessary? + onSampleRateChange(); + shifter.init(); +} + +void BootyModule::onSampleRateChange() +{ + T rate = engineGetSampleRate(); + shifter.setSampleRate(rate); +} + +json_t *BootyModule::toJson() +{ + json_t *rootJ = json_object(); + const int rg = shifter.freqRange; + json_object_set_new(rootJ, "range", json_integer(rg)); + return rootJ; +} + +void BootyModule::fromJson(json_t *rootJ) +{ + json_t *driverJ = json_object_get(rootJ, "range"); + if (driverJ) { + const int rg = json_number_value(driverJ); + + // TODO: should we be more robust about float <> int issues? + //need to tell the control what text to display + for (int i = 0; i < 5; ++i) { + if (rg == values[i]) { + rangeChoice->text = ranges[i]; + } + } + shifter.freqRange = rg; + fflush(stdout); + } +} + +void BootyModule::step() +{ + shifter.step(); +} + +/*********************************************************************************** + * + * RangeChoice selector widget + * + ***********************************************************************************/ + +const char* ranges[5] = { + "5 Hz", + "50 Hz", + "500 Hz", + "5 kHz", + "exp" +}; + +float values[5] = { + 5, + 50, + 500, + 5000, + 0 +}; + +struct RangeItem : MenuItem +{ + RangeItem(int index, float * output, ChoiceButton * inParent) : + rangeIndex(index), rangeOut(output), rangeChoice(inParent) + { + text = ranges[index]; + } + + const int rangeIndex; + float * const rangeOut; + ChoiceButton* const rangeChoice; + + void onAction(EventAction &e) override + { + rangeChoice->text = ranges[rangeIndex]; + *rangeOut = values[rangeIndex]; + } +}; + +struct RangeChoice : ChoiceButton +{ + RangeChoice(float * out, const Vec& pos, float width) : output(out) + { + assert(*output == 5); + this->text = std::string(ranges[0]); + this->box.pos = pos; + this->box.size.x = width; + } + float * const output; + void onAction(EventAction &e) override + { + Menu *menu = rack::global_ui->ui.gScene->createMenu(); + + menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round(); + menu->box.size.x = box.size.x; + { + menu->addChild(new RangeItem(0, output, this)); + menu->addChild(new RangeItem(1, output, this)); + menu->addChild(new RangeItem(2, output, this)); + menu->addChild(new RangeItem(3, output, this)); + menu->addChild(new RangeItem(4, output, this)); + } + } +}; + +//////////////////// +// module widget +//////////////////// + +struct BootyWidget : ModuleWidget +{ + BootyWidget(BootyModule *); +}; + +/** + * Widget constructor will describe my implementation structure and + * provide meta-data. + * This is not shared by all modules in the DLL, just one + */ +BootyWidget::BootyWidget(BootyModule *module) : ModuleWidget(module) +{ + box.size = Vec(6 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); + + { + SVGPanel *panel = new SVGPanel(); + panel->box.size = box.size; + panel->setBackground(SVG::load(assetPlugin(plugin, "res/booty_panel.svg"))); + addChild(panel); + } + + const int leftInputX = 11; + const int rightInputX = 55; + + const int row0 = 45; + const int row1 = 102; + static int row2 = 186; + + // Inputs on Row 0 + addInput(Port::create(Vec(leftInputX, row0), Port::INPUT, module, module->shifter.AUDIO_INPUT)); + addInput(Port::create(Vec(rightInputX, row0), Port::INPUT, module, module->shifter.CV_INPUT)); + + // shift Range on row 2 + const float margin = 16; + float xPos = margin; + float width = 6 * RACK_GRID_WIDTH - 2 * margin; + + // TODO: why do we need to reach into the module from here? I guess any + // time UI callbacks need to go bak.. + module->rangeChoice = new RangeChoice(&module->shifter.freqRange, Vec(xPos, row2), width); + addChild(module->rangeChoice); + + // knob on row 1 + addParam(ParamWidget::create(Vec(18, row1), module, module->shifter.PITCH_PARAM, -5.0, 5.0, 0.0)); + + const float row3 = 317.5; + + // Outputs on row 3 + const float leftOutputX = 9.5; + const float rightOutputX = 55.5; + + addOutput(Port::create(Vec(leftOutputX, row3), Port::OUTPUT, module, module->shifter.SIN_OUTPUT)); + addOutput(Port::create(Vec(rightOutputX, row3), Port::OUTPUT, module, module->shifter.COS_OUTPUT)); + + // screws + addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); +} + +// Specify the Module and ModuleWidget subclass, human-readable +// manufacturer name for categorization, module slug (should never +// change), human-readable module name, and any number of tags +// (found in `include/tags.hpp`) separated by commas. +RACK_PLUGIN_MODEL_INIT(squinkylabs_plug1, Booty) { + Model *modelBootyModule = Model::create("Squinky Labs", + "squinkylabs-freqshifter", + "Booty Frequency Shifter", EFFECT_TAG, RING_MODULATOR_TAG); + return modelBootyModule; +} + diff --git a/plugins/community/repos/squinkylabs-plug1/src/CPU_Hog.cpp b/plugins/community/repos/squinkylabs-plug1/src/CPU_Hog.cpp new file mode 100644 index 00000000..ee529ed2 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/src/CPU_Hog.cpp @@ -0,0 +1,174 @@ + +#include +#include "Squinky.hpp" +#ifdef _CPU_HOG + +#include "WidgetComposite.h" + +#include "ThreadClient.h" +#include "ThreadServer.h" +#include "ThreadSharedState.h" + + +/** The following block of constants control what + * this plugin does. Change them and re-build + */ +static const int numLoadThreads = 1; +static const int drawMillisecondSleep = 0; + +static std::atomic drawIsSleeping; + +/** + * Implementation class for BootyModule + */ +struct CPU_HogModule : Module +{ + CPU_HogModule(); + ~CPU_HogModule(); + + /** + * Overrides of Module functions + */ + void step() override; + + int stepsWhileDrawing=0; +private: + typedef float T; + std::vector< std::shared_ptr > threads; +}; + +class PServer : public ThreadServer +{ +public: + PServer(std::shared_ptr state) + : ThreadServer(state) + { + + } + virtual void threadFunction () override; + + ~PServer() { + } +private: + bool didRun = false; + double dummy = 0; +}; + + void PServer::threadFunction() + { + sharedState->serverRunning = true; + for (bool done = false; !done; ) { + if (sharedState->serverStopRequested.load()) { + done = true; + } else { + // now kill a lot of time + for (int i=0; i< 10000; ++i) { + dummy += std::log(rand()) * std::sin(rand()); + } + + } + } + + thread->detach(); + sharedState->serverRunning = false; + } + +CPU_HogModule::CPU_HogModule() : Module(0,0,0,0) +{ + for (int i=0; i state = std::make_shared(); + std::unique_ptr server(new PServer(state)); + threads.push_back( + std::make_shared( + state, + std::move(server))); + } + + // TODO: can we assume onSampleRateChange() gets called first, so this is unnecessary? + onSampleRateChange(); +} + +CPU_HogModule::~CPU_HogModule() +{ + threads.resize(0); +} + + +void CPU_HogModule::step() +{ + if (drawIsSleeping) { + stepsWhileDrawing++; + } +} + +//////////////////// +// module widget +//////////////////// + +struct CPU_HogWidget : ModuleWidget +{ + CPU_HogWidget(CPU_HogModule *); + void draw(NVGcontext *vg) override + { + const CPU_HogModule* pMod = static_cast(module); + std::stringstream s; + s << pMod->stepsWhileDrawing; + steps->text = s.str(); + + ModuleWidget::draw(vg); + if (drawMillisecondSleep) { + drawIsSleeping = true; + std::this_thread::sleep_for(std::chrono::milliseconds(drawMillisecondSleep)); + drawIsSleeping = false; + } + } + Label* steps; +}; + +/** + * Widget constructor will describe my implementation structure and + * provide meta-data. + * This is not shared by all modules in the DLL, just one + */ +CPU_HogWidget::CPU_HogWidget(CPU_HogModule *module) : ModuleWidget(module) +{ + box.size = Vec(6 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); + + { + SVGPanel *panel = new SVGPanel(); + panel->box.size = box.size; + panel->setBackground(SVG::load(assetPlugin(plugin, "res/cpu_hog_panel.svg"))); + addChild(panel); + } + + Label* label=new Label(); + label->box.pos = Vec(10, 140); + label->text = "SleepSteps"; + label->color = COLOR_BLACK; + addChild(label); + + steps=new Label(); + steps->box.pos = Vec(10, 180); + steps->text = ""; + steps->color = COLOR_BLACK; + addChild(steps); + + // screws + addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); +} + +// Specify the Module and ModuleWidget subclass, human-readable +// manufacturer name for categorization, module slug (should never +// change), human-readable module name, and any number of tags +// (found in `include/tags.hpp`) separated by commas. +RACK_PLUGIN_MODEL_INIT(squinkylabs_plug1, CPU_hog) { + Model *modelCPU_HogModule = Model::create("Squinky Labs", + "squinkylabs-cpuhog", + "CPU Hog", EFFECT_TAG); + return modelCPU_HogModule; +} +#endif + diff --git a/plugins/community/repos/squinkylabs-plug1/src/ColoredNoiseModule.cpp b/plugins/community/repos/squinkylabs-plug1/src/ColoredNoiseModule.cpp new file mode 100644 index 00000000..dd461583 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/src/ColoredNoiseModule.cpp @@ -0,0 +1,249 @@ + +#include "Squinky.hpp" + +#include "WidgetComposite.h" +#include "ColoredNoise.h" +#include "NoiseDrawer.h" + +/** + * Implementation class for VocalWidget + */ +struct ColoredNoiseModule : Module +{ + ColoredNoiseModule(); + + /** + * Overrides of Module functions + */ + void step() override; + void onSampleRateChange() override; + + ColoredNoise noiseSource; +private: + typedef float T; +}; + +ColoredNoiseModule::ColoredNoiseModule() + : Module(noiseSource.NUM_PARAMS, + noiseSource.NUM_INPUTS, + noiseSource.NUM_OUTPUTS, + noiseSource.NUM_LIGHTS), + noiseSource(this) +{ + onSampleRateChange(); + noiseSource.init(); +} + +void ColoredNoiseModule::onSampleRateChange() +{ + T rate = engineGetSampleRate(); + noiseSource.setSampleRate(rate); +} + +void ColoredNoiseModule::step() +{ + noiseSource.step(); +} + +//////////////////// +// module widget +//////////////////// + +struct ColoredNoiseWidget : ModuleWidget +{ + ColoredNoiseWidget(ColoredNoiseModule *); + Label * slopeLabel; + Label * signLabel; +}; + +// The colors of noise (UI colors) +static const unsigned char red[3] = {0xff, 0x04, 0x14}; +static const unsigned char pink[3] = {0xff, 0x3a, 0x6d}; +static const unsigned char white[3] = {0xff, 0xff, 0xff}; +static const unsigned char blue[3] = {0x54, 0x43, 0xc1}; +static const unsigned char violet[3] = {0x9d, 0x3c, 0xe6}; + +// 0 <= x <= 1 +static float interp(float x, int x0, int x1) +{ + return x1 * x + x0 * (1 - x); +} + +// 0 <= x <= 3 +static void interp(unsigned char * out, float x, const unsigned char* y0, const unsigned char* y1) +{ + x = x * 1.0 / 3.0; // 0..1 + out[0] = interp(x, y0[0], y1[0]); + out[1] = interp(x, y0[1], y1[1]); + out[2] = interp(x, y0[2], y1[2]); +} + +static void copyColor(unsigned char * out, const unsigned char* in) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +static void getColor(unsigned char * out, float x) +{ + if (x < -6) { + copyColor(out, red); + } else if (x >= 6) { + copyColor(out, violet); + } else { + if (x < -3) { + interp(out, x + 6, red, pink); + } else if (x < 0) { + interp(out, x + 3, pink, white); + } else if (x < 3) { + interp(out, x + 0, white, blue); + } else if (x < 6) { + interp(out, x - 3, blue, violet); + } else { + copyColor(out, white); + } + } +} + +// the draw size of the colored noise display. +const int colorWidth = 85; +const int colorHeight = 180; +const int colorX = 10; +const int colorY = 170; + +struct ColorDisplay : TransparentWidget +{ + ColoredNoiseModule *module; + ColorDisplay(Label *slopeLabel, Label *signLabel) + : _slopeLabel(slopeLabel), + _signLabel(signLabel) + { + } + + Label* _slopeLabel; + Label* _signLabel; + std::unique_ptr _noiseDrawer; + + void draw(NVGcontext *vg) override + { + // First draw the solid fill + const float slope = module->noiseSource.getSlope(); + unsigned char color[3]; + getColor(color, slope); + nvgFillColor(vg, nvgRGBA(color[0], color[1], color[2], 0xff)); + + nvgBeginPath(vg); + + nvgRect(vg, colorX, colorY, 6 * colorWidth, colorHeight); + nvgFill(vg); + + // then the noise + if (!_noiseDrawer) { + // TODO: this 100x100 was a mistake, but now we like the + // slight stretching. look into this some more to try and + // improve the looks later. + _noiseDrawer.reset(new NoiseDrawer(vg, 100, 100)); + } + _noiseDrawer->draw(vg, colorX, colorY, colorWidth, colorHeight); + + + // update the slope display in the UI + const bool slopeSign = slope >= 0; + const float slopeAbs = std::abs(slope); + std::stringstream s; + s.precision(1); + s.setf(std::ios::fixed, std::ios::floatfield); + + s << slopeAbs << " db/oct"; + _slopeLabel->text = s.str(); + + const char * mini = "\u2005-"; + _signLabel->text = slopeSign ? "+" : mini; + } +}; + +/** + * Widget constructor will describe my implementation structure and + * provide meta-data. + * This is not shared by all modules in the DLL, just one + */ +ColoredNoiseWidget::ColoredNoiseWidget(ColoredNoiseModule *module) : ModuleWidget(module) +{ + box.size = Vec(6 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); + + // save so we can update later. + slopeLabel = new Label(); + signLabel = new Label(); + + // add the color display + { + ColorDisplay *display = new ColorDisplay(slopeLabel, signLabel); + display->module = module; + display->box.pos = Vec(0, 0); + display->box.size = Vec(6 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); + addChild(display); + display->module = module; + } + + // Add the background panel + { + SVGPanel *panel = new SVGPanel(); + panel->box.size = box.size; + panel->setBackground(SVG::load(assetPlugin(plugin, "res/colors_panel.svg"))); + addChild(panel); + } + + addOutput(Port::create( + Vec(32, 310), + Port::OUTPUT, + module, + module->noiseSource.AUDIO_OUTPUT)); + Label* label = new Label(); + label->box.pos = Vec(24.2, 294); + label->text = "OUT"; + label->color = COLOR_WHITE; + addChild(label); + + addParam(ParamWidget::create( + Vec(22, 80), module, module->noiseSource.SLOPE_PARAM, -5.0, 5.0, 0.0)); + + addParam(ParamWidget::create( + Vec(58, 46), + module, module->noiseSource.SLOPE_TRIM, -1.0, 1.0, 1.0)); + + addInput(Port::create( + Vec(14, 42), + Port::INPUT, + module, + module->noiseSource.SLOPE_CV)); + + // Create the labels for slope. They will get + // text content later. + const float labelY = 146; + slopeLabel->box.pos = Vec(12, labelY); + slopeLabel->text = ""; + slopeLabel->color = COLOR_BLACK; + addChild(slopeLabel); + signLabel->box.pos = Vec(2, labelY); + signLabel->text = ""; + signLabel->color = COLOR_BLACK; + addChild(signLabel); + + // screws + addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); +} + +RACK_PLUGIN_MODEL_INIT(squinkylabs_plug1, ColoredNoise) { + Model *modelColoredNoiseModule = Model::create( + "Squinky Labs", + "squinkylabs-coloredNoise", + "Colored Noise", NOISE_TAG); + return modelColoredNoiseModule; +} + + + diff --git a/plugins/community/repos/squinkylabs-plug1/src/NoiseDrawer.h b/plugins/community/repos/squinkylabs-plug1/src/NoiseDrawer.h new file mode 100644 index 00000000..5ae1c5f9 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/src/NoiseDrawer.h @@ -0,0 +1,125 @@ + +#pragma once +#include "nanovg.h" + +/** + * Renders animated noise image sing nanogl + */ +// 0 = random pixel grey level +// 1 = random white, bk, transparent. +const int method = 0; +class NoiseDrawer +{ +public: + NoiseDrawer(NVGcontext *vg, float width, float height) + : _width(width), _height(height) + { + makeImage(vg); + _vg = vg; + } + + ~NoiseDrawer() + { + nvgDeleteImage(_vg, _image); + _image = 0; + } + + void draw(NVGcontext *vg, float colorX, float colorY, float colorWidth, float colorHeight); + +private: + /** + * Holds an image (gl texture) in memory for + * the lifetime of the plugin instance. + */ + int _image = 0; + NVGcontext* _vg = nullptr; + const int _width, _height; + + int frameCount = 0; + float randomX = 0; + float randomY = 0; + + void makeImage(NVGcontext *vg); +}; + + +inline void NoiseDrawer::makeImage(NVGcontext *vg) +{ + // let's synthesize some white noise + const int memSize = _width * _height * 4; + unsigned char * memImage = new unsigned char[memSize]; + + for (int row = 0; row < _height; ++row) { + for (int col = 0; col < _width; ++col) { + unsigned char * pix = memImage + (4 * (row*_width + col)); + if (method == 0) { + int value = int((255.f * rand()) / float(RAND_MAX)); + pix[0] = value; + pix[1] = value; + pix[2] = value; + pix[3] = 255; // opaque, for now + } else if (method == 1) { + //generate 0 .. 100 + int rnd = int((100.f * rand()) / float(RAND_MAX)); + int value, alpha; + if (rnd > 75) { + value = 0; + alpha = 255; + } else if (rnd > 75) { + value = 255; + alpha = 255; + } else { + value = 128; + alpha = 0; + } + pix[0] = value; + pix[1] = value; + pix[2] = value; + pix[3] = alpha; + } + } + } + + _image = nvgCreateImageRGBA(vg, + _width, + _height, + NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY, + memImage); + + assert(_image != 0); + delete[] memImage; +} + + +inline void NoiseDrawer::draw(NVGcontext *vg, + float drawX, + float drawY, + float drawWidth, + float drawHeight) +{ + assert(_image); + + // noise looks slightly better with anti-alias off? + nvgShapeAntiAlias(vg, false); + + // Don't update the noise position every frame. Old TV is only + // 30 fps, and this looks better even slower. + if (frameCount++ > 3) { + randomX = (float) rand() * _width / float(RAND_MAX); + randomY = (float) rand() * _height / float(RAND_MAX); + frameCount = 0; + } + + // Clever trick. We don't draw new noise every time, we just + // draw the same noise image from a random offset. + NVGpaint paint = nvgImagePattern(vg, + randomX, randomY, + _width, _height, + 0, _image, 0.15f); + + nvgBeginPath(vg); + nvgRect(vg, drawX, drawY, drawWidth, drawHeight); + + nvgFillPaint(vg, paint); + nvgFill(vg); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/src/Squinky.cpp b/plugins/community/repos/squinkylabs-plug1/src/Squinky.cpp new file mode 100644 index 00000000..0ab926b4 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/src/Squinky.cpp @@ -0,0 +1,24 @@ +// plugin main +#include "Squinky.hpp" + +RACK_PLUGIN_MODEL_DECLARE(squinkylabs_plug1, Booty); +RACK_PLUGIN_MODEL_DECLARE(squinkylabs_plug1, Vocal); +RACK_PLUGIN_MODEL_DECLARE(squinkylabs_plug1, VocalFilter); +RACK_PLUGIN_MODEL_DECLARE(squinkylabs_plug1, ColoredNoise); +RACK_PLUGIN_MODEL_DECLARE(squinkylabs_plug1, Tremolo); +RACK_PLUGIN_MODEL_DECLARE(squinkylabs_plug1, CPU_Hog); +RACK_PLUGIN_MODEL_DECLARE(squinkylabs_plug1, ThreadBoost); + +RACK_PLUGIN_INIT(squinkylabs_plug1) { + RACK_PLUGIN_INIT_ID(); + + RACK_PLUGIN_MODEL_ADD(squinkylabs_plug1, Booty); + RACK_PLUGIN_MODEL_ADD(squinkylabs_plug1, Vocal); + RACK_PLUGIN_MODEL_ADD(squinkylabs_plug1, VocalFilter); + RACK_PLUGIN_MODEL_ADD(squinkylabs_plug1, ColoredNoise); + RACK_PLUGIN_MODEL_ADD(squinkylabs_plug1, Tremolo); +#ifdef _CPU_HOG + RACK_PLUGIN_MODEL_ADD(squinkylabs_plug1, CPU_Hog); +#endif + RACK_PLUGIN_MODEL_ADD(squinkylabs_plug1, ThreadBoost); +} diff --git a/plugins/community/repos/squinkylabs-plug1/src/Squinky.hpp b/plugins/community/repos/squinkylabs-plug1/src/Squinky.hpp new file mode 100644 index 00000000..c9122bf8 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/src/Squinky.hpp @@ -0,0 +1,10 @@ +#include "rack.hpp" + +using namespace rack; + +RACK_PLUGIN_DECLARE(squinkylabs_plug1); + +#ifdef USE_VST2 +#define plugin "squinkylabs-plug1" +#endif // USE_VST2 + diff --git a/plugins/community/repos/squinkylabs-plug1/src/ThreadBoost.cpp b/plugins/community/repos/squinkylabs-plug1/src/ThreadBoost.cpp new file mode 100644 index 00000000..204b453d --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/src/ThreadBoost.cpp @@ -0,0 +1,175 @@ + +#include "Squinky.hpp" +#include "WidgetComposite.h" +#include "ThreadPriority.h" + + +struct ThreadBoostModule : Module +{ + enum ParamIds + { + THREAD_BOOST_PARAM, + NUM_PARAMS + }; + enum InputIds + { + + NUM_INPUTS + }; + enum OutputIds + { + + NUM_OUTPUTS + }; + enum LightIds + { + NORMAL_LIGHT, + BOOSTED_LIGHT, + REALTIME_LIGHT, + ERROR_LIGHT, + NUM_LIGHTS + }; + + ThreadBoostModule(); + + /** + * Overrides of Module functions + */ + void step() override; + +private: + int boostState = 0; + void lightOnly(LightIds l) + { + for (int i = NORMAL_LIGHT; i < NUM_LIGHTS; ++i) { + bool b = (i == l); + lights[i].value = b; + } + } + +}; + +ThreadBoostModule::ThreadBoostModule() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) +{ +} + +void ThreadBoostModule::step() +{ + float x = params[THREAD_BOOST_PARAM].value + .5f; + int i = std::floor(x); + if (i != boostState) { + switch (i) { + case 0: + ThreadPriority::restore(); + lightOnly(NORMAL_LIGHT); + break; + case 1: + { + bool b = ThreadPriority::boostNormal(); + if (b) { + lightOnly(BOOSTED_LIGHT); + } else { + lightOnly(ERROR_LIGHT); + } + break; + } + case 2: + { + bool b = ThreadPriority::boostRealtime(); + if (b) { + lightOnly(REALTIME_LIGHT); + } else { + lightOnly(ERROR_LIGHT); + } + break; + } + } + boostState = i; + } +} + +//////////////////// +// module widget +//////////////////// + +struct ThreadBoostWidget : ModuleWidget +{ + ThreadBoostWidget(ThreadBoostModule *); +}; + +/** + * Widget constructor will describe my implementation structure and + * provide meta-data. + * This is not shared by all modules in the DLL, just one + */ +ThreadBoostWidget::ThreadBoostWidget(ThreadBoostModule *module) + : ModuleWidget(module) +{ + box.size = Vec(6 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); + + { + SVGPanel *panel = new SVGPanel(); + panel->box.size = box.size; + panel->setBackground(SVG::load(assetPlugin(plugin, "res/thread_booster_panel.svg"))); + addChild(panel); + } + + addParam(ParamWidget::create( + Vec(30, 140), module, ThreadBoostModule::THREAD_BOOST_PARAM, 0.0f, 2.0f, 0.0f)); + + const int ledX = 10; + const int labelX = 16; + const int ledY = 200; + const int labelY = ledY - 5; + const int deltaY = 30; + Label* label; + addChild(ModuleLightWidget::create>( + Vec(ledX, ledY), module, ThreadBoostModule::NORMAL_LIGHT)); + label = new Label(); + label->box.pos = Vec(labelX, labelY); + label->text = "Normal"; + label->color = COLOR_BLACK; + addChild(label); + + addChild(ModuleLightWidget::create>( + Vec(ledX, ledY + deltaY), module, ThreadBoostModule::BOOSTED_LIGHT)); + label = new Label(); + label->box.pos = Vec(labelX, labelY + deltaY); + label->text = "Boost"; + label->color = COLOR_BLACK; + addChild(label); + + addChild(ModuleLightWidget::create>( + Vec(ledX, ledY + 2 * deltaY), module, ThreadBoostModule::REALTIME_LIGHT)); + label = new Label(); + label->box.pos = Vec(labelX, labelY + 2 * deltaY); + label->text = "Real-time"; + label->color = COLOR_BLACK; + addChild(label); + + addChild(ModuleLightWidget::create>( + Vec(ledX, ledY + 3 * deltaY), module, ThreadBoostModule::ERROR_LIGHT)); + label = new Label(); + label->box.pos = Vec(labelX, labelY + 3 * deltaY); + label->text = "Error"; + label->color = COLOR_BLACK; + addChild(label); + + // screws + addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); +} + +// Specify the Module and ModuleWidget subclass, human-readable +// manufacturer name for categorization, module slug (should never +// change), human-readable module name, and any number of tags +// (found in `include/tags.hpp`) separated by commas. +RACK_PLUGIN_MODEL_INIT(squinkylabs_plug1, ThreadBoost) { + Model *modelThreadBoostModule = Model::create("Squinky Labs", + "squinkylabs-booster", + "Thread Booster", UTILITY_TAG); + return modelThreadBoostModule; +} + diff --git a/plugins/community/repos/squinkylabs-plug1/src/TremoloModule.cpp b/plugins/community/repos/squinkylabs-plug1/src/TremoloModule.cpp new file mode 100644 index 00000000..49083acd --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/src/TremoloModule.cpp @@ -0,0 +1,192 @@ + +#include +#include "Squinky.hpp" +#include "WidgetComposite.h" +#include "Tremolo.h" + +/** + */ +struct TremoloModule : Module +{ +public: + TremoloModule(); + /** + * Overrides of Module functions + */ + void step() override; + void onSampleRateChange() override; + Tremolo tremolo; +private: +}; + +void TremoloModule::onSampleRateChange() +{ + float rate = engineGetSampleRate(); + tremolo.setSampleRate(rate); +} + +TremoloModule::TremoloModule() + : Module(tremolo.NUM_PARAMS, + tremolo.NUM_INPUTS, + tremolo.NUM_OUTPUTS, + tremolo.NUM_LIGHTS), + tremolo(this) +{ + onSampleRateChange(); + tremolo.init(); +} + +void TremoloModule::step() +{ + tremolo.step(); +} + +//////////////////// +// module widget +//////////////////// + +struct TremoloWidget : ModuleWidget +{ + TremoloWidget(TremoloModule *); + + void addLabel(const Vec& v, const char* str, const NVGcolor& color = COLOR_BLACK) + { + Label* label = new Label(); + label->box.pos = v; + label->text = str; + label->color = color; + addChild(label); + } + void addClockSection(TremoloModule *module); + void addIOSection(TremoloModule *module); + void addMainSection(TremoloModule *module); +}; + + +void TremoloWidget::addClockSection(TremoloModule *module) +{ + const float y = 40; // global offset for clock block + const float labelY = y + 36; + + addInput(Port::create(Vec(10, y + 7), Port::INPUT, module, module->tremolo.CLOCK_INPUT)); + addLabel(Vec(2, labelY), "ckin"); + + addParam(ParamWidget::create( + Vec(110, y), module, module->tremolo.LFO_RATE_PARAM, -5.0, 5.0, 0.0)); + addLabel(Vec(104, labelY), "Rate"); + + const float cmy = y; + const float cmx = 60; + addParam(ParamWidget::create( + Vec(cmx, cmy), module, module->tremolo.CLOCK_MULT_PARAM, 0.0f, 4.0f, 4.0f)); + addLabel(Vec(cmx - 8, labelY), "Clock"); + addLabel(Vec(cmx - 19, cmy + 20), "x1"); + addLabel(Vec(cmx + 21, cmy + 20), "int"); + addLabel(Vec(cmx - 24, cmy + 0), "x2"); + addLabel(Vec(cmx + 24, cmy + 0), "x4"); + addLabel(Vec(cmx, cmy - 16), "x3"); +} + +void TremoloWidget::addIOSection(TremoloModule *module) +{ + const float rowIO = 317; + const float label = rowIO - 17; + const float deltaX = 35; + const float x = 10; + + addInput(Port::create(Vec(x, rowIO), Port::INPUT, module, module->tremolo.AUDIO_INPUT)); + addLabel(Vec(8, label), "in"); + + addOutput(Port::create(Vec(x + deltaX, rowIO), Port::OUTPUT, module, module->tremolo.AUDIO_OUTPUT)); + addLabel(Vec(x + deltaX - 6, label), "out", COLOR_WHITE); + + addOutput(Port::create(Vec(x + 2 * deltaX, rowIO), Port::OUTPUT, module, module->tremolo.SAW_OUTPUT)); + addLabel(Vec(x + 2 * deltaX - 7, label), "saw", COLOR_WHITE); + + addOutput(Port::create(Vec(x + 3 * deltaX, rowIO), Port::OUTPUT, module, module->tremolo.LFO_OUTPUT)); + addLabel(Vec(x + 3 * deltaX - 2, label), "lfo", COLOR_WHITE); +} + +void TremoloWidget::addMainSection(TremoloModule *module) +{ + const float knobX = 64; + const float knobY = 100; + const float knobDy = 50; + const float labelX = 100; + const float labelY = knobY; + const float trimX = 40; + const float trimY = knobY + 10; + const float inY = knobY + 6; + const float inX = 8; + + addParam(ParamWidget::create( + Vec(knobX, knobY + 0 * knobDy), module, module->tremolo.LFO_SHAPE_PARAM, -5.0, 5.0, 0.0)); + addParam(ParamWidget::create( + Vec(trimX, trimY + 0 * knobDy), module, module->tremolo.LFO_SHAPE_TRIM_PARAM, -1.0, 1.0, 1.0)); + addInput(Port::create( + Vec(inX, inY + 0 * knobDy), Port::INPUT, module, module->tremolo.LFO_SHAPE_INPUT)); + addLabel( + Vec(labelX, labelY + 0 * knobDy), "Shape"); + + addParam(ParamWidget::create( + Vec(knobX, knobY + 1 * knobDy), module, module->tremolo.LFO_SKEW_PARAM, -5.0, 5.0, 0.0)); + addParam(ParamWidget::create( + Vec(trimX, trimY + 1 * knobDy), module, module->tremolo.LFO_SKEW_TRIM_PARAM, -1.0, 1.0, 1.0)); + addInput(Port::create( + Vec(inX, labelY + 1 * knobDy + 6), Port::INPUT, module, module->tremolo.LFO_SKEW_INPUT)); + addLabel( + Vec(labelX+1, labelY + 1 * knobDy), "Skew"); + + addParam(ParamWidget::create( + Vec(knobX, knobY + 2 * knobDy), module, module->tremolo.LFO_PHASE_PARAM, -5.0, 5.0, 0.0)); + addParam(ParamWidget::create( + Vec(trimX, trimY + 2 * knobDy), module, module->tremolo.LFO_PHASE_TRIM_PARAM, -1.0, 1.0, 1.0)); + addInput(Port::create( + Vec(inX, labelY + 2 * knobDy + 6), Port::INPUT, module, module->tremolo.LFO_PHASE_INPUT)); + addLabel( + Vec(labelX, labelY + 2 * knobDy), "Phase"); + + addParam(ParamWidget::create( + Vec(knobX, knobY + 3 * knobDy), module, module->tremolo.MOD_DEPTH_PARAM, -5.0, 5.0, 0.0)); + addParam(ParamWidget::create( + Vec(trimX, trimY + 3 * knobDy), module, module->tremolo.MOD_DEPTH_TRIM_PARAM, -1.0, 1.0, 1.0)); + addInput(Port::create( + Vec(inX, labelY + 3 * knobDy + 6), Port::INPUT, module, module->tremolo.MOD_DEPTH_INPUT)); + addLabel( + Vec(labelX, labelY + 3 * knobDy), "Depth"); +} + +/** + * Widget constructor will describe my implementation structure and + * provide meta-data. + * This is not shared by all modules in the DLL, just one + */ +TremoloWidget::TremoloWidget(TremoloModule *module) : ModuleWidget(module) +{ + box.size = Vec(10 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); + { + SVGPanel *panel = new SVGPanel(); + panel->box.size = box.size; + panel->setBackground(SVG::load(assetPlugin(plugin, "res/trem_panel.svg"))); + addChild(panel); + } + + addClockSection(module); + addMainSection(module); + addIOSection(module); + + // screws + addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); +} + +RACK_PLUGIN_MODEL_INIT(squinkylabs_plug1, Tremolo) { + Model *modelTremoloModule = Model::create("Squinky Labs", + "squinkylabs-tremolo", + "Chopper Tremolo", EFFECT_TAG, LFO_TAG); + return modelTremoloModule; +} + diff --git a/plugins/community/repos/squinkylabs-plug1/src/VocalFilterModule.cpp b/plugins/community/repos/squinkylabs-plug1/src/VocalFilterModule.cpp new file mode 100644 index 00000000..526b821a --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/src/VocalFilterModule.cpp @@ -0,0 +1,265 @@ + +#include "Squinky.hpp" + +#include "WidgetComposite.h" +#include "VocalFilter.h" + +/** + * Implementation class for VocalWidget + */ +struct VocalFilterModule : Module +{ + + VocalFilterModule(); + + /** + * Overrides of Module functions + */ + void step() override; + void onSampleRateChange() override; + + VocalFilter vocalFilter; +private: + typedef float T; +}; + +VocalFilterModule::VocalFilterModule() : Module(vocalFilter.NUM_PARAMS, vocalFilter.NUM_INPUTS, vocalFilter.NUM_OUTPUTS, vocalFilter.NUM_LIGHTS), +vocalFilter(this) +{ + onSampleRateChange(); + vocalFilter.init(); +} + +void VocalFilterModule::onSampleRateChange() +{ + T rate = engineGetSampleRate(); + vocalFilter.setSampleRate(rate); +} + +void VocalFilterModule::step() +{ + vocalFilter.step(); +} + +//////////////////// +// module widget +//////////////////// + +struct VocalFilterWidget : ModuleWidget +{ + VocalFilterWidget(VocalFilterModule *); + void addVowelLabels(); + void addModelKnob(VocalFilterModule *module, float x, float y); +}; + +void VocalFilterWidget::addVowelLabels() +{ + const float ledX = 20; + const float ledDx = 26; + const float ledY = 43; + + const float vOffsetX = -8; + const float vOffsetY = 14; + for (int i = 0; i < 5; ++i) { + VocalFilter::LightIds id = (VocalFilter::LightIds) i; + std::string ltext; + switch (id) { + case VocalFilter::LED_A: + ltext = "A"; + break; + case VocalFilter::LED_E: + ltext = "E"; + break; + case VocalFilter::LED_I: + ltext = "I"; + break; + case VocalFilter::LED_O: + ltext = "O"; + break; + case VocalFilter::LED_U: + ltext = "U"; + break; + default: + assert(false); + } + Label * label = nullptr; label = new Label(); + label->text = ltext; + label->color = COLOR_BLACK; + label->box.pos = Vec(ledX + vOffsetX + i * ledDx, ledY + vOffsetY); + + addChild(label); + + addChild(ModuleLightWidget::create>( + Vec(ledX + i * ledDx, ledY), module, id)); + } +} + +void VocalFilterWidget::addModelKnob(VocalFilterModule *module, float x, float y) +{ + // 5 pos vocal model + // 0(bass) 1(tenor) 2(countertenor) 3(alto) 4(soprano) + Label* label = new Label(); + label->box.pos = Vec(x - 18, y + 24); + label->text = "B"; + label->color = COLOR_BLACK; + addChild(label); + + label = new Label(); + label->box.pos = Vec(x - 20, y + 0); + label->text = "T"; + label->color = COLOR_BLACK; + addChild(label); + + label = new Label(); + label->box.pos = Vec(x - 2, y - 20); + label->text = "CT"; + label->color = COLOR_BLACK; + addChild(label); + + label = new Label(); + label->box.pos = Vec(x + 30, y + 0); + label->text = "A"; + label->color = COLOR_BLACK; + addChild(label); + + label = new Label(); + label->box.pos = Vec(x + 23, y + 24); + label->text = "S"; + label->color = COLOR_BLACK; + addChild(label); + + addParam(ParamWidget::create(Vec(x - .5, y), module, module->vocalFilter.FILTER_MODEL_SELECT_PARAM, 0.0f, 4.0f, 2.0f)); +} + +/** + * Widget constructor will describe my implementation structure and + * provide meta-data. + * This is not shared by all modules in the DLL, just one + */ +VocalFilterWidget::VocalFilterWidget(VocalFilterModule *module) : ModuleWidget(module) +{ + box.size = Vec(12 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); + + { + SVGPanel *panel = new SVGPanel(); + panel->box.size = box.size; + panel->setBackground(SVG::load(assetPlugin(plugin, "res/formants_panel.svg"))); + addChild(panel); + } + + addVowelLabels(); + + const float mid = 70; // the middle area, with the four main knobs + const float rightOffsetY = 40; // right knobs drop this amount + const float row2L = mid + 20; //the first row of knobs + const float row2R = row2L + rightOffsetY; + const float row3L = row2L + rightOffsetY * 2; + const float row3R = row3L + rightOffsetY; + + const float col1 = 10; // the left hand strip of inputs and atternuverters + const float col2 = 50; // left column of big knobs + const float col3 = 100; + const float col4 = 146; // inputs and attv on right + + const float labelOffset = -18; // height of label above knob + const float dyUp = -14; // vertical space between input and atten + const float dyDn = 30; + + const float trimDyL = 1; // move atten down to match knob; + const float trimDyR = 22; // move atten down to match knob; + + const float trimDx = 3; // move to the right to match input + Label* label; + + addParam(ParamWidget::create( + Vec(col2, row2L), + module, module->vocalFilter.FILTER_VOWEL_PARAM, -5.0, 5.0, 0.0)); + addInput(Port::create( + Vec(col1, row2L + dyDn), + Port::INPUT, module, module->vocalFilter.FILTER_VOWEL_CV_INPUT)); + addParam(ParamWidget::create( + Vec(col1 + trimDx, row2L + trimDyL), + module, module->vocalFilter.FILTER_VOWEL_TRIM_PARAM, -1.0, 1.0, 1.0)); + + // Fc + label = new Label(); + label->box.pos = Vec(col3, row2R + labelOffset); + label->text = "Fc"; + label->color = COLOR_BLACK; + addChild(label); + addParam(ParamWidget::create(Vec(col3, row2R), module, module->vocalFilter.FILTER_FC_PARAM, -5.0, 5.0, 0.0)); + addInput(Port::create( + Vec(col4, row2R + dyUp), + Port::INPUT, module, module->vocalFilter.FILTER_FC_CV_INPUT)); + addParam(ParamWidget::create( + Vec(col4 + trimDx, row2R + trimDyR), + module, module->vocalFilter.FILTER_FC_TRIM_PARAM, -1.0, 1.0, 1.0)); + + // Q + label = new Label(); + label->box.pos = Vec(col2, row3L + labelOffset); + label->text = "Q"; + label->color = COLOR_BLACK; + addChild(label); + addParam(ParamWidget::create( + Vec(col2, row3L), + module, module->vocalFilter.FILTER_Q_PARAM, -5.0, 5.0, 0.0)); + addInput(Port::create( + Vec(col1, row3L + dyDn), + Port::INPUT, module, module->vocalFilter.FILTER_Q_CV_INPUT)); + addParam(ParamWidget::create( + Vec(col1 + trimDx, row3L + trimDyL), + module, module->vocalFilter.FILTER_Q_TRIM_PARAM, -1.0, 1.0, 1.0)); + + // Brightness + label = new Label(); + label->box.pos = Vec(col3, row3R + labelOffset); + label->text = "Brite"; + label->color = COLOR_BLACK; + addChild(label); + addParam(ParamWidget::create( + Vec(col3, row3R), + module, module->vocalFilter.FILTER_BRIGHTNESS_PARAM, -5.0, 5.0, 0.0)); + addInput(Port::create( + Vec(col4, row3R + dyUp), + Port::INPUT, module, module->vocalFilter.FILTER_BRIGHTNESS_INPUT)); + addParam(ParamWidget::create( + Vec(col4 + trimDx, row3R + trimDyR), + module, module->vocalFilter.FILTER_BRIGHTNESS_TRIM_PARAM, -1.0, 1.0, 1.0)); + + addModelKnob(module, 71, 274); + + // I.O on row bottom + const float AudioInputX = 10.0; + const float outputX = 140.0; + const float iOrow = 312; + const float ioLabelOffset = -19; + + label = new Label(); + label->box.pos = Vec(outputX - 6, iOrow + ioLabelOffset); + label->text = "Out"; + label->color = COLOR_WHITE; + addChild(label); + label = new Label(); + label->box.pos = Vec(AudioInputX - 1, iOrow + ioLabelOffset); + label->text = "In"; + label->color = COLOR_BLACK; + addChild(label); + addInput(Port::create(Vec(AudioInputX, iOrow), Port::INPUT, module, module->vocalFilter.AUDIO_INPUT)); + addOutput(Port::create(Vec(outputX, iOrow), Port::OUTPUT, module, module->vocalFilter.AUDIO_OUTPUT)); + + /************************************************* + * screws + */ + addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); +} + +RACK_PLUGIN_MODEL_INIT(squinkylabs_plug1, VocalFilter) { + Model *modelVocalFilterModule = Model::create("Squinky Labs", + "squinkylabs-vocalfilter", + "Vocal Filter", EFFECT_TAG, FILTER_TAG); + return modelVocalFilterModule; +} diff --git a/plugins/community/repos/squinkylabs-plug1/src/VocalModule.cpp b/plugins/community/repos/squinkylabs-plug1/src/VocalModule.cpp new file mode 100644 index 00000000..ce5576ea --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/src/VocalModule.cpp @@ -0,0 +1,200 @@ +#include "Squinky.hpp" +#include "WidgetComposite.h" +#include "VocalAnimator.h" + +/** + * Implementation class for VocalWidget + */ +struct VocalModule : Module +{ + VocalModule(); + + /** + * Overrides of Module functions + */ + void step() override; + void onSampleRateChange() override; + using Animator = VocalAnimator; + Animator animator; +private: + typedef float T; +}; + +VocalModule::VocalModule() : Module(animator.NUM_PARAMS, animator.NUM_INPUTS, animator.NUM_OUTPUTS, animator.NUM_LIGHTS), +animator(this) +{ + onSampleRateChange(); + animator.init(); +} + +void VocalModule::onSampleRateChange() +{ + T rate = engineGetSampleRate(); + animator.setSampleRate(rate); +} + +void VocalModule::step() +{ + animator.step(); +} + +//////////////////// +// module widget +//////////////////// + +struct VocalWidget : ModuleWidget +{ + VocalWidget(VocalModule *); +}; + +template +struct MuteLight : BASE +{ + MuteLight() + { + this->box.size = mm2px(Vec(6.0f, 6.0f)); + } +}; + +struct NKK2 : SVGSwitch, ToggleSwitch +{ + NKK2() + { + addFrame(SVG::load(assetGlobal("res/ComponentLibrary/NKK_0.svg"))); + addFrame(SVG::load(assetGlobal("res/ComponentLibrary/NKK_2.svg"))); + } +}; + +/** + * Widget constructor will describe my implementation structure and + * provide meta-data. + * This is not shared by all modules in the DLL, just one + */ +VocalWidget::VocalWidget(VocalModule *module) : ModuleWidget(module) +{ + const float width = 14 * RACK_GRID_WIDTH; + box.size = Vec(width, RACK_GRID_HEIGHT); + { + SVGPanel *panel = new SVGPanel(); + panel->box.size = box.size; + panel->setBackground(SVG::load(assetPlugin(plugin, "res/vocal_animator_panel.svg"))); + addChild(panel); + } + /** + * LEDs and LFO outputs + */ + + const float lfoBlockY = 38; // was 22. move down to make space + + const float ledX = width - 46; + const float ledY = lfoBlockY + 7.5; + const float ledSpacingY = 30; + + const float lfoOutY = lfoBlockY; + const float lfoOutX = width - 30; + + const float lfoInputX = 24; + const float lfoInputY = lfoBlockY + 0; + const float lfoTrimX = 68; + const float lfoTrimY = lfoInputY + 3; + + const float lfoRateKnobX = 100; + const float lfoRateKnobY = lfoBlockY + 24; + + addChild(ModuleLightWidget::create>( + Vec(ledX, ledY), module, module->animator.LFO0_LIGHT)); + addChild(ModuleLightWidget::create>( + Vec(ledX, ledY + ledSpacingY), module, module->animator.LFO1_LIGHT)); + addChild(ModuleLightWidget::create>( + Vec(ledX, ledY + 2 * ledSpacingY), module, module->animator.LFO2_LIGHT)); + + addOutput(Port::create( + Vec(lfoOutX, lfoOutY), Port::OUTPUT, module, VocalModule::Animator::LFO0_OUTPUT)); + addOutput(Port::create( + Vec(lfoOutX, lfoOutY + 1 * ledSpacingY), Port::OUTPUT, module, VocalModule::Animator::LFO1_OUTPUT)); + addOutput(Port::create( + Vec(lfoOutX, lfoOutY + 2 * ledSpacingY), Port::OUTPUT, module, VocalModule::Animator::LFO2_OUTPUT)); + + addParam(ParamWidget::create( + Vec(lfoRateKnobX, lfoRateKnobY), module, module->animator.LFO_RATE_PARAM, -5.0, 5.0, 0.0)); + + addInput(Port::create( + Vec(lfoInputX, lfoInputY), Port::INPUT, module, VocalModule::Animator::LFO_RATE_CV_INPUT)); + addParam(ParamWidget::create( + Vec(lfoTrimX, lfoTrimY), module, module->animator.LFO_RATE_TRIM_PARAM, -1.0, 1.0, 1.0)); + + // the matrix switch + addParam(ParamWidget::create( + Vec(42, 65), module, module->animator.LFO_MIX_PARAM, 0.0f, 2.0f, 0.0f)); + + /** + * Parameters and CV + */ + const float mainBlockY = 140; + const float mainBlockX = 20; + + const float colSpacingX = 64; + + const float knobX = mainBlockX + 0; + const float knobY = mainBlockY + 24; + + const float trimX = mainBlockX + 11; + const float trimY = mainBlockY + 78; + + const float inputX = mainBlockX + 8; + const float inputY = mainBlockY + 108; + + addParam(ParamWidget::create( + Vec(knobX, knobY), module, module->animator.FILTER_FC_PARAM, -5.0, 5.0, 0.0)); + addInput(Port::create( + Vec(inputX, inputY), Port::INPUT, module, VocalModule::Animator::FILTER_FC_CV_INPUT)); + addParam(ParamWidget::create( + Vec(trimX, trimY), module, module->animator.FILTER_FC_TRIM_PARAM, -1.0, 1.0, 1.0)); + + addParam(ParamWidget::create( + Vec(knobX + colSpacingX, knobY), module, module->animator.FILTER_Q_PARAM, -5.0, 5.0, 0.0)); + addInput(Port::create( + Vec(inputX + colSpacingX, inputY), Port::INPUT, module, VocalModule::Animator::FILTER_Q_CV_INPUT)); + addParam(ParamWidget::create( + Vec(trimX + colSpacingX, trimY), module, module->animator.FILTER_Q_TRIM_PARAM, -1.0, 1.0, 1.0)); + + addParam(ParamWidget::create( + Vec(knobX + 2 * colSpacingX, knobY), module, module->animator.FILTER_MOD_DEPTH_PARAM, -5.0, 5.0, 0.0)); + addInput(Port::create( + Vec(inputX + 2 * colSpacingX, inputY), Port::INPUT, module, VocalModule::Animator::FILTER_MOD_DEPTH_CV_INPUT)); + addParam(ParamWidget::create( + Vec(trimX + 2 * colSpacingX, trimY), module, module->animator.FILTER_MOD_DEPTH_TRIM_PARAM, -1.0, 1.0, 1.0)); + + const float row3 = 310; + + // I.O on row 3 + const float AudioInputX = inputX; + const float outputX = inputX + 2 * colSpacingX; + + addInput(Port::create( + Vec(AudioInputX, row3), Port::INPUT, module, VocalModule::Animator::AUDIO_INPUT)); + addOutput(Port::create( + Vec(outputX, row3), Port::OUTPUT, module, VocalModule::Animator::AUDIO_OUTPUT)); + + const float bassX = inputX + colSpacingX - 4; + const float bassY = row3 - 8; + + // the bass boost switch + addParam(ParamWidget::create( + Vec(bassX, bassY), module, module->animator.BASS_EXP_PARAM, 0.0f, 1.0f, 0.0f)); + + /************************************************* + * screws + */ + addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); +} + +RACK_PLUGIN_MODEL_INIT(squinkylabs_plug1, Vocal) { + Model *modelVocalModule = Model::create("Squinky Labs", + "squinkylabs-vocalanimator", + "Vocal Animator", EFFECT_TAG, FILTER_TAG, LFO_TAG, RANDOM_TAG); + return modelVocalModule; +} diff --git a/plugins/community/repos/squinkylabs-plug1/test.mk b/plugins/community/repos/squinkylabs-plug1/test.mk new file mode 100644 index 00000000..1cbc7e3e --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test.mk @@ -0,0 +1,67 @@ +# makefile fragment to make test.exe, the unit test program. +#include "../../arch.mk" +include $(RACK_DIR)/arch.mk + +TEST_SOURCES = $(wildcard test/*.cpp) +TEST_SOURCES += $(wildcard dsp/**/*.cpp) +TEST_SOURCES += $(wildcard dsp/third-party/falco/*.cpp) +TEST_SOURCES += $(wildcard sqsrc/**/*.cpp) +TEST_SOURCES += dsp/third-party/kiss_fft130/tools/kiss_fftr.c +TEST_SOURCES += dsp/third-party/kiss_fft130/kiss_fft.c + +## This is a list of full paths to the .o files we want to build +TEST_OBJECTS = $(patsubst %, build_test/%.o, $(TEST_SOURCES)) + +build_test/%.cpp.o: %.cpp + mkdir -p $(@D) + $(CXX) $(CXXFLAGS) -c -o $@ $< + +build_test/%.c.o: %.c + mkdir -p $(@D) + $(CXX) $(CXXFLAGS) -c -o $@ $< + +# Always define _PERF for the perf tests. +perf.exe : PERFFLAG = -D _PERF + +# Turn off asserts for perf, unless user overrides on command line +perf.exe : FLAGS += $(ASSERTOFF) + +FLAGS += $(PERFFLAG) + +ifeq ($(ARCH), win) + # don't need these yet + # -lcomdlg32 -lole32 -ldsound -lwinmm +test.exe perf.exe : LDFLAGS = -static \ + -mwindows \ + -lpthread -lopengl32 -lgdi32 -lws2_32 +endif + +ifeq ($(ARCH), lin) +test.exe perf.exe : LDFLAGS = -rdynamic \ + -lpthread -lGL -ldl \ + $(shell pkg-config --libs gtk+-2.0) +endif + +ifeq ($(ARCH), mac) +test.exe perf.exe : LDFLAGS = -stdlib=libc++ -lpthread -ldl \ + -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo +endif + +test : test.exe + +## Note that perf and test targets both used build_test for object files, +## So you need to be careful to delete/clean when switching between the two. +## Consider fixing this in the future. +perf : perf.exe + +## cleantest will clean out all the test and perf build products +cleantest : + rm -rfv build_test + rm -fv test.exe + rm -fv perf.exe + +test.exe : $(TEST_OBJECTS) + $(CXX) -o $@ $^ $(LDFLAGS) + +perf.exe : $(TEST_OBJECTS) + $(CXX) -o $@ $^ $(LDFLAGS) \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/ExtremeTester.h b/plugins/community/repos/squinkylabs-plug1/test/ExtremeTester.h new file mode 100644 index 00000000..03b0eebb --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/ExtremeTester.h @@ -0,0 +1,126 @@ +#pragma once + +#include "asserts.h" +#include + +/** + * Utility class that counts up in binary using an array of ints to hold the bits. + * + */ +class BitCounter +{ +public: + void reset(int size) + { + done = false; + state.resize(size); + for (int i = 0; i < size; ++i) { + state[i] = 0; + } + } + bool atMax() const + { + for (size_t i = 0; i < state.size(); ++i) { + if (state[i] == 0) { + return false; + } + } + return true; + } + bool isDone() const + { + return done; + } + void next() + { + if (atMax()) { + done = true; + return; + } + state[0]++; + for (size_t i = 0; i < state.size(); ++i) { + if (state[i] > 1) { + state[i] = 0; + ++state[i + 1]; + } + } + + } + + template + void setState(std::vector& testSignal, const std::vector< std::pair>* signalLimits) + { + for (int i = (int) state.size() - 1; i >= 0; --i) { + float min = -10.f; + float max = 10.f; + if (signalLimits) { // here we want to clip these to the possible values of params + min = (*signalLimits)[i].first; + max = (*signalLimits)[i].second; + + assertNE(min, max); + assertGT(max, min); + } + testSignal[i].value = state[i] > 0 ? max : min; + } + } + + + void dump(const char * label) + { + printf("State (%s): ", label); + for (int i = (int) state.size() - 1; i >= 0; --i) { + printf("%d ", state[i]); + } + printf("\n"); + } +private: + std::vector state; + bool done; + +}; + + +/** +* Tests a composite by feeding all the inputs and parameters (in every possible permutation) +* with extreme inputs. Asserts that the output stays in a semi-sane range. +* Typically this test will fail by triggering an assert in some distant code. +*/ +template +class ExtremeTester +{ +public: + + static void test(T& dut, + const std::vector< std::pair>& paramLimits, + bool checkOutput, + const char * testName) + { + printf("extreme test starting for %s. %s ....\n", testName, checkOutput ? "test output" : ""); + const int numInputs = dut.NUM_INPUTS; + const int numParams = dut.NUM_PARAMS; + const int numOutputs = dut.NUM_OUTPUTS; + assert(numInputs < 20); + assertEQ(paramLimits.size(), numParams); + + const std::vector< std::pair> * nullLimits = nullptr; + BitCounter inputState; + BitCounter paramsState; + for (inputState.reset(numInputs); !inputState.isDone(); inputState.next()) { + inputState.setState(dut.inputs, nullLimits); + for (paramsState.reset(numParams); !paramsState.isDone(); paramsState.next()) { + paramsState.setState(dut.params, ¶mLimits); + for (int i = 0; i < 100; ++i) { + dut.step(); + for (int j = 0; j < numOutputs; ++j) { + const float out = dut.outputs[j].value; + if (checkOutput) { + assertGE(out, -150); + assertLE(out, 150); + } + } + } + } + } + printf("extreme test done\n"); + } +}; \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/MeasureTime.h b/plugins/community/repos/squinkylabs-plug1/test/MeasureTime.h new file mode 100644 index 00000000..095f381c --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/MeasureTime.h @@ -0,0 +1,145 @@ +#pragma once + +#include +#define __STDC_FORMAT_MACROS +#include +#include "TimeStatsCollector.h" + +template +class TestBuffers; + + +/** + * class MeasureTime. + * + * Used to estimate the amount of time a function takes to execute. + * Will run the function over and over in a tight loop. Return value of function + * is saved to testBuffers. Otherwise the compiler might optimize the whole thing away. + * Usually ends by printing out the data. + * + * One of that statistics printed out is "quota used per 1 percent". Here we are arbitrarily + * stating that a synthesizer module "should" use only one percent of the available audio processing CPU. + * This won't be the case for all plugins/developers, but certainly is a plausible benchmark. + * + * A big caveat to all this is that code running inside VCV Rack will most likely run slower than the + * same code running in at right loop. Some reasons are: + * - VCV's audio processing thread probably has some internal overheard, so + * it's unlikely that modules will get 100% of one core. + * - In a tight loop all the data you code references will probably end up in fast on-chip + * cache memory. In VCV your step function is called once, then all the other module's step + * functions are called. This may force your data out of cache. + */ +template +class MeasureTime +{ +public: + MeasureTime() = delete; // we are only static + + /** + * Executes function "func" and measures how long it takes. + * Will call func in a tight look lasting minTime seconds. + * When done, prints out statistics. + */ + static void run(const char * name, std::function func, float minTime) + { + int64_t iterations; + bool done = false; + + //keep increasing the number of iterations until we last at least minTime seconds + for (iterations = 100; !done; iterations *= 2) { + double elapsed = measureTimeSub(func, iterations); + if (elapsed >= minTime) { + double itersPerSec = iterations / elapsed; + double full = 44100; + double percent = full * 100 / itersPerSec; + printf("\nmeasure %s over time %f\n", name, minTime); + + printf("did %" PRId64 " iterations in %f seconds\n", iterations, elapsed); + printf("that's %f per sec\n", itersPerSec); + printf("percent CPU usage: %f\n", percent); + printf("best case instances: %f\n", 100 / percent); + printf("quota used per 1 percent : %f\n", percent * 100); + fflush(stdout); + done = true; + } + } + } + + /** + * Run test iterators time, return total seconds. + */ + static double measureTimeSub(std::function func, int64_t iterations) + { + const double t0 = SqTime::seconds(); + for (int64_t i = 0; i < iterations; ++i) { + const T x = func(); + TestBuffers::put(x); + } + + const double t1 = SqTime::seconds(); + const double elapsed = t1 - t0; + return elapsed; + } +}; + + +/** + * Simple producer / consumer for test data. + * Serves up a precalculated list of random numbers. + */ +template +class TestBuffers +{ +public: + static const size_t size = 60000; + static void put(T x) + { + destData[destIndex++] = x; + if (destIndex >= size) { + destIndex = 0; + } + } + static T get() + { + T ret = sourceData[sourceIndex++]; + if (sourceIndex >= size) { + sourceIndex = 0; + } + return ret; + } + // + TestBuffers() + { + for (int i = 0; i < size; ++i) { + sourceData[i] = (float) rand() / (float) RAND_MAX; + } + } +private: + static size_t sourceIndex; + static size_t destIndex; + static T sourceData[size]; + static T destData[size]; +}; + + +template +T TestBuffers::sourceData[size]; + +template +T TestBuffers::destData[size]; + +template +size_t TestBuffers::sourceIndex = 0; + +template +size_t TestBuffers::destIndex = 512; + + +/** + * Simple timer implementation for running inside Visual Studio + */ + + + + + diff --git a/plugins/community/repos/squinkylabs-plug1/test/SqTime.h b/plugins/community/repos/squinkylabs-plug1/test/SqTime.h new file mode 100644 index 00000000..f37b0e0d --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/SqTime.h @@ -0,0 +1,51 @@ +#pragma once +/** + * @class SqTime + * A simple high-res timer. Returns current seconds + */ + + + /** + * Windows version is based on QueryPerformanceFrequency + * Typically this is accurate to 1/3 microsecond. Can be made more + * accurate by tinkering with your bios + */ +#if defined(_MSC_VER) || defined(ARCH_WIN) +#include +class SqTime +{ +public: + static double seconds() + { + LARGE_INTEGER t; + if (frequency == 0) { + + QueryPerformanceFrequency(&t); + frequency = double(t.QuadPart); + } + + QueryPerformanceCounter(&t); + int64_t n = t.QuadPart; + return double(n) / frequency; + } +private: + static double frequency; +}; +double SqTime::frequency = 0; + +#else +#include +class SqTime +{ +public: + static double seconds() + { + struct timespec ts; + int x = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts); + assert(x == 0); + + // seconds = sec + nsec / 10**9 + return double(ts.tv_sec) + double(ts.tv_nsec) / (1000.0 * 1000.0 * 1000.0); + } +}; +#endif diff --git a/plugins/community/repos/squinkylabs-plug1/test/TestSignal.h b/plugins/community/repos/squinkylabs-plug1/test/TestSignal.h new file mode 100644 index 00000000..aa51b8ce --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/TestSignal.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include + +/** + * Utilities for generating and analyzing test signals. + * + * All test signals are +-1volt unless otherwise specified + */ + +template +class TestSignal +{ +public: + TestSignal() = delete; // we are only static + // freq is normalized f / sr + static void generateSin(T * output, int numSamples, T freq); + static double getRMS(const T * signal, int numSamples); + + /** + * Measure the gain of a processing lambda at a specificed frequency + */ + static T measureGain(T freq, std::function func); + + /** + * Measure the RMS output of a generator lambda + */ + static double measureOutput(int samples, std::function func); +}; + + +template +inline void TestSignal::generateSin(T * output, int numSamples, T freq) +{ + assert(freq < .5); + assert(freq > 0); + + double phase = 0; + for (int i = 0; i < numSamples; ++i) { + const double phi = phase * 2 * AudioMath::Pi; + + output[i] = T(std::sin(phi)); + phase += freq; + // should we normalize the phase here, instead of letting it grow? + } +} + +template +inline double TestSignal::getRMS(const T * signal, int numSamples) +{ + assert(numSamples > 0); + double sumSq = 0; + for (int i = 0; i < numSamples; ++i) { + //printf("getRMS, i=%d, samp=%d\n") + sumSq += double(signal[i]) * signal[i]; + } + return std::sqrt(sumSq / numSamples); +} + +template +inline T TestSignal::measureGain(T freq, std::function func) +{ + const int size = 60000; + T input[size]; + T output[size]; + + //generateSin(T * output, int numSamples, T freq); + TestSignal::generateSin(input, size, freq); + for (int i = 0; i < size; ++i) { + output[i] = func(input[i]); + } + T vo = T(TestSignal::getRMS(output, size)); + T vi = T(TestSignal::getRMS(input, size)); + return vo / vi; +} + +template +inline double TestSignal::measureOutput(int numSamples, std::function func) +{ + //std::unique_ptr buffer(new T[numSamples]); + std::vector buffer(numSamples); + for (int i = 0; i < numSamples; ++i) { + buffer[i] = func(); + } + + return getRMS(buffer.data(), numSamples); + +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/TimeStatsCollector.h b/plugins/community/repos/squinkylabs-plug1/test/TimeStatsCollector.h new file mode 100644 index 00000000..7941afa0 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/TimeStatsCollector.h @@ -0,0 +1,72 @@ +#pragma once +#include "SqTime.h" + +/** + * Unfinished time class. Meant to be inserted into other code (like VCV Rack). + * Averages states over time. + * Will printf states every now and then. + * Many ways to improve this - printing from a different thread is one. + */ +class TimeStatsCollector +{ +public: + TimeStatsCollector() + { + } + + /** + * Instrumented code calls this to start a timing sample. + */ + void start() + { + startTime = SqTime::seconds(); + } + + /** + * Instrumented code calls this to step timing + * @param numLoops is the number of "events" since start was called. + */ + void stop(int numLoops) + { + const double stop = SqTime::seconds(); + const double delta = stop - startTime; + numDataPoints += numLoops; + totalTimeFrame += delta; + if (delta < minTime) { + minTime = delta; + } else if (delta > maxTime) { + maxTime = delta; + } + + if (numDataPoints > 1000) { + const double avgTime = totalTimeFrame / numDataPoints; + const double srTime = (1.0 / 44100.0); + const double ratio = avgTime / srTime; + totalTimeGlobal += avgTime; + numFramesInTotal++; + + double runningAvg = totalTimeGlobal / (numFramesInTotal * srTime); + + printf("\nsrTime=%f avtTime=%f\n", srTime, avgTime); + printf("this block: %f%% min=%f max=%f globalmax=%f running avg=%f\n", + ratio * 100, minTime, maxTime, globalMax, runningAvg * 100); + if (globalMax < maxTime) { + globalMax = maxTime; + } + numDataPoints=0; + totalTimeFrame = 0; + minTime = 1000000000; + maxTime = -100000000000; + } + } + +private: + int numDataPoints = 0; // number of samples in current frame. + double totalTimeFrame = 0; // Time spent in the test code this frame. + double startTime; // Start time if current sample + double minTime = 1000000000; // shorted time observed in current frame + double maxTime = -100000000000;// longest time observed in current frame. + double globalMax = -100000000000; + double totalTimeGlobal = 0; + int numFramesInTotal = 0; +}; \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/asserts.h b/plugins/community/repos/squinkylabs-plug1/test/asserts.h new file mode 100644 index 00000000..66f5bf7e --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/asserts.h @@ -0,0 +1,57 @@ +#pragma once + +#include "AudioMath.h" + +#include +#include + +/** + * Our own little assert library, loosely inspired by Chai Assert. + * + * Will print information on failure, then generate a "real" assertion + */ + + +#define assertEQEx(actual, expected, msg) if (actual != expected) { \ + std::cout << "assertEq failed " << msg << " actual value =" << \ + actual << " expected=" << expected << std::endl; \ + assert(false); } + +#define assertEQ(actual, expected) assertEQEx(actual, expected, "") + +#define assertNEEx(actual, expected, msg) if (actual == expected) { \ + std::cout << "assertNE failed " << msg << " did not expect " << \ + actual << " to be == to " << expected << std::endl; \ + assert(false); } + +#define assertNE(actual, expected) assertNEEx(actual, expected, "") + +#define assertClose(actual, expected, diff) if (!AudioMath::closeTo(actual, expected, diff)) { \ + std::cout << "assertClose failed actual value =" << \ + actual << " expected=" << expected << std::endl << std::flush; \ + assert(false); } + + +// assert less than +#define assertLT(actual, expected) if ( actual >= expected) { \ + std::cout << "assertLt " << expected << " actual value = " << \ + actual << std::endl ; \ + assert(false); } + +// assert less than or equal to +#define assertLE(actual, expected) if ( actual > expected) { \ + std::cout << "assertLE " << expected << " actual value = " << \ + actual << std::endl ; \ + assert(false); } + +// assert greater than +#define assertGT(actual, expected) if ( actual <= expected) { \ + std::cout << "assertGT " << expected << " actual value = " << \ + actual << std::endl ; \ + assert(false); } +// assert greater than or equal to +#define assertGE(actual, expected) if ( actual < expected) { \ + std::cout << "assertGE " << expected << " actual value = " << \ + actual << std::endl ; \ + assert(false); } +// leave space after macro \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/main.cpp b/plugins/community/repos/squinkylabs-plug1/test/main.cpp new file mode 100644 index 00000000..3629eaaf --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/main.cpp @@ -0,0 +1,90 @@ +/** + * Unit test entry point + */ + +#include +#include +#include + +extern void testBiquad(); +extern void testTestSignal(); +extern void testSaw(); +extern void testLookupTable(); +extern void testSinOscillator(); +extern void testHilbert(); +extern void testAudioMath(); +extern void perfTest(); +extern void testFrequencyShifter(); +extern void testStateVariable(); +extern void testVocalAnimator(); +extern void testObjectCache(); +extern void testThread(bool exended); +extern void testFFT(); +extern void testRingBuffer(); +extern void testManagedPool(); +extern void testColoredNoise(); +extern void testFFTCrossFader(); +extern void testFinalLeaks(); +extern void testClockMult(); +extern void testTremolo(); +extern void testGateTrigger(); + +int main(int argc, char ** argv) +{ + bool runPerf = false; + bool extended = false; + if (argc > 1) { + std::string arg = argv[1]; + if (arg == "--ext") { + extended = true; + } + } +#ifdef _PERF + runPerf = true; +#ifndef NDEBUG +#error asserts should be off for perf test +#endif +#endif + // While this code may work in 32 bit applications, it's not tested for that. + // Want to be sure we are testing the case we care about. + assert(sizeof(size_t) == 8); + + testAudioMath(); + testRingBuffer(); + testGateTrigger(); + testManagedPool(); + testLookupTable(); + testObjectCache(); + testTestSignal(); + testBiquad(); + testSaw(); + testClockMult(); + + testSinOscillator(); + testHilbert(); + testStateVariable(); + + testFFT(); + testFFTCrossFader(); + testThread(extended); + + // after testing all the components, test composites. + testTremolo(); + testColoredNoise(); + testFrequencyShifter(); + testVocalAnimator(); + + if (runPerf) { + perfTest(); + } + + testFinalLeaks(); + + // When we run inside Visual Studio, don't exit debugger immediately +#if defined(_MSC_VER) + printf("Test passed. Press any key to continue...\n"); fflush(stdout); + getchar(); +#else + printf("Tests passed.\n"); +#endif +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/perfTest.cpp b/plugins/community/repos/squinkylabs-plug1/test/perfTest.cpp new file mode 100644 index 00000000..d1569819 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/perfTest.cpp @@ -0,0 +1,267 @@ +#include +#include +#include +#include + +#include "AudioMath.h" +#include "BiquadParams.h" +#include "BiquadFilter.h" +#include "BiquadState.h" +#include "ColoredNoise.h" +#include "FrequencyShifter.h" +#include "HilbertFilterDesigner.h" +#include "LookupTableFactory.h" +#include "TestComposite.h" +#include "Tremolo.h" +#include "VocalAnimator.h" +#include "VocalFilter.h" + +using Shifter = FrequencyShifter; +using Animator = VocalAnimator; +using VocFilter = VocalFilter; +using Colors = ColoredNoise; +using Trem = Tremolo; + +#include "MeasureTime.h" + +// There are many tests that are disabled with #if 0. +// In most cases they still work, but don't need to be run regularly + +#if 0 +static void test1() +{ + double d = .1; + srand(57); + const double scale = 1.0 / RAND_MAX; + + MeasureTime::run("test1 (do nothing)", [&d, scale]() { + return TestBuffers::get(); + }, 1); + + MeasureTime::run("test1 sin", []() { + float x = std::sin(TestBuffers::get()); + return x; + }, 1); + + MeasureTime::run("test1 sin double", []() { + float x = std::sin(TestBuffers::get()); + return x; + }, 1); + + MeasureTime::run("test1 sinx2 float", []() { + float x = std::sin(TestBuffers::get()); + x = std::sin(x); + return x; + }, 1); + + MeasureTime::run("mult float-10", []() { + float x = TestBuffers::get(); + float y = TestBuffers::get(); + return x * y; + }, 10); + + MeasureTime::run("mult dbl", []() { + double x = TestBuffers::get(); + double y = TestBuffers::get(); + return x * y; + }, 1); + + MeasureTime::run("div float", []() { + float x = TestBuffers::get(); + float y = TestBuffers::get(); + return x / y; + }, 1); + + MeasureTime::run("div dbl", []() { + double x = TestBuffers::get(); + double y = TestBuffers::get(); + return x / y; + }, 1); + + MeasureTime::run("test1 (do nothing)", [&d, scale]() { + return TestBuffers::get(); + }, 1); + + MeasureTime::run("test1 pow2 float", []() { + float x = std::pow(2, TestBuffers::get()); + return x; + }, 1); + MeasureTime::run("test1 pow rnd float", []() { + float x = std::pow(TestBuffers::get(), TestBuffers::get()); + return x; + }, 1); + + MeasureTime::run("test1 exp float", []() { + float x = std::exp(TestBuffers::get()); + return x; + }, 1); +} +#endif + +template +static void testHilbert() +{ + BiquadParams paramsSin; + BiquadParams paramsCos; + BiquadState state; + HilbertFilterDesigner::design(44100, paramsSin, paramsCos); + + MeasureTime::run("hilbert", [&state, ¶msSin]() { + + T d = BiquadFilter::run(TestBuffers::get(), state, paramsSin); + return d; + }, 1); +} + +#if 0 +static void testExpRange() +{ + using T = float; + LookupTableParams table; + LookupTableFactory::makeExp2(table); + + MeasureTime::run("exp lookup", [&table]() { + + T d = LookupTable::lookup(table, TestBuffers::get()); + return d; + }, 1); +} +#endif + +static void testShifter() +{ + Shifter fs; + + fs.setSampleRate(44100); + fs.init(); + + fs.inputs[Shifter::AUDIO_INPUT].value = 0; + + MeasureTime::run("shifter", [&fs]() { + fs.inputs[Shifter::AUDIO_INPUT].value = TestBuffers::get(); + fs.step(); + return fs.outputs[Shifter::SIN_OUTPUT].value; + }, 1); +} + +static void testAnimator() +{ + Animator an; + + an.setSampleRate(44100); + an.init(); + + an.inputs[Shifter::AUDIO_INPUT].value = 0; + + MeasureTime::run("animator", [&an]() { + an.inputs[Shifter::AUDIO_INPUT].value = TestBuffers::get(); + an.step(); + return an.outputs[Shifter::SIN_OUTPUT].value; + }, 1); +} + + +static void testVocalFilter() +{ + VocFilter an; + + an.setSampleRate(44100); + an.init(); + + an.inputs[Shifter::AUDIO_INPUT].value = 0; + + MeasureTime::run("vocal filter", [&an]() { + an.inputs[Shifter::AUDIO_INPUT].value = TestBuffers::get(); + an.step(); + return an.outputs[Shifter::SIN_OUTPUT].value; + }, 1); +} + + + +static void testColors() +{ + Colors co; + + co.setSampleRate(44100); + co.init(); + + + MeasureTime::run("colors", [&co]() { + co.step(); + return co.outputs[Colors::AUDIO_OUTPUT].value; + }, 1); +} + +static void testTremolo() +{ + Trem tr; + + tr.setSampleRate(44100); + tr.init(); + + + MeasureTime::run("trem", [&tr]() { + tr.inputs[Trem::AUDIO_INPUT].value = TestBuffers::get(); + tr.step(); + return tr.outputs[Trem::AUDIO_OUTPUT].value; + }, 1); +} + +#if 0 +static void testAttenuverters() +{ + auto scaler = AudioMath::makeLinearScaler(-2, 2); + MeasureTime::run("linear scaler", [&scaler]() { + float cv = TestBuffers::get(); + float knob = TestBuffers::get(); + float trim = TestBuffers::get(); + return scaler(cv, knob, trim); + }, 1); + + LookupTableParams lookup; + LookupTableFactory::makeBipolarAudioTaper(lookup); + MeasureTime::run("bipolar lookup", [&lookup]() { + float x = TestBuffers::get(); + return LookupTable::lookup(lookup, x); + }, 1); + + + // auto refFuncPos = AudioMath::makeFunc_AudioTaper(LookupTableFactory::audioTaperKnee()); + + { + + auto bipolarScaler = [&lookup, &scaler](float cv, float knob, float trim) { + float scaledTrim = LookupTable::lookup(lookup, cv); + return scaler(cv, knob, scaledTrim); + }; + + MeasureTime::run("bipolar scaler", [&bipolarScaler]() { + float cv = TestBuffers::get(); + float knob = TestBuffers::get(); + float trim = TestBuffers::get(); + return bipolarScaler(cv, knob, trim); + }, 1); + } +} +#endif + +void perfTest() +{ +#if 0 + testAttenuverters(); + testExpRange(); +#endif + testVocalFilter(); + testAnimator(); + testShifter(); + + testColors(); + testTremolo(); + + // test1(); +#if 0 + testHilbert(); + testHilbert(); +#endif +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testAudioMath.cpp b/plugins/community/repos/squinkylabs-plug1/test/testAudioMath.cpp new file mode 100644 index 00000000..5b8d1827 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testAudioMath.cpp @@ -0,0 +1,125 @@ + +#include +#include + +#include "asserts.h" +#include "AudioMath.h" +#include "LookupTableFactory.h" + +using namespace std; + +static void test0() +{ + assert(AudioMath::closeTo(0, 0, .000001)); + assert(AudioMath::closeTo(1000, 1001, 1.1)); + assert(!AudioMath::closeTo(1000, 1010, 1.1)); + assert(!AudioMath::closeTo(1010, 1000, 1.1)); +} + +static void test1() +{ + assert(AudioMath::closeTo(3.145, AudioMath::Pi, .1)); + assert(AudioMath::closeTo(3.145 / 2, AudioMath::Pi_2, .1)); + assert(AudioMath::closeTo(log(10), AudioMath::Ln10, .001)); +} + +static void test2() +{ + const double d = .0001; + std::function f = AudioMath::makeFunc_Sin(); + assert(AudioMath::closeTo(f(0), 0, d)); + assert(AudioMath::closeTo(f(.5), 0, d)); + assert(AudioMath::closeTo(f(.25), 1, d)); + assert(AudioMath::closeTo(f(.75), -1, d)); + assert(AudioMath::closeTo(f(.125), 1.0 / sqrt(2), d)); +} + +static void test3() +{ + const double d = .0001; + std::function f = AudioMath::makeFunc_Exp(0, 4, 2, 32); + + assert(AudioMath::closeTo(f(0), 2, d)); + assert(AudioMath::closeTo(f(1), 4, d)); + assert(AudioMath::closeTo(f(2), 8, d)); + assert(AudioMath::closeTo(f(3), 16, d)); + assert(AudioMath::closeTo(f(4), 32, d)); + assert(f(5) > 33); + assert(f(-1) < 1.5); +} + +static void testAudioTaper() +{ + double db = -18; + std::function f = AudioMath::makeFunc_AudioTaper(db); + assertClose(f(1), 1, .001); + assertClose(f(.25), .125, .001); + assertClose(f(.251), .126, .001); + assertClose(f(.249), 1.0 / 8.0, .001); + assertClose(f(0), 0, .001); +} + +static void testScaler() +{ + AudioMath::ScaleFun f = AudioMath::makeLinearScaler(3, 4); + // scale(cv, knob, trim + + // knob comes through only shifted + assertEQ(f(0, -5, 0), 3.); + assertEQ(f(0, 5, 0), 4.); + assertEQ(f(0, 0, 0), 3.5); + + // cv also come through, it trim up + assertEQ(f(-5, 0, 1), 3.); + assertEQ(f(5, 0, 1), 4.); + assertEQ(f(0, 0, 1), 3.5); + + // no cv if trim 0 + assertEQ(f(-5, 0, 0), 3.5); + + // neg trim inverts cv + assertEQ(f(-5, 0, -1), 4.); + + + // trim half way + assertEQ(f(5, 0, .5), 3.75); +} + +static void testBipolarAudioScaler() +{ + AudioMath::ScaleFun f = AudioMath::makeBipolarAudioScaler(3, 4); + // scale(cv, knob, trim + + // knob comes through only shifted + assertEQ(f(0, -5, 0), 3.); + assertEQ(f(0, 5, 0), 4.); + assertEQ(f(0, 0, 0), 3.5); + + // cv also come through, it trim up + assertEQ(f(-5, 0, 1), 3.); + assertEQ(f(5, 0, 1), 4.); + assertEQ(f(0, 0, 1), 3.5); + + // no cv if trim 0 + assertEQ(f(-5, 0, 0), 3.5); + + // neg trim inverts cv + assertEQ(f(-5, 0, -1), 4.); + + // trim quarter - should be audio knee + auto f2 = AudioMath::makeBipolarAudioScaler(-1, 1); + float x = f2(5, 0, .25); + float y = (float) AudioMath::gainFromDb(LookupTableFactory::audioTaperKnee()); + assertClose(x, y, .001); +} + +void testAudioMath() +{ + test0(); + test1(); + test2(); + test3(); + testAudioTaper(); + testScaler(); + testBipolarAudioScaler(); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testBiquad.cpp b/plugins/community/repos/squinkylabs-plug1/test/testBiquad.cpp new file mode 100644 index 00000000..5f78d681 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testBiquad.cpp @@ -0,0 +1,208 @@ +/** + * + */ + +#include +#include + +#include "ButterworthFilterDesigner.h" +#include "BiquadFilter.h" +#include "BiquadFilter.h" +#include "BiquadParams.h" +#include "BiquadParams.h" +#include "BiquadState.h" +#include "BiquadState.h" + + +template +static void testState_0() +{ + BiquadState p; + for (int i = 0; i < N; ++i) { + assert(p.z0(i) == 0); + assert(p.z1(i) == 0); + } + p.z0(0) = 5; + p.z1(0) = 6; + assert(p.z0(0) == 5); + assert(p.z1(0) == 6); + + if (N > 1) { + p.z0(N - 1) = 55; + p.z1(N - 1) = 66; + assert(p.z0(N - 1) == 55); + assert(p.z1(N - 1) == 66); + } + + assert(p.z0(0) == 5); + assert(p.z1(0) == 6); +} + +template +static void testParam_0() +{ + + BiquadParams p; + for (int i = 0; i < N; ++i) { + assert(p.A2(i) == 0); + assert(p.A1(i) == 0); + assert(p.B0(i) == 0); + assert(p.B1(i) == 0); + assert(p.B2(i) == 0); + } + + p.A1(0) = 1; + p.A2(0) = 2; + p.B0(0) = 10; + p.B1(0) = 11; + p.B2(0) = 12; + + assert(p.A1(0) == 1); + assert(p.A2(0) == 2); + assert(p.B0(0) == 10); + assert(p.B1(0) == 11); + assert(p.B2(0) == 12); + + if (N > 1) { + p.A1(N - 1) = 111; + p.A2(N - 1) = 112; + p.B0(N - 1) = 1110; + p.B1(N - 1) = 1111; + p.B2(N - 1) = 1112; + + assert(p.A1(N - 1) == 111); + assert(p.A2(N - 1) == 112); + assert(p.B0(N - 1) == 1110); + assert(p.B1(N - 1) == 1111); + assert(p.B2(N - 1) == 1112); + } + + assert(p.A1(0) == 1); + assert(p.A2(0) == 2); + assert(p.B0(0) == 10); + assert(p.B1(0) == 11); + assert(p.B2(0) == 12); +} + +template +static void test2() +{ + BiquadParams p; + ButterworthFilterDesigner::designThreePoleLowpass(p, T(.1)); + BiquadState s; + T d = BiquadFilter::run(0, s, p); + (void) d; +} + +// test that filter designer does something (more than just generate zero +template +static void testBasicDesigner2() +{ + BiquadParams p; + ButterworthFilterDesigner::designTwoPoleLowpass(p, T(.1)); + assert(p.A1(0) != 0); + assert(p.A2(0) != 0); + assert(p.B1(0) != 0); + assert(p.B2(0) != 0); + assert(p.B0(0) != 0); +} + +// test that filter designer does something (more than just generate zero +template +static void testBasicDesigner3() +{ + BiquadParams p; + ButterworthFilterDesigner::designThreePoleLowpass(p, T(.1)); + assert(p.A1(0) != 0); + assert(p.A2(0) != 0); + assert(p.B1(0) != 0); + assert(p.B2(0) != 0); + assert(p.B0(0) != 0); +} + +// test that filter does something +template +static void testBasicFilter2() +{ + BiquadParams params; + BiquadState state; + ButterworthFilterDesigner::designTwoPoleLowpass(params, T(.1)); + + T lastValue = -1; + + // the first five values of the step increase + for (int i = 0; i < 100; ++i) { + T temp = BiquadFilter::run(1, state, params); + if (i < 5) { + // the first 5 are strictly increasing and below 1 + assert(temp < 1); + assert(temp > lastValue); + } else if (i < 10) { + // the next are all overshoot + assert(temp > 1 && temp < 1.05); + } else if (i > 50) { + //settled + assert(temp > .999 && temp < 1.001); + } + + lastValue = temp; + } + const T val = BiquadFilter::run(1, state, params); + (void) val; + +} + +// test that filter does something +template +static void testBasicFilter3() +{ + BiquadParams params; + BiquadState state; + ButterworthFilterDesigner::designThreePoleLowpass(params, T(.1)); + + T lastValue = 1; + + //the first five values of the step decrease (to -1) + for (int i = 0; i < 100; ++i) { + T temp = BiquadFilter::run(1, state, params); + + if (i < 6) { + // the first 5 are strictly increasing and below 1 + assert(temp > -1); + assert(temp < lastValue); + } else if (i < 10) { + // the next are all overshoot + assert(temp < -1); + assert(temp > -1.1); + } else if (i > 400) { + //settled + assert(temp < -.999 && temp > -1.001); + } + lastValue = temp; + } +} + + +void testBiquad() +{ + testState_0(); + testState_0(); + + testParam_0(); + testParam_0(); + + test2(); + test2(); + + testBasicDesigner2(); + testBasicDesigner2(); + testBasicDesigner3(); + testBasicDesigner3(); + + testBasicFilter2(); + testBasicFilter2(); + testBasicFilter3(); + testBasicFilter3(); + + // TODO: actually measure the freq resp +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testClockMult.cpp b/plugins/community/repos/squinkylabs-plug1/test/testClockMult.cpp new file mode 100644 index 00000000..1440c4c4 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testClockMult.cpp @@ -0,0 +1,180 @@ + +#include +#include "asserts.h" + +#include "ClockMult.h" +#include "TestComposite.h" +#include "Tremolo.h" + + +/** + * During training, we should get no output. + * On first ref-clock after training, should go high + */ +static void test0() +{ + ClockMult cm; + cm.setMultiplier(1); + const int period = 4; + + // printf("test0, 0\n"); + cm.refClock(); // give it an external clock + + // printf("test0, 1\n"); + // train with 4 ref clocks() + for (int i = 0; i < period; ++i) { + cm.sampleClock(); + assertEQ(cm.getSaw(), 0); + assertEQ(cm.getMultipliedClock(), false); + // printf("test0, 2\n"); + + } + + // send ref-clock now to set period at 4 and start clocking + cm.refClock(); + assertEQ(cm.getSaw(), 0); + assertEQ(cm.getMultipliedClock(), true); + assertClose(cm._getFreq(), .25f, .000001); +} + + +/** + * Test synched saw output + */ +static void test1() +{ + ClockMult cm; + cm.setMultiplier(1); + const int period = 4; + + // printf("test0, 0\n"); + cm.refClock(); // give it an external clock + + // printf("test0, 1\n"); + // train with 4 ref clocks() + for (int i = 0; i < period; ++i) { + cm.sampleClock(); + assertEQ(cm.getSaw(), 0); + assertEQ(cm.getMultipliedClock(), false); + // printf("test0, 2\n"); + + } + + // send ref-clock now to set period at 4 and start clocking + cm.refClock(); + assertEQ(cm.getSaw(), 0); + assertEQ(cm.getMultipliedClock(), true); + assertClose(cm._getFreq(), .25f, .000001); + + for (int i = 0; i < period - 1; ++i) { + cm.sampleClock(); + // printf("in loop, i=%d, saw=%f\n", i, cm.getSaw()); + assertEQ(cm.getSaw(), .25 * (i + 1)); + assertEQ(cm.getMultipliedClock(), true); + } +} + +/** + * Test free running + */ +static void test2() +{ + ClockMult cm; + cm.setMultiplier(0); + cm.setFreeRunFreq(.1f); + + assertEQ(cm.getSaw(), 0); + for (int i = 0; i < 9; ++i) { + cm.sampleClock(); + assertClose(cm.getSaw(), (i + 1) * .1f, .0001); + } + cm.sampleClock(); + assertClose(cm.getSaw(), 0, .0001); + for (int i = 0; i < 9; ++i) { + cm.sampleClock(); + assertClose(cm.getSaw(), (i + 1) * .1f, .0001); + } +} + + +/** +* Test free running, no interference from ref clock +*/ +static void test4() +{ + ClockMult cm; + cm.setMultiplier(0); + cm.setFreeRunFreq(.1f); + + assertEQ(cm.getSaw(), 0); + for (int i = 0; i < 9; ++i) { + cm.sampleClock(); + cm.refClock(); + assertClose(cm.getSaw(), (i + 1) * .1f, .0001); + } + cm.sampleClock(); + assertClose(cm.getSaw(), 0, .0001); + for (int i = 0; i < 9; ++i) { + cm.refClock(); + cm.sampleClock(); + assertClose(cm.getSaw(), (i + 1) * .1f, .0001); + } +} +/** +* Test synched saw output long term, no jitter +*/ +static void test3(int mult) +{ + + ClockMult cm; + cm.setMultiplier(mult); + + // ref clock period. + const int period = 4 * mult; + + + cm.refClock(); // give it an external clock + + // train with period of ref clocks() + for (int i = 0; i < period; ++i) { + cm.sampleClock(); + assertEQ(cm.getSaw(), 0); + assertEQ(cm.getMultipliedClock(), false); + } + + for (int j = 0; j < 2; ++j) { + // send ref-clock now to set period at 4 and start clocking + cm.refClock(); + + assertEQ(cm.getSaw(), 0); + + assertEQ(cm.getMultipliedClock(), true); + assertClose(cm._getFreq(), .25f, .000001); + + for (int i = 0; i < period; ++i) { + cm.sampleClock(); + //printf("in loop, i=%d saw = %f\n", i, cm.getSaw()); + float expectedSaw = .25f * (i + 1); + while (expectedSaw >= 1) { + expectedSaw -= 1.f; + } + assertClose(cm.getSaw(), expectedSaw, .0001); + + // clock out not working yet + // assertEQ(cm.getMultipliedClock(), true); + } + } +} + +void testClockMult() +{ + test0(); + test1(); + test2(); + test4(); + test3(1); + test3(2); + test3(3); + test3(4); + +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testColoredNoise.cpp b/plugins/community/repos/squinkylabs-plug1/test/testColoredNoise.cpp new file mode 100644 index 00000000..50ffede8 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testColoredNoise.cpp @@ -0,0 +1,96 @@ + +#include "ColoredNoise.h" +#include "TestComposite.h" +#include "asserts.h" + +extern void testFinalLeaks(); + +using Noise = ColoredNoise; + +// bring it up and process a bit. +static void test0() +{ + assertEQ(ThreadServer::_count, 0); + assertEQ(FFTDataCpx::_count, 0); + { + Noise cn; + cn.init(); + // calling step should get client to request an FFT frame + + while (cn._msgCount() < 1) { + cn.step(); + + } + } + assertEQ(ThreadServer::_count, 0); + assertEQ(FFTDataCpx::_count, 0); +} + +// porcess until audio comes out +static void test1() +{ + Noise cn; + cn.init(); + // calling step should get client to request an FFT frame + int num = 0; + bool started = false; + for (bool done = false; !done; ) { + cn.step(); + const float output = cn.outputs[Noise::AUDIO_OUTPUT].value; + if (output > .1) {; + started = true; + } + assert(output < 10); + assert(output > -10); + if (started) { + num++; + if (num > 100000) { + done = true; + } + } + } +} + + +// try to request a few different kinds of noise +static void test2() +{ + Noise cn; + cn.init(); + while (cn._msgCount() < 1) { + cn.step(); + } + cn.params[Noise::SLOPE_PARAM].value = -6; + while (cn._msgCount() < 2) { + cn.step(); + } + cn.params[Noise::SLOPE_PARAM].value = 3; + while (cn._msgCount() < 3) { + cn.step(); + } + cn.params[Noise::SLOPE_PARAM].value = 2; + while (cn._msgCount() < 4) { + cn.step(); + } + cn.params[Noise::SLOPE_PARAM].value = 2.3f; + while (cn._msgCount() < 5) { + cn.step(); + } + cn.params[Noise::SLOPE_PARAM].value = 2.5f; + while (cn._msgCount() < 6) { + cn.step(); + } + cn.params[Noise::SLOPE_PARAM].value = -1.2f; + while (cn._msgCount() < 7) { + cn.step(); + } +} + +void testColoredNoise() +{ + + test0(); + test1(); + test2(); + testFinalLeaks(); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testFFT.cpp b/plugins/community/repos/squinkylabs-plug1/test/testFFT.cpp new file mode 100644 index 00000000..636b63b6 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testFFT.cpp @@ -0,0 +1,292 @@ + +#include "asserts.h" +#include +#include + +#include "AudioMath.h" +#include "FFTData.h" +#include "FFT.h" + +extern void testFinalLeaks(); + +static void testAccessors() +{ + FFTDataReal d0(16); + + d0.set(0, 4); + assertEQ(d0.get(0), 4); + + FFTDataCpx dc(16); + + cpx x(3, 4); + dc.set(5, x); + assertEQ(dc.get(5), x); +} + +static void testFFTErrors() +{ + FFTDataReal real(16); + FFTDataCpx cpx(15); + const bool b = FFT::forward(&cpx, real); + assert(!b); // should error if size mismatch +} + +static void testForwardFFT_DC() +{ + FFTDataReal real(16); + FFTDataCpx complex(16); + + // set real for DC + for (int i = 0; i < 16; ++i) { + real.set(i, 1.0); + } + const bool b = FFT::forward(&complex, real); + assert(b); + + for (int i = 0; i < 16; ++i) { + cpx v = complex.get(i); + float mag = std::abs(v); + float expect = (i == 0) ? 1.f : 0.f; + assertEQ(mag, expect); + } +} + +static void test3() +{ + FFTDataReal real(16); + FFTDataCpx complex(16); + + // set real for fundamental sin. + // make peak 2.0 so fft will come out to one + for (int i = 0; i < 16; ++i) { + auto x = 2.0 *sin(AudioMath::Pi * 2.0 * i / 16.0); + real.set(i, float(x)); + } + const bool b = FFT::forward(&complex, real); + assert(b); + + for (int i = 0; i < 16; ++i) { + cpx v = complex.get(i); + float mag = std::abs(v); + + float expect = (i == 1) ? 1.f : 0.f; + assertClose(mag, expect, .0001); + } +} + + +static void testRoundTrip() +{ + FFTDataReal realIn(16); + FFTDataReal realOut(16); + FFTDataCpx complex(16); + + // set real for DC + for (int i = 0; i < 16; ++i) { + realIn.set(i, 1.0); + } + + bool b = FFT::forward(&complex, realIn); + assert(b); + b = FFT::inverse(&realOut, complex); + + for (int i = 0; i < 16; ++i) { + + float expect = 1.f; // scaled DC (TODO: fix scaling) + assertEQ(realOut.get(i) , expect); + } +} + + +static void testNoiseFormula() +{ + const int bins = 64 * 1024 ; + std::unique_ptr data(new FFTDataCpx(bins)); + assertEQ(data->size(), bins); + + FFT::makeNoiseSpectrum(data.get(), ColoredNoiseSpec()); + + std::set phases; + + for (int i = 0; i < bins; ++i) { + const cpx x = data->get(i); + float mag = std::abs(x); + float phase = std::arg(x); + + const float expectedMag = (i == 0) ? 0.f : (i < (bins / 2)) ? 1.f : 0.f; + + assertClose(mag, expectedMag, .0001); + phases.insert(phase); + } +} + +static float getPeak(const FFTDataReal& data) +{ + float peak = 0; + for (int i = 0; i < data.size(); ++i) { + peak = std::max(peak, std::abs(data.get(i))); + } + return peak; +} + + +static void testWhiteNoiseRT() +{ + const int bins = 2048; + std::unique_ptr noiseSpectrum(new FFTDataCpx(bins)); + std::unique_ptr noiseRealSignal(new FFTDataReal(bins)); + std::unique_ptr noiseSpectrum2(new FFTDataCpx(bins)); + + for (int i = 0; i < bins; ++i) { + cpx x(0,0); + noiseSpectrum2->set(i, x); + } + + FFT::makeNoiseSpectrum(noiseSpectrum.get(), ColoredNoiseSpec()); + + FFT::inverse(noiseRealSignal.get(), *noiseSpectrum); + + FFT::forward(noiseSpectrum2.get(), *noiseRealSignal); + + float totalPhase = 0; + float minPhase = 0; + float maxPhase = 0; + for (int i = 0; i < bins/2; ++i) { + float expected = (i == 0) ? 0.f : 1.f; + cpx data = noiseSpectrum2->get(i); + + assertClose(std::abs(data), expected, .0001); + const float phase = std::arg(data); + totalPhase += phase; + minPhase = std::min(phase, minPhase); + maxPhase = std::max(phase, maxPhase); + + //printf("phase[%d] = %f\n", i, std::arg(data)); + } + //printf("TODO: assert on phase\n"); + //printf("total phase=%f, average=%f\n", totalPhase, totalPhase / (bins / 2)); + //printf("maxPhase %f min %f\n", maxPhase, minPhase); +} + +static void testNoiseRTSub(int bins) +{ + std::unique_ptr dataCpx(new FFTDataCpx(bins)); + std::unique_ptr dataReal(new FFTDataReal(bins)); + assertEQ(dataCpx->size(), bins); + FFT::makeNoiseSpectrum(dataCpx.get(), ColoredNoiseSpec()); + + FFT::inverse(dataReal.get(), *dataCpx); + FFT::normalize(dataReal.get()); + + const float peak = getPeak(*dataReal); + + assertClose( peak, 1.0f , .001); + +} + +static void testNoiseRT() +{ + testNoiseRTSub(4); + testNoiseRTSub(8); + testNoiseRTSub(16); + testNoiseRTSub(1024); + testNoiseRTSub(1024 * 64); +} + + +static void testPinkNoise() +{ + const int bins = 1024*4; + std::unique_ptr data(new FFTDataCpx(bins)); + assertEQ(data->size(), bins); + + ColoredNoiseSpec spec; + spec.highFreqCorner = 22100; // makes no difference for - slope; + spec.slope = -3; + spec.sampleRate = 44100; + + FFT::makeNoiseSpectrum(data.get(), spec); + + + // pick a starting bin above our 40 hz low freq corner + const int baseBin = 16; + //float freqBase = 44100 * baseBin / (float) bins; + const float freqBase = FFT::bin2Freq(baseBin, 44100, bins); + assertGT (freqBase, 80); + + // mid-band, quadruple freq should reduce amp by 6db + float mag16 = std::abs(data->get(baseBin)); + float mag64 = std::abs(data->get(4 * baseBin)); + + // TODO: compare in db + assertClose(mag16, 2 * mag64, .01); + + + float lastMag = std::abs(data->get(1)); + for (int i = 1; i < bins / 2; ++i) { + const float mag = std::abs(data->get(i)); + assertLE(mag, lastMag); + lastMag = mag; + } + + for (int i = bins / 2; i < bins; ++i) { + assertClose(std::abs(data->get(i)), 0, .00001); + } +} + +static void testBlueNoise(float corner = 0) +{ + const int bins = 1024 * 4; + std::unique_ptr data(new FFTDataCpx(bins)); + assertEQ(data->size(), bins); + + ColoredNoiseSpec spec; + spec.slope = 3; + spec.sampleRate = 44100; + + if (corner != 0) { + spec.highFreqCorner = corner; + } else { + assertEQ(spec.highFreqCorner, 4000); + } + + FFT::makeNoiseSpectrum(data.get(), spec); + + float freq16 = 44100 * 16 / (float) bins; + assertGT(freq16, 20); + + // mid-band, quadruple freq should reduce amp by 6db + float mag16 = std::abs(data->get(16)); + float mag64 = std::abs(data->get(64)); + + assertClose(2 * mag16, mag64, .1); + + float lastMag = 0; + for (int i = 1; i < bins / 2; ++i) { + const float mag = std::abs(data->get(i)); + assertGE(mag, .999f * lastMag); + lastMag = mag; + } + + for (int i = bins / 2; i < bins; ++i) { + assertClose(std::abs(data->get(i)), 0, .00001); + } +} + +void testFFT() +{ + assertEQ(FFTDataReal::_count, 0); + assertEQ(FFTDataCpx::_count, 0); + testAccessors(); + testFFTErrors(); + testForwardFFT_DC(); + test3(); + testRoundTrip(); + testNoiseFormula(); + testNoiseRT(); + testPinkNoise(); + testBlueNoise(); + testBlueNoise(8000.f); + testWhiteNoiseRT(); + testFinalLeaks(); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testFFTCrossFader.cpp b/plugins/community/repos/squinkylabs-plug1/test/testFFTCrossFader.cpp new file mode 100644 index 00000000..8102dcd1 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testFFTCrossFader.cpp @@ -0,0 +1,313 @@ +#include "FFTCrossFader.h" +#include "ColoredNoise.h" +#include "asserts.h" + +#include + + +class Tester +{ +public: + Tester(int crossFadeSize, int frameSize) : + f(crossFadeSize) + { + for (int i = 0; i < 3; ++i) { + std::shared_ptr p = std::make_shared(frameSize); + messages.push_back(p); + } + } + + FFTCrossFader f; + std::vector< std::shared_ptr > messages; +}; + +// accepting data on empty should not return on +static void test0() +{ + Tester test(4, 10); + assertEQ(test.messages[0]->dataBuffer->get(0), 0); + assertEQ(test.messages[0]->dataBuffer->get(9), 0); + + NoiseMessage* t = test.f.acceptData(test.messages[0].get()); + assertEQ(t, 0); +} + +//empty should return 0 +static void test1() +{ + Tester test(4, 10); + for (int i = 0; i < 20; ++i) { + float x = 5; + test.f.step(&x); + assertEQ(x, 0); + } +} + +// one buff, should play it +static void test2() +{ + Tester test(4, 10); + for (int i = 0; i < 10; ++i) { + test.messages[0]->dataBuffer->set(i, float(i)); + } + NoiseMessage* t = test.f.acceptData(test.messages[0].get()); + assertEQ(t, 0); + + // pluy buffer once + for (int i = 0; i < 10; ++i) { + float x = 5; + test.f.step(&x); + assertEQ(x, i); + } + + //play it again. + for (int i = 0; i < 10; ++i) { + float x = 5; + test.f.step(&x); + assertEQ(x, i); + } +} + + +// two buff, should crossfade +static void test3(bool testBuff0) +{ + Tester test(4, 10); + + // fill the buff to test with data, other one with zeros + for (int i = 0; i < 10; ++i) { + test.messages[0]->dataBuffer->set(i, testBuff0 ? 9.f : 0.f); + test.messages[1]->dataBuffer->set(i, testBuff0 ? 0.f : 18.f); + } + + // put both in, to cross fade + NoiseMessage* t = test.f.acceptData(test.messages[0].get()); + assertEQ(t, 0); + t = test.f.acceptData(test.messages[1].get()); + assertEQ(t, 0); + + int emptyCount = 0; + + // play buffer once + + // buffer 0 full of 9, so should see fade 9..0 + float expected0[] = {9, 6, 3, 0, 0, 0, 0, 0, 0, 0}; + + // buffer 0 all zero, 1 all 18, so should see 0..18 + float expected1[] = {0, 6, 12, 18, 18, 18, 18, 18, 18, 18}; + for (int i = 0; i < 10; ++i) { + float x = 5; + t = test.f.step(&x); + if (t) { + ++emptyCount; + } + const float expected = testBuff0 ? expected0[i] : expected1[i]; + assertEQ(x, expected); + } + + //play it again. + for (int i = 0; i < 10; ++i) { + float x = 5; + // test.f.step(&x); + t = test.f.step(&x); + if (t) { + ++emptyCount; + } + const float expectedTail = testBuff0 ? 0.f : 18.f; + assertEQ(x, expectedTail); + } + + assertEQ(emptyCount, 1); +} + + +// two buff, should crossfade. odd size crossfade +static void test7(bool testBuff0) +{ + Tester test(5, 10); + + // fill the buff to test with data, other one with zeros + for (int i = 0; i < 10; ++i) { + test.messages[0]->dataBuffer->set(i, testBuff0 ? 12.f : 0.f); + test.messages[1]->dataBuffer->set(i, testBuff0 ? 0.f : 24.f); + } + + // put both in, to cross fade + NoiseMessage* t = test.f.acceptData(test.messages[0].get()); + assertEQ(t, 0); + t = test.f.acceptData(test.messages[1].get()); + assertEQ(t, 0); + + int emptyCount = 0; + + // play buffer once + + float expected0[] = {12, 9, 6, 3, 0, 0, 0, 0, 0, 0}; + + float expected1[] = {0, 6, 12, 18, 24, 24, 24, 24, 24, 24}; + for (int i = 0; i < 10; ++i) { + float x = 5; + t = test.f.step(&x); + if (t) { + ++emptyCount; + } + const float expected = testBuff0 ? expected0[i] : expected1[i]; + assertEQ(x, expected); + } + + //play it again. + for (int i = 0; i < 10; ++i) { + float x = 5; + // test.f.step(&x); + t = test.f.step(&x); + if (t) { + ++emptyCount; + } + const float expectedTail = testBuff0 ? 0.f : 24.f; + assertEQ(x, expectedTail); + } + + assertEQ(emptyCount, 1); +} + +// extra buffer rejected +static void test4() +{ + Tester test(4, 10); + NoiseMessage* t = test.f.acceptData(test.messages[0].get()); + assertEQ(t, 0); + t = test.f.acceptData(test.messages[1].get()); + assertEQ(t, 0); + t = test.f.acceptData(test.messages[2].get()); + assertNE(t, 0); +} + + +// test wrap-around case +static void test5() +{ + // fade of 4, buffer of 8 + Tester test(4, 8); + + // fill the buff to test with data, other one with zeros + for (int i = 0; i < 8; ++i) { + test.messages[0]->dataBuffer->set(i, 0.f); + test.messages[1]->dataBuffer->set(i, float(i)); + } + + // put zero 0 + NoiseMessage* t = test.f.acceptData(test.messages[0].get()); + + float x; + // clock 6 + for (int i = 0; i < 6; ++i) { + x = 5; + t = test.f.step(&x); + assertEQ(x, 0); // 0 + assertEQ(t, 0); + } + + // now start crossfade + + t = test.f.acceptData(test.messages[1].get()); + assertEQ(t, 0); + + // sample #6. start fade + t = test.f.step(&x); + assertEQ(x, 0); // 0 + assertEQ(t, 0); + + // sample #7. fade #2 + t = test.f.step(&x); + assertClose(x, .3333333f, .0001); // 0 + assertEQ(t, 0); + + // sample#8, fade #3 + t = test.f.step(&x); + assertClose(x, 1.3333333f, .0001); // 0 + assertEQ(t, 0); + + // sample#8, fade #4 (last), buff 0 (?) gets returned + t = test.f.step(&x); + assertClose(x, 3.f, .0001); // 0 + assertNE(t, 0); + + // done fading + t = test.f.step(&x); + assertClose(x, 4.f, .0001); // 0 + assertEQ(t, 0); + + t = test.f.step(&x); + assertClose(x, 5.f, .0001); // 0 + assertEQ(t, 0); + + t = test.f.step(&x); + assertClose(x, 6.f, .0001); // 0 + assertEQ(t, 0); + + t = test.f.step(&x); + assertClose(x, 7.f, .0001); // 0 + assertEQ(t, 0); + + t = test.f.step(&x); + assertClose(x, 0.f, .0001); // 0 + assertEQ(t, 0); +} + +// test makeup gain +static void test6(bool makeup) +{ + // fade of 5, buffer of 8 + Tester test(5, 8); + test.f.enableMakeupGain(makeup); + + // fill the buffers with 1 + for (int i = 0; i < 8; ++i) { + test.messages[0]->dataBuffer->set(i, 1.f); + test.messages[1]->dataBuffer->set(i, 1.f); + } + + // put messages + test.f.acceptData(test.messages[0].get()); + test.f.acceptData(test.messages[1].get()); + + float x; + for (int i = 0; i < 5; ++i) { + x = 5; + test.f.step(&x); + float expected = 1; + if (makeup) switch (i) { + case 0: + case 4: + expected = 1; + break; + case 2: + expected = std::sqrt(2.f); + break; + case 1: + case 3: + expected = (1.f + std::sqrt(2.f)) / 2.f; + break; + default: assert(false); + } + assertClose(x, expected, .0001); + } +} + +void testFFTCrossFader() +{ + assertEQ(FFTDataReal::_count, 0); + test0(); + test1(); + test2(); + test3(true); + test3(false); + test7(true); + test7(false); + test4(); + test5(); + test6(false); + test6(true); + + assertEQ(FFTDataReal::_count, 0); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testFinalLeaks.cpp b/plugins/community/repos/squinkylabs-plug1/test/testFinalLeaks.cpp new file mode 100644 index 00000000..07450bcc --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testFinalLeaks.cpp @@ -0,0 +1,19 @@ + +#include "LookupTable.h" +#include "ThreadSharedState.h" +#include "ThreadServer.h" +#include "FFTData.h" + +#include "asserts.h" +extern int _numBiquads; + +void testFinalLeaks() +{ + assertEQ(ThreadMessage::_dbgCount, 0); + assertEQ(FFTDataReal::_count, 0); + assertEQ(FFTDataCpx::_count, 0); + assertEQ(ThreadSharedState::_dbgCount, 0); + assertEQ(ThreadServer::_count, 0); + assertEQ(_numLookupParams, 0); + assertEQ(_numBiquads, 0) +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testFrequencyShifter.cpp b/plugins/community/repos/squinkylabs-plug1/test/testFrequencyShifter.cpp new file mode 100644 index 00000000..59ba9f0e --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testFrequencyShifter.cpp @@ -0,0 +1,65 @@ +#include +#include + +#include "FrequencyShifter.h" +#include "TestComposite.h" +#include "ExtremeTester.h" + +using Shifter = FrequencyShifter; + +// just test the can compile, etc.. +static void test0() +{ + Shifter fs; + fs.setSampleRate(44100); + fs.init(); + fs.step(); +} + +// test for signal +static void test1() +{ + Shifter fs; + + fs.setSampleRate(44100); + fs.init(); + + fs.inputs[Shifter::AUDIO_INPUT].value = 0; + fs.outputs[Shifter::SIN_OUTPUT].value = 0; + + // with no input, should have no output + for (int i = 0; i < 50; ++i) { + fs.step(); + assert(fs.outputs[Shifter::SIN_OUTPUT].value == 0); + } + + fs.inputs[Shifter::AUDIO_INPUT].value = 1; + // this should produce output + for (int i = 0; i < 50; ++i) { + fs.step(); + assert(!AudioMath::closeTo(fs.outputs[Shifter::SIN_OUTPUT].value, 0, .00001)); + assert(!AudioMath::closeTo(fs.outputs[Shifter::COS_OUTPUT].value, 0, .00001)); + } +} + +static void testExtreme() +{ + + using fp = std::pair; + std::vector< std::pair > paramLimits; + Shifter va; + va.setSampleRate(44100); + va.init(); + + paramLimits.resize(va.NUM_PARAMS); + paramLimits[va.PITCH_PARAM] = fp(-5.f, 5.f); + + ExtremeTester::test(va, paramLimits, true, "shifter"); +} + +void testFrequencyShifter() +{ + test0(); + test1(); + testExtreme(); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testGateTrigger.cpp b/plugins/community/repos/squinkylabs-plug1/test/testGateTrigger.cpp new file mode 100644 index 00000000..ef4e7604 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testGateTrigger.cpp @@ -0,0 +1,107 @@ + +#include +#include "GateTrigger.h" +#include "SchmidtTrigger.h" + +static void sc0() +{ + SchmidtTrigger sc(-10, 10); + bool b = sc.go(-20); + assert(!b); +} + + +static void sc1() +{ + SchmidtTrigger sc(-10, 10); + bool b = sc.go(20); + assert(b); +} + +static void sc2() +{ + SchmidtTrigger sc(-10, 10); + bool b = sc.go(20); + assert(b); + b = sc.go(9); + assert(b); + b = sc.go(-9); + assert(b); + b = sc.go(-11); + assert(!b); + + b = sc.go(-999); + assert(!b); + + b = sc.go(9); + assert(!b); + b = sc.go(11); + assert(b); +} + +// check defaults for schmidt +static void sc3() +{ + SchmidtTrigger sc; + bool b = sc.go(cGateHi + .1f); + assert(b); + b = sc.go(cGateLow + .1f); + assert(b); + b = sc.go(cGateLow - .1f); + assert(!b); + + b = sc.go(cGateHi - .1f); + assert(!b); + b = sc.go(cGateHi + .1f); + assert(b); +} + + +// check that threshold accessors are sane +void g_1() +{ + GateTrigger g; + assert(g.thlo() > 0); + assert(g.thhi() > g.thlo()); + + assert(g.thhi() < 10.f); + assert(g.thhi() > 1.f); +} + + +void testAfterReset(GateTrigger& g) +{ + g.go(10.f); // right after "reset", start with gate + assert(!g.gate()); + assert(!g.trigger()); + + g.go(0.f); + assert(!g.gate()); + assert(!g.trigger()); + + g.go(10.f); + assert(g.gate()); + assert(g.trigger()); +} + +void grst1() +{ + GateTrigger g; + testAfterReset(g); + + g.go(10.f); + g.reset(); + testAfterReset(g); +} + + + +void testGateTrigger() +{ + sc0(); + sc1(); + sc2(); + sc3(); + g_1(); + grst1(); +} diff --git a/plugins/community/repos/squinkylabs-plug1/test/testHilbert.cpp b/plugins/community/repos/squinkylabs-plug1/test/testHilbert.cpp new file mode 100644 index 00000000..5c71670c --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testHilbert.cpp @@ -0,0 +1,79 @@ + +#include "HilbertFilterDesigner.h" +#include "AudioMath.h" +#include "BiquadParams.h" +#include "BiquadFilter.h" +#include "BiquadState.h" + +#include +#include +#include + +// test that we can hook it up +template +static void test0() +{ + BiquadParams paramsSin; + BiquadParams paramsCos; + HilbertFilterDesigner::design(44100, paramsSin, paramsCos); +} + +// test that filter designer does something (more than just generate zero +template +static void test1() +{ + BiquadParams paramsSin; + BiquadParams paramsCos; + HilbertFilterDesigner::design(44100, paramsSin, paramsCos); + + const double delta = .00001; + for (int i = 0; i < 3; ++i) { + assert(!AudioMath::closeTo(0, (paramsSin.A1(i)), delta)); + assert(!AudioMath::closeTo(0, (paramsSin.A2(i)), delta)); + assert(!AudioMath::closeTo(0, (paramsSin.B0(i)), delta)); + assert(!AudioMath::closeTo(0, (paramsSin.B1(i)), delta)); + assert(!AudioMath::closeTo(0, (paramsSin.B2(i)), delta)); + + assert(!AudioMath::closeTo(0, (paramsCos.A1(i)), delta)); + assert(!AudioMath::closeTo(0, (paramsCos.A2(i)), delta)); + assert(!AudioMath::closeTo(0, (paramsCos.B0(i)), delta)); + assert(!AudioMath::closeTo(0, (paramsCos.B1(i)), delta)); + assert(!AudioMath::closeTo(0, (paramsCos.B2(i)), delta)); + } +} + +// see if it passes audio +template +static void test2() +{ + BiquadParams paramsSin; + BiquadParams paramsCos; + BiquadState stateSin; + BiquadState stateCos; + HilbertFilterDesigner::design(44100, paramsSin, paramsCos); + + // const T hilbertSin = BiquadFilter::run(input, hilbertFilterStateSin, hilbertFilterParamsSin); + T input = 1; + // T t = BiquadFilter::run(input, state, paramsSin); + for (int i = 0; i < 10; ++i) { + T ts = BiquadFilter::run(input, stateSin, paramsSin); + T tc = BiquadFilter::run(input, stateCos, paramsCos); + assert(!AudioMath::closeTo(ts, 0, .00001)); + assert(!AudioMath::closeTo(tc, 0, .00001)); + } +} + +template +static void test() +{ + test0(); + test1(); + test2(); + +} + +void testHilbert() +{ + test(); + test(); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testLookupTable.cpp b/plugins/community/repos/squinkylabs-plug1/test/testLookupTable.cpp new file mode 100644 index 00000000..cb26abb6 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testLookupTable.cpp @@ -0,0 +1,270 @@ + +#include +#include + +#include "asserts.h" +#include "AudioMath.h" +#include "LookupTable.h" +#include "LookupTableFactory.h" + +using namespace std; + +// test that we can call all the functions +template +static void test0() +{ + LookupTableParams p; + const int tableSize = 512; + + std::function f = [](double d) { + return 0; + }; + + LookupTable::init(p, tableSize, 0, 1, f); + LookupTable::lookup(p, 0); +} + +// test that simple lookup works +template +static void test1() +{ + LookupTableParams p; + const int tableSize = 512; + + std::function f = [](double d) { + return 100; + }; + + LookupTable::init(p, tableSize, 0, 1, f); + assert(LookupTable::lookup(p, 0) == 100); + assert(LookupTable::lookup(p, 1) == 100); + assert(LookupTable::lookup(p, T(.342)) == 100); +} + + +// test that sin works +template +static void test2() +{ + LookupTableParams p; + const int tableSize = 512; + + std::function f = [](double d) { + return std::sin(d); + }; + + LookupTable::init(p, tableSize, 0, 1, f); + + const T tolerance = T(0.000001); + for (double d = 0; d < 1; d += .0001) { + T output = LookupTable::lookup(p, T(d)); + + const bool t = AudioMath::closeTo(output, std::sin(d), tolerance); + if (!t) { + cout << "failing with expected=" << std::sin(d) << " actual=" << output << " delta=" << std::abs(output - std::sin(d)); + assert(false); + } + } +} + + +// test that sin works on domain 10..32 +template +static void test3() +{ + LookupTableParams p; + const int tableSize = 16; + + std::function f = [](double d) { + const double s = (d - 10) / 3; + return std::sin(s); + }; + + LookupTable::init(p, tableSize, 10, 13, f); + + const T tolerance = T(0.01); + for (double d = 10; d < 13; d += .0001) { + const T output = LookupTable::lookup(p, T(d)); + + const T expected = (T) std::sin((d - 10.0) / 3); + const bool t = AudioMath::closeTo(output, expected, tolerance); + if (!t) { + cout << "failing with d=" << d << " expected=" << expected << " actual=" << output << " delta=" << std::abs(output - std::sin(d)); + assert(false); + } + } +} + +// test that sin at extremes works +template +static void test4() +{ + LookupTableParams exponential; + + const T xMin = -5; + const T xMax = 5; + + std::function expFunc = AudioMath::makeFunc_Exp(-5, 5, 2, 2000); + LookupTable::init(exponential, 128, -5, 5, expFunc); + + // Had to loosen tolerance to pass with windows gcc. Is there a problem + // with precision, or is this expected with fast math? + const T tolerance = T(0.0003); + T outputLow = LookupTable::lookup(exponential, xMin); + + T outputHigh = LookupTable::lookup(exponential, xMax); + + bool t = AudioMath::closeTo(outputLow, 2, tolerance); + if (!t) { + cout << "failing l with expected=" << 2 << " actual=" << outputLow << " delta=" << std::abs(outputLow - 2) << std::endl; + assert(false); + } + t = AudioMath::closeTo(outputHigh, 2000, tolerance); + if (!t) { + cout << "failing h with expected=" << 2000 << " actual=" << outputHigh << " delta=" << std::abs(outputHigh - 2000) << std::endl; + assert(false); + } +} + +template +static void testDiscrete1() +{ + LookupTableParams lookup; + + T y[] = {0, 10}; + LookupTable::initDiscrete(lookup, 2, y); + + assertEQ(LookupTable::lookup(lookup, 0), 0); + assertEQ(LookupTable::lookup(lookup, .5), 5); + assertEQ(LookupTable::lookup(lookup, 1), 10); + + assertEQ(LookupTable::lookup(lookup, T(.1)), 1); + assertClose(LookupTable::lookup(lookup, T(.01)), T(.1), .00001); +} + +template +static void testDiscrete2() +{ + LookupTableParams lookup; + + T y[] = {100, 100.5, 2000, -10}; + LookupTable::initDiscrete(lookup, 4, y); + + assertEQ(LookupTable::lookup(lookup, 0), 100); + assertEQ(LookupTable::lookup(lookup, 1), 100.5); + assertEQ(LookupTable::lookup(lookup, 2), 2000); + assertEQ(LookupTable::lookup(lookup, 3), -10); + + assertEQ(LookupTable::lookup(lookup, 2.5), 1000 - 5); +} + +template +static void testExpSimpleLookup() +{ + LookupTableParams lookup; + LookupTableFactory::makeExp2(lookup); + + const double xMin = LookupTableFactory::expXMin(); + const double xMax = LookupTableFactory::expXMax(); + assert(5 > xMin); + assert(11 < xMax); + assertClose(LookupTable::lookup(lookup, 5), std::pow(2, 5), .01); + assertClose(LookupTable::lookup(lookup, 11), std::pow(2, 11), 2); // TODO: tighten +} + + + +// test that extreme inputs is clamped +template +static void testExpRange() +{ + LookupTableParams lookup; + LookupTable::makeExp2(lookup); + auto k1 = LookupTable::lookup(lookup, -1); + auto k2 = LookupTable::lookup(lookup, 11); + + assertClose(LookupTable::lookup(lookup, -1), LookupTable::lookup(lookup, 0), .01); + assertClose(LookupTable::lookup(lookup, 11), LookupTable::lookup(lookup, 10), .01); + assertClose(LookupTable::lookup(lookup, -100), LookupTable::lookup(lookup, 0), .01); + assertClose(LookupTable::lookup(lookup, 1100), LookupTable::lookup(lookup, 10), .01); +} + + +template +static void testExpTolerance(T centsTolerance) +{ + const T xMin = (T) LookupTableFactory::expXMin(); + const T xMax = (T) LookupTableFactory::expXMax(); + + LookupTableParams table; + LookupTableFactory::makeExp2(table); + for (T x = xMin; x <= xMax; x += T(.0001)) { + T y = LookupTable::lookup(table, x); // and back + double accurate = std::pow(2.0, x); + double errorCents = std::abs(1200.0 * std::log2(y / accurate)); + assertClose(errorCents, 0, centsTolerance); + } +} + +template +static void testBipolarSimpleLookup() +{ + LookupTableParams lookup; + LookupTableFactory::makeBipolarAudioTaper(lookup); + + assertClose(LookupTable::lookup(lookup, 0), 0, .01); + assertClose(LookupTable::lookup(lookup, 1), 1, .01); + assertClose(LookupTable::lookup(lookup, -1), -1, .01); +} + + +template +static void testBipolarTolerance() +{ + LookupTableParams lookup; + LookupTableFactory::makeBipolarAudioTaper(lookup); + const double toleratedError = 1 - AudioMath::gainFromDb(-.1);// let's go for one db. + assert(toleratedError > 0); + + auto refFuncPos = AudioMath::makeFunc_AudioTaper(LookupTableFactory::audioTaperKnee()); + auto refFuncNeg = [refFuncPos](double x) { + assert(x <= 0); + return -refFuncPos(-x); + }; + + for (double x = -1; x < 1; x += .001) { + + const T test = LookupTable::lookup(lookup, (T) x); + T ref = 1234; + if (x < 0) { + ref = (T) refFuncNeg(x); + } else { + ref = (T) refFuncPos(x); + } + assertClose(test, ref, toleratedError); + } +} + +template +static void test() +{ + test0(); + test1(); + test2(); + test3(); + test4(); + testDiscrete1(); + testDiscrete2(); + testExpSimpleLookup(); + testExpTolerance(100); // 1 semitone + testExpTolerance(10); + testExpTolerance(1); + testBipolarSimpleLookup(); + testBipolarTolerance(); +} + +void testLookupTable() +{ + test(); + test(); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testObjectCache.cpp b/plugins/community/repos/squinkylabs-plug1/test/testObjectCache.cpp new file mode 100644 index 00000000..c3d2a133 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testObjectCache.cpp @@ -0,0 +1,187 @@ + +#include "asserts.h" +#include "ObjectCache.h" + +extern int _numLookupParams; + +template +static void testBipolar() +{ + assertEQ(_numLookupParams, 0); + + auto test = ObjectCache::getBipolarAudioTaper(); + assertEQ(_numLookupParams, 1); + auto test2 = ObjectCache::getBipolarAudioTaper(); + assertEQ(_numLookupParams, 1); + test.reset(); + assertEQ(_numLookupParams, 1); + + test2.reset(); + assertEQ(_numLookupParams, 0); + + { + // simple test that bipolar audio scalers use cached lookups, and they work + AudioMath::ScaleFun f = AudioMath::makeBipolarAudioScaler(3, 4); + assertEQ(f(0, -5, 0), 3.); + assertEQ(_numLookupParams, 1); + } + assertEQ(_numLookupParams, 0); + + // make again + test = ObjectCache::getBipolarAudioTaper(); + assertEQ(_numLookupParams, 1); +} + +template +static void testSin() +{ + assertEQ(_numLookupParams, 0); + + auto test = ObjectCache::getSinLookup(); + assertEQ(_numLookupParams, 1); + auto test2 = ObjectCache::getSinLookup(); + assertEQ(_numLookupParams, 1); + test.reset(); + assertEQ(_numLookupParams, 1); + + test2.reset(); + assertEQ(_numLookupParams, 0); + + { + // // simple test that bipolar audio scalers use cached lookups, and they work + AudioMath::ScaleFun f = AudioMath::makeBipolarAudioScaler(3, 4); + assertEQ(f(0, -5, 0), 3.); + assertEQ(_numLookupParams, 1); + } + assertEQ(_numLookupParams, 0); + + // make again + test = ObjectCache::getSinLookup(); + assertEQ(_numLookupParams, 1); +} + + +template +static void testExp2() +{ + assertEQ(_numLookupParams, 0); + + auto test = ObjectCache::getExp2(); + assertEQ(_numLookupParams, 1); + auto test2 = ObjectCache::getExp2(); + assertEQ(_numLookupParams, 1); + test.reset(); + assertEQ(_numLookupParams, 1); + + test2.reset(); + assertEQ(_numLookupParams, 0); + + { + auto test3 = ObjectCache::getExp2(); + const double x = LookupTable::lookup(*test3, (T)3.2); + const double y = std::pow(2, 3.2); + assertClose(x, y, .001); + assertEQ(_numLookupParams, 1); + } + assertEQ(_numLookupParams, 0); + + // make again + test = ObjectCache::getExp2(); + assertEQ(_numLookupParams, 1); +} + +template +static void testExp2b() +{ + { + // make sure exp2 is really 1V/octave + auto ex2 = ObjectCache::getExp2(); + const T a = LookupTable::lookup(*ex2, 5); + const T b = LookupTable::lookup(*ex2, 6); + assertClose(b / a, 2, .001); + } +} + + +template +static void testDb2Gain() +{ + assertEQ(_numLookupParams, 0); + + auto test = ObjectCache::getDb2Gain(); + assertEQ(_numLookupParams, 1); + auto test2 = ObjectCache::getDb2Gain(); + assertEQ(_numLookupParams, 1); + test.reset(); + assertEQ(_numLookupParams, 1); + + test2.reset(); + assertEQ(_numLookupParams, 0); + + { + auto test3 = ObjectCache::getDb2Gain(); + const double x = LookupTable::lookup(*test3, (T) -12); + const double y = AudioMath::gainFromDb(-12); + + + assertClose(x, y, .1); + assertEQ(_numLookupParams, 1); + } + assertEQ(_numLookupParams, 0); + + // make again + test = ObjectCache::getDb2Gain(); + assertEQ(_numLookupParams, 1); +} + +template +static void testDb2Gain2() +{ + assertEQ(_numLookupParams, 0); + + auto test = ObjectCache::getDb2Gain(); + + // .1 db from -80 to +20 + for (double db = -80; db < 20; db += .1) { + const double x = LookupTable::lookup(*test, (T) db); + const double y = AudioMath::gainFromDb(db); + + assertClose(x, y, .2); + } +} + + +template +static void testTanh5() +{ + auto test = ObjectCache::getTanh5(); + auto test2 = ObjectCache::getTanh5(); + assertEQ(_numLookupParams, 1); + + for (double x = -5; x <= 5; x += .1) { + const double y = LookupTable::lookup(*test, (T) x); + const double y0 = std::tanh(x); + + assertClose(y, y0, .01); + } +} + +template +static void test() +{ + testBipolar(); + testSin(); + testExp2(); + testExp2b(); + testDb2Gain(); + testDb2Gain2(); + testTanh5(); +} + +void testObjectCache() +{ + assertEQ(_numLookupParams, 0); + test(); + test(); + assertEQ(_numLookupParams, 0); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testRingBuffer.cpp b/plugins/community/repos/squinkylabs-plug1/test/testRingBuffer.cpp new file mode 100644 index 00000000..faa031c4 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testRingBuffer.cpp @@ -0,0 +1,185 @@ + + +#include "RingBuffer.h" +#include "asserts.h" + + +static void testConstruct() +{ + RingBuffer rb; + assert(rb.empty()); + assert(!rb.full()); + + + RingBuffer rb2; +} + +static void testSimpleAccess() +{ + RingBuffer rb; + rb.push(55); + assert(!rb.empty()); + assert(!rb.full()); + + int x = rb.pop(); + assertEQ(x, 55); + + assert(rb.empty()); + assert(!rb.full()); +} + +static void testMultiAccess() +{ + RingBuffer rb; + rb.push(1234); + rb.push(5678); + assert(!rb.empty()); + assert(!rb.full()); + + int x = rb.pop(); + assertEQ(x, 1234); + + assert(!rb.empty()); + assert(!rb.full()); + + x = rb.pop(); + assertEQ(x, 5678); + + assert(rb.empty()); + assert(!rb.full()); +} + +static void testWrap() +{ + RingBuffer rb; + rb.push(1234); + rb.push(5678); + rb.pop(); + rb.pop(); + rb.push(1); + rb.push(2); + rb.push(3); + + assertEQ(rb.pop(), 1); + assertEQ(rb.pop(), 2); + assertEQ(rb.pop(), 3); + + assert(rb.empty()); +} + +static void testFull() +{ + RingBuffer rb; + rb.push(1234); + rb.push(5678); + rb.pop(); + rb.pop(); + rb.push(1); + rb.push(2); + rb.push(3); + rb.push(4); + assert(rb.full()); + assert(!rb.empty()); + + assertEQ(rb.pop(), 1); + assertEQ(rb.pop(), 2); + assertEQ(rb.pop(), 3); + assertEQ(rb.pop(), 4); + + assert(rb.empty()); + assert(!rb.full()); +} + +static void testOne() +{ + const char * p = "foo"; + RingBuffer rb; + rb.push(p); + assert(!rb.empty()); + assert(rb.full()); + + assertEQ(rb.pop(), p); + assert(rb.empty()); + assert(!rb.full()); +} + + +void testRingBuffer() +{ + testConstruct(); + testSimpleAccess(); + testMultiAccess(); + testWrap(); + testFull(); + testOne(); +} + +/***********************************************************************************************/ +#include "ManagedPool.h" + +static void testMP0() +{ + ManagedPool mp; + assert(mp.full()); + assert(!mp.empty()); +} + +static void testMP_access() +{ + ManagedPool mp; + + int* p = mp.pop(); + *p = 77; + mp.push(p); + assert(mp.full()); + + p = mp.pop(); + assertEQ(*p, 77); +} + +static int count = 0; +class SimpleObj +{ +public: + SimpleObj() + { + ++count; + } + ~SimpleObj() + { + --count; + } +}; + +static void testMP_mem() +{ + assertEQ(count, 0); + { + assertEQ(count, 0); + ManagedPool mp; + assertEQ(count, 4); + } + assertEQ(count, 0); +} + + +static void testMP_mem2() +{ + assertEQ(count, 0); + { + assertEQ(count, 0); + ManagedPool mp; + assertEQ(count, 4); + mp.pop(); + mp.pop(); // make sure the ones we remove still get destroyed + } + assertEQ(count, 0); +} + +void testManagedPool() +{ + testMP0(); + testMP_access(); + testMP_mem(); + testMP_mem2(); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testSaw.cpp b/plugins/community/repos/squinkylabs-plug1/test/testSaw.cpp new file mode 100644 index 00000000..3715db5d --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testSaw.cpp @@ -0,0 +1,281 @@ +#include +#include + +#include "MultiModOsc.h" +#include "AudioMath.h" +#include "SawOscillator.h" +#include "TestSignal.h" + +using namespace std; + +// do objects exist? +template +static void testSaw1() +{ + SawOscillatorParams params; + SawOscillator::setFrequency(params, (T(.1))); + SawOscillatorState state; + SawOscillator::runSaw(state, params); + + using osc = MultiModOsc; + typename osc::State mstate; + typename osc::Params mparams; + T output[3]; + osc::run(output, mstate, mparams); +} + +/** + * Does parameter calculation do anything? + */ +template +static void testSaw2() +{ + SawOscillatorParams params; + assert(params.phaseIncrement == 0); + SawOscillator::setFrequency(params, (T(.1))); + assert(params.phaseIncrement > 0); +} + +/** + * Does something come out? + */ +template +static void testSaw3() +{ + SawOscillatorParams params; + SawOscillator::setFrequency(params, (T(.2))); + SawOscillatorState state; + SawOscillator::runSaw(state, params); + const T out = SawOscillator::runSaw(state, params); + assert(out > 0); + assert(out < 1); +} + +/** +* Does something come out? +*/ +template +static void testTri3() +{ + SawOscillatorParams params; + SawOscillator::setFrequency(params, (T(.2))); + SawOscillatorState state; + SawOscillator::runSaw(state, params); + const T out = SawOscillator::runTri(state, params); + assert(out > 0); + assert(out < 1); +} + +/** +* Does something come out? +*/ +template +static void testMulti3() +{ + using osc = MultiModOsc; + typename osc::State state; + typename osc::Params params; + T output[3] = {0, 0, 0}; + T output2[3] = {0, 0, 0}; + + osc::run(output, state, params); + osc::run(output, state, params); + osc::run(output2, state, params); + for (int i = 0; i < 3; ++i) { + assert(output[i] != 0); + assert(output2[i] != 0); + assert(output[i] != output2[i]); + + if (i > 0) { + assert(output[i] != output[i - 1]); + assert(output2[i] != output2[i - 1]); + } + } +} + +/** + * Does it look like a triangle? + */ + +template +static void testTri4() +{ + const T freq = T(.1); // was .01 + const T delta = 2 * freq; + SawOscillatorParams params; + SawOscillator::setFrequency(params, (T(.01))); + SawOscillatorState state; + + T last = -freq; + bool increasing = true; + for (int i = 0; i < 1000; ++i) { + const T output = SawOscillator::runTri(state, params); + + assert(output >= -1); + assert(output <= 1); + if (increasing) { + if (output > last) { + // still increasing + } else { + // started decreasing + + assert(AudioMath::closeTo(output, 1, delta)); + increasing = false; + } + } else { + if (output < last) { + // still decreasing + } else { + // started increasing + assert(AudioMath::closeTo(output, -1, delta)); + increasing = true; + } + } + + last = output; + } +} + +/** +* Does it look like a saw? +*/ +template +static void testSaw4() +{ + const T freq = T(.01); + const T delta = freq / 1000; + SawOscillatorParams params; + SawOscillator::setFrequency(params, (T(.01))); + SawOscillatorState state; + + T last = 0; + for (int i = 0; i < 1000; ++i) { + const T output = SawOscillator::runSaw(state, params); + + assert(output >= 0); + assert(output < 1); + + if (output < last) { + assert(last > .99); + assert(output < .01); + } else { + assert(output < (last + freq + delta)); + } + + last = output; + } +} + +/** +* Is the quadrature really 90 out of phase? +*/ +template +static void testSaw5() +{ + SawOscillatorParams params; + SawOscillator::setFrequency(params, (T(.01))); + SawOscillatorState state; + + T output; + T quadratureOutput; + for (int i = 0; i < 1000; ++i) { + SawOscillator::runQuadrature(output, quadratureOutput, state, params); + + // normalize output (unwrap) + if (quadratureOutput < output) { + quadratureOutput += 1; + } + assert(quadratureOutput = (output + T(.25))); + } +} + + +/** +* Does it look like a negative saw? +*/ +template +static void testSaw6() +{ + const T freq = T(-.01); + const T delta = freq / 1000; + SawOscillatorParams params; + SawOscillator::setFrequency(params, freq); + SawOscillatorState state; + + T last = 0; + for (int i = 0; i < 1000; ++i) { + const T output = SawOscillator::runSaw(state, params); + + assert(output >= 0); + assert(output < 1); + + if (output > last) { + // wrap case - did we more or less wrap? + assert(last < .01); + assert(output > .98); + } else { + // no-wrap - are we decreasing + assert(output > (last + freq + delta)); + } + last = output; + } +} + +/** + * IS the RMS for triangle as expected? + */ +template +static void testTri7() +{ + const int div = 1024; + const T freq = T(1.0 / T(div)); + SawOscillatorParams params; + SawOscillator::setFrequency(params, freq); + SawOscillatorState state; + double amplitude = TestSignal::measureOutput(div, [&state, ¶ms]() { + return SawOscillator::runTri(state, params); + }); + + // RMS of tri wave is 1 / cube root 3 + assert(AudioMath::closeTo(amplitude, 0.57735, .0001)); +} + +template +static void testSaw7() +{ + const int div = 1024; + const T freq = T(1.0 / T(div)); + SawOscillatorParams params; + SawOscillator::setFrequency(params, freq); + SawOscillatorState state; + double amplitude = TestSignal::measureOutput(div * 16, [&state, ¶ms]() { + // normalize to 1V pp + return 2 * SawOscillator::runSaw(state, params) - 1; + }); + + + // RMS of saw wave is 1 / cube root 3 + assert(AudioMath::closeTo(amplitude, 0.57735, .0001)); +} + +template +static void testSawT() +{ + testSaw1(); + testSaw2(); + testSaw3(); + testTri3(); + testMulti3(); + testSaw4(); + testTri4(); + testSaw5(); + testSaw6(); + testTri7(); + testSaw7(); +} + +void testSaw() +{ + testSawT(); + testSawT(); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testSinOscillator.cpp b/plugins/community/repos/squinkylabs-plug1/test/testSinOscillator.cpp new file mode 100644 index 00000000..a34343ca --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testSinOscillator.cpp @@ -0,0 +1,139 @@ +#include +#include + +#include "asserts.h" +#include "SinOscillator.h" +using namespace std; + +// test that it can be hooked up +template +static void test1() +{ + SinOscillatorParams p; + SinOscillatorState s; + SinOscillator::setFrequency(p, T(.1)); + T x = SinOscillator::run(s, p); + (void) x; +} + +// test that it makes output +template +static void test2() +{ + SinOscillatorParams p; + SinOscillatorState s; + SinOscillator::setFrequency(p, T(.1)); + T x = SinOscillator::run(s, p); + assert(x == 0); + x = SinOscillator::run(s, p); + assert(x > 0); +} + +// test that sin lookup is correct +template +static void test3() +{ + SinOscillatorParams params; + SinOscillatorState s; + SinOscillator::setFrequency(params, T(.1)); + + auto& lookup = params.lookupParams; + + const double delta = .00001; + + // sin(0) == 0; + T y = LookupTable::lookup(*lookup, 0); + assert(AudioMath::closeTo(y, 0, delta)); + + // sin(2pi) == 0 + y = LookupTable::lookup(*lookup, 1); + assert(AudioMath::closeTo(y, 0, delta)); + + // sin(pi/2) == 1 + y = LookupTable::lookup(*lookup, .25); + assert(AudioMath::closeTo(y, 1, delta)); + + // sin(pi) == 0 + y = LookupTable::lookup(*lookup, .5); + assert(AudioMath::closeTo(y, 0, delta)); +} + +// test that output is correct freq +template +static void test4() +{ + const int clocksPerPeriod = 64; + SinOscillatorParams params; + SinOscillatorState state; + SinOscillator::setFrequency(params, T(1.0 / clocksPerPeriod)); + + const double delta = .00001; + T output, quadrature; + for (int i = 0; i <= clocksPerPeriod; ++i) { + SinOscillator::runQuadrature(output, quadrature, state, params); + if (i == 0) { + // sin+cos(0) = 0 + 1 + assert(AudioMath::closeTo(output, 0, delta)); + assert(AudioMath::closeTo(quadrature, 1, delta)); + } + if (i == 64) { + // sin+cos(0) = 0 + 1 + assert(AudioMath::closeTo(output, 0, delta)); + assert(AudioMath::closeTo(quadrature, 1, delta)); + } + if (i == 16) { + // sin+cos(pi/2) = 1 0 + assert(AudioMath::closeTo(output, 1, delta)); + assert(AudioMath::closeTo(quadrature, 0, delta)); + } + if (i == 32) { + // sin+cos(pi) = 0 -1 + assert(AudioMath::closeTo(output, 0, delta)); + assert(AudioMath::closeTo(quadrature, -1, delta)); + } + if (i == 48) { + // sin+cos(3pi/2) = -1 0 + assert(AudioMath::closeTo(output, -1, delta)); + assert(AudioMath::closeTo(quadrature, 0, delta)); + } + } +} + +template +static void testDistortion() +{ + SinOscillatorParams params; + SinOscillatorState s; + SinOscillator::setFrequency(params, T(.0423781)); + auto& lookup = params.lookupParams; + + double err = 0; + for (double d = 0; d < 1; d += .00123) { + double x = LookupTable::lookup(*lookup, (T) d); + double y = sin(d * AudioMath::Pi * 2); + // printf("d=%f sin=%f look=%f\n", d, y, x); + assertClose(x, y, .01); + + const double e = std::abs(x - y); + err = std::max(err, e); + } + double errDb = AudioMath::db(err); + // printf("THD = %f\n", errDb); + assertLT(errDb, -80); +} + +template +static void test() +{ + test1(); + test2(); + test3(); + test4(); + testDistortion(); +} + +void testSinOscillator() +{ + test(); + test(); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testStateVariable.cpp b/plugins/community/repos/squinkylabs-plug1/test/testStateVariable.cpp new file mode 100644 index 00000000..980ee5c6 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testStateVariable.cpp @@ -0,0 +1,125 @@ +#include + +#include "asserts.h" +#include "StateVariableFilter.h" +#include "TestSignal.h" + +/** + * Simple test - can we get output that looks kind of low pass + */ +template +static void test1() +{ + StateVariableFilterParams params; + StateVariableFilterState state; + params.setMode(StateVariableFilterParams::Mode::LowPass); + params.setFreq(T(.05)); // TODO: make an even fraction + params.setQ(T(.7)); + + T lastValue = -1; + for (int i = 0; i < 5; ++i) { + T output = StateVariableFilter::run(1, state, params); + assert(output > lastValue); + lastValue = output; + } +} + + +/** + * Measure freq response at some points + */ +template +static void testLowpass() +{ + const T fc = T(.001); + const T q = T(1.0 / std::sqrt(2)); // butterworth + StateVariableFilterParams params; + StateVariableFilterState state; + params.setMode(StateVariableFilterParams::Mode::LowPass); + params.setFreq(fc); // TODO: make an even fraction + params.setQ(q); + + double g = TestSignal::measureGain(fc / 4, [&state, ¶ms](T input) { + return StateVariableFilter::run(input, state, params); + }); + g = AudioMath::db(g); + assert(AudioMath::closeTo(g, 0, .05)); + + g = TestSignal::measureGain(fc, [&state, ¶ms](T input) { + return StateVariableFilter::run(input, state, params); + }); + g = AudioMath::db(g); + assert(AudioMath::closeTo(g, -3, .05)); + + double g2 = TestSignal::measureGain(fc * 4, [&state, ¶ms](T input) { + return StateVariableFilter::run(input, state, params); + }); + g2 = AudioMath::db(g2); + + double g3 = TestSignal::measureGain(fc * 8, [&state, ¶ms](T input) { + return StateVariableFilter::run(input, state, params); + }); + g3 = AudioMath::db(g3); + assert(AudioMath::closeTo(g2 - g3, 12, 2)); +} + +/** + * Verify that passband gain tracks Q + */ +static void testBandpass() +{ + const float fc = .01f; + const float q = (1.0f / float(std::sqrt(2))); // butterworth + StateVariableFilterParams params; + StateVariableFilterState state; + params.setMode(StateVariableFilterParams::Mode::BandPass); + params.setFreq(fc); // TODO: make an even fraction + params.setQ(q); + + double g0 = TestSignal::measureGain(fc, [&state, ¶ms](float input) { + return StateVariableFilter::run(input, state, params); + }); + g0 = AudioMath::db(g0); + + for (int i = 2; i < 100; i *= 2) { + const float q = float(i); + params.setQ(q); + + double g = TestSignal::measureGain(fc, [&state, ¶ms](float input) { + return StateVariableFilter::run(input, state, params); + }); + g = AudioMath::db(g); + // printf("q = %f, gain db = %f qdb=%f\n", q, g, AudioMath::db(q)); + + assertClose(g, AudioMath::db(q), .5); + } +} + +template +static void testSetBandwidth() +{ + StateVariableFilterParams params; + params.setNormalizedBandwidth(T(.1)); + assertEQ(params.getNormalizedBandwidth(), T(.1)); + + params.setNormalizedBandwidth(T(.5)); + assertEQ(params.getNormalizedBandwidth(), T(.5)); + + params.setQ(10); + assertEQ(params.getNormalizedBandwidth(), T(.1)) +} + +template +static void test() +{ + test1(); + testLowpass(); + testSetBandwidth(); +} + +void testStateVariable() +{ + test(); + test(); + testBandpass(); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testTestSignal.cpp b/plugins/community/repos/squinkylabs-plug1/test/testTestSignal.cpp new file mode 100644 index 00000000..2da8fa46 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testTestSignal.cpp @@ -0,0 +1,120 @@ +#include +#include + +#include "AudioMath.h" +#include "TestSignal.h" + +/** + * Test that sin generates a signal +-1 + */ +template +static void test1() +{ + const int size = 20000; + T buffer[size]; + TestSignal::generateSin(buffer, size, T(.01)); + + T min = 1, max = -1; + for (int i = 0; i < size; ++i) { + const T x = buffer[i]; + assert(x <= 1); + assert(x >= -1); + if (min > x) { + min = x; + } + if (max < x) { + max = x; + } + } + + const T delta = T(.0000001); + assert(AudioMath::closeTo(min, T(-1), delta)); + assert(AudioMath::closeTo(max, T(1), delta)); +} + +/** + * Test the period of sin is correct + */ +template +static void test2() +{ + const int size = 20000; + T buffer[size]; + TestSignal::generateSin(buffer, size, T(.001)); + + const T delta = T(.0000001); + assert(AudioMath::closeTo(buffer[0], 0, delta)); + + T last = -1; + int period = 0; + for (int i = 0; i < size; ++i) { + const T x = buffer[i]; + if (x <= last) { + period = (i - 1) * 4; + break; + } + last = x; + } + assert(period == 1000); +} + +template +static void test3() +{ + const int size = 20000; + T buffer[size]; + + buffer[0] = 1; + assert(TestSignal::getRMS(buffer, 1) == 1); + + buffer[0] = -1; + assert(TestSignal::getRMS(buffer, 1) == 1); + + for (int i = 0; i < 5; ++i) { + buffer[i] = 1; + } + assert(TestSignal::getRMS(buffer, 1) == 1); + + for (int i = 0; i < 5; ++i) { + buffer[i] = -2; + } + assert(TestSignal::getRMS(buffer, 1) == 2); + + TestSignal::generateSin(buffer, size, T(.001)); + const double amplitude = TestSignal::getRMS(buffer, size); + assert(AudioMath::closeTo(amplitude, std::sqrt(2.0) / 2, .00001)); +} + +template +static void testUnityGain() +{ + const T gl = TestSignal::measureGain(T(.001), [](T input) { + return input; + }); + + assert(gl == 1); +} + +template +static void test4() +{ + double amp = TestSignal::measureOutput(5, []() { + return T(-3); + }); + assert(amp == 3); +} +template +static void test() +{ + test1(); + test2(); + test3(); + testUnityGain(); + test4(); +} + +void testTestSignal() +{ + test(); + test(); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testThread.cpp b/plugins/community/repos/squinkylabs-plug1/test/testThread.cpp new file mode 100644 index 00000000..2f4737db --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testThread.cpp @@ -0,0 +1,234 @@ + +#include "asserts.h" +#include "ThreadSharedState.h" +#include "ThreadServer.h" +#include "ThreadClient.h" +#include "ThreadPriority.h" + +#include +#include +#include + + + +// test that we can build and tear down. +static void test0() +{ + assertEQ(ThreadSharedState::_dbgCount, 0); + { + std::shared_ptr noise = std::make_shared(); + std::unique_ptr server(new ThreadServer(noise)); + std::unique_ptr client(new ThreadClient(noise, std::move(server))); + } + assertEQ(ThreadSharedState::_dbgCount, 0); +} + +static void test1() +{ + for (int i = 0; i < 200; ++i) + test0(); +} + +/**************************************************************************/ + +// client will send to server +class Test1Message : public ThreadMessage +{ +public: + Test1Message() : ThreadMessage(Type::TEST1) + { + } + int payload = 0; +}; + +// client will send to server +class Test2Message : public ThreadMessage +{ +public: + Test2Message() : ThreadMessage(Type::TEST2) + { + } +}; + +class TestServer : public ThreadServer +{ +public: + TestServer(std::shared_ptr state) : ThreadServer(state) + { + } + void handleMessage(ThreadMessage* msg) override + { + switch (msg->type) { + case ThreadMessage::Type::TEST1: + { + Test1Message * tstMsg = static_cast(msg); + assertEQ(tstMsg->payload, nextExpectedPayload); + ++nextExpectedPayload; + tstMsg->payload += 1000; + sendMessageToClient(tstMsg); // send back the modified one + } + break; + default: + assert(false); + } + } + int nextExpectedPayload = 100; +}; + +static void test2() +{ + // Set up all the objects + std::unique_ptr msg(new Test1Message()); + std::shared_ptr state = std::make_shared(); + std::unique_ptr server(new TestServer(state)); + std::unique_ptr client(new ThreadClient(state, std::move(server))); + + for (int count = 0; count < 50; ++count) { + msg->payload = 100 + count; + const int expectedPayload = msg->payload + 1000; + for (bool done = false; !done; ) { + bool b = client->sendMessage(msg.get()); + if (b) { + done = true; + } + } + + for (bool done = false; !done; ) { + auto rxmsg = client->getMessage(); + if (rxmsg) { + done = true; + assert(rxmsg->type == ThreadMessage::Type::TEST1); + Test1Message* tmsg = reinterpret_cast(rxmsg); + assertEQ(tmsg->payload, expectedPayload); + } + } + } +} + +// not a real test +static void test3() +{ + bool b = ThreadPriority::boostNormal(); + bool b2 = ThreadPriority::boostRealtime(); + printf("\nnormal boost: %d\n", b); + printf("realtime boost: %d\n", b2); + ThreadPriority::restore(); +} + +static std::atomic stopNow; +static std::atomic count; +static double xxx, yyy; +static std::atomic slow; +static std::atomic fast; + +//thread func +static void t4(bool iAmIt,int boost) +{ + // printf("t4 called with %d\n", iAmIt); + + if (iAmIt) { + switch (boost) { + case 0: + printf("no boost\n"); + break; + case 1: + printf("boosting\n"); + ThreadPriority::boostNormal(); + break; + case 2: + printf("boosting RT\n"); + ThreadPriority::boostRealtime(); + break; + default: + assert(false); + } + + fflush(stdout); + + } + while (!stopNow) { + for (int i = 0; i < 100000; ++i) { + yyy = yyy + (double) rand(); + } + + if (iAmIt) { + ++fast; + } else { + ++slow; + } + } +} + +// runs all the test treads, returns ratio of work done in the default theads +// and work done in test thread. +static double test4sub(int boost) +{ + stopNow = false; + count = 0; + xxx = 0; + yyy = 0; + slow = 0; + fast = 0; + int numSlow = 0; + std::vector< std::shared_ptr> threads; + + threads.push_back(std::make_shared(t4, true, boost)); + for (int i = 0; i < 9; ++i) { + threads.push_back(std::make_shared(t4, false, 0)); + ++numSlow; + } + + printf("started all\n"); + xxx = 0; + yyy = 0; + + std::this_thread::sleep_for(std::chrono::seconds(20)); + stopNow = true; + + for (auto thread : threads) { + thread->join(); + } + + ThreadPriority::restore(); + const double ret = (double) slow / (double) fast; + printf("slow/fast was %f (%d) ratio=%d\n", ret, (int) slow, numSlow); + return ret; +} + +static void test4() +{ + printf("testing thread priorities, part1. will take a while\n"); fflush(stdout); + const double ref = test4sub(0); + printf("testing thread priorities, part2. will take a while\n"); fflush(stdout); + const double boosted = test4sub(1); + printf("testing thread priorities, part3. will take a while\n"); fflush(stdout); + const double boostedRT = test4sub(2); + printf("ref = %f, boosted = %f rt=%f\n", ref, boosted, boostedRT); fflush(stdout); +} + +#ifdef ARCH_WIN +static void test5() +{ + ThreadPriority::boostRealtimeWindows(); +} +#endif +/*****************************************************************/ + +void testThread(bool extended) +{ + + assertEQ(ThreadSharedState::_dbgCount, 0); + assertEQ(ThreadMessage::_dbgCount, 0); + test0(); + test1(); + test2(); + test3(); + if (extended) { + test4(); + } +#ifdef ARCH_WIN + test5(); +#endif + assertEQ(ThreadSharedState::_dbgCount, 0); + assertEQ(ThreadMessage::_dbgCount, 0); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testTremolo.cpp b/plugins/community/repos/squinkylabs-plug1/test/testTremolo.cpp new file mode 100644 index 00000000..397ed6fd --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testTremolo.cpp @@ -0,0 +1,50 @@ + +#include "asserts.h" +#include "Tremolo.h" +#include "TestComposite.h" + +using Trem = Tremolo; + +static void test0() +{ + Trem t; + t.setSampleRate(44100); + t.init(); + assertEQ(t.outputs[Trem::SAW_OUTPUT].value, 0); + assertEQ(t.outputs[Trem::AUDIO_OUTPUT].value, 0); + t.step(); +} + +static void test1Sub(float skew) +{ + Trem t; + t.setSampleRate(44100); + t.init(); + + t.params[Trem::CLOCK_MULT_PARAM].value = 4; // 4 is free run for Trem + t.params[Trem::LFO_RATE_PARAM].value = 5; // max speed + t.params[Trem::LFO_SKEW_PARAM].value = skew; + float max = -100; + float min = 100; + for (int i = 0; i < 5000; ++i) { + t.step(); + const float x = t.outputs[Trem::SAW_OUTPUT].value; + max = std::max(x, max); + min = std::min(x, min); + } + assertClose(max, .5f, .001); + assertClose(min, -.5f, .001); +} + +static void test1() +{ + test1Sub(0); + test1Sub(5); + test1Sub(-5); +} + +void testTremolo() +{ + test0(); + test1(); +} \ No newline at end of file diff --git a/plugins/community/repos/squinkylabs-plug1/test/testVocalAnimator.cpp b/plugins/community/repos/squinkylabs-plug1/test/testVocalAnimator.cpp new file mode 100644 index 00000000..a8b12d91 --- /dev/null +++ b/plugins/community/repos/squinkylabs-plug1/test/testVocalAnimator.cpp @@ -0,0 +1,373 @@ +#include "asserts.h" + + +#include "ExtremeTester.h" +#include "VocalAnimator.h" +#include "TestComposite.h" +#include "VocalFilter.h" +#include "FormantTables2.h" + +using Animator = VocalAnimator; + +/** + * Verify no output with no input. + */ +static void test0() +{ + Animator anim; + anim.setSampleRate(44100); + anim.init(); + + anim.outputs[Animator::AUDIO_OUTPUT].value = 0; + anim.step(); // prime it + + // with no input, should have no output + for (int i = 0; i < 50; ++i) { + anim.step(); + assert(anim.outputs[Animator::AUDIO_OUTPUT].value == 0); + } +} + +/** + * Verify output with input. + */ +static void test1() +{ + Animator anim; + anim.setSampleRate(44100); + anim.init(); + + anim.outputs[Animator::AUDIO_OUTPUT].value = 0; + anim.inputs[Animator::AUDIO_INPUT].value = 1; + anim.step(); // prime it + // with input, should have output + for (int i = 0; i < 50; ++i) { + anim.step(); + assert(anim.outputs[Animator::AUDIO_OUTPUT].value != 0); + } +} + +/** + * Verify filter settings with no mod. + */ +static void test2() +{ + Animator anim; + anim.setSampleRate(44100); + anim.init(); + for (int i = 0; i < 4; ++i) { + float freq = anim.normalizedFilterFreq[i] * 44100; + assertEQ(freq, anim.nominalFilterCenterHz[i]); + } +} + +/** +* Verify filter settings respond to Fc. +*/ +static void test3() +{ + Animator anim; + anim.setSampleRate(44100); + anim.init(); + anim.params[anim.FILTER_FC_PARAM].value = 0; + anim.step(); + + for (int i = 0; i < 4; ++i) { + // assert(anim.filterFrequency[i] == anim.nominalFilterCenter[i]); + float freq = anim.normalizedFilterFreq[i] * 44100; + assertClose(freq, anim.nominalFilterCenterHz[i], 1); + } + + anim.params[anim.FILTER_FC_PARAM].value = 1; + anim.step(); + + + // assert that when we shift up, the expected values shift up + for (int i = 0; i < 4; ++i) { + float freq = anim.normalizedFilterFreq[i] * 44100; + //printf("i=%d, freq=%f, nominal=%f\n", i, freq, anim.nominalFilterCenterHz[i]); + if (i == 3) { + assertClose(freq, anim.nominalFilterCenterHz[i], 1); + } else + assert(freq > anim.nominalFilterCenterHz[i]); + } + +#if 0 + anim.params[anim.FILTER_FC_PARAM].value = -1; + anim.step(); + for (int i = 0; i < 4; ++i) { + if (i == 3) + assert(anim.filterFrequency[i] == anim.nominalFilterCenter[i]); + else + assert(anim.filterFrequency[i] < anim.nominalFilterCenter[i]); + } +#endif +} + +static void testScalers() +{ + Animator anim; + anim.setSampleRate(44100); + anim.init(); + + // cv/knob, trim + + + // cases with no CV + assertClose(.5, anim.scale0_1(0, 0, 1), .001); // knob half, full trim + assertClose(.5, anim.scale0_1(0, 0, -1), .001); // knob half, full neg trim + assertClose(1, anim.scale0_1(0, 5, 0), .001); // knob full + assertClose(0, anim.scale0_1(0, -5, 0), .001); // knob down full + assertClose(.75, anim.scale0_1(0, (5.0f * .5f), 0), .001); // knob 3/4 + + // CV, no knob + assertClose(1, anim.scale0_1(5, 0, 1), .001); // full cv, untrimmed + assertClose(0, anim.scale0_1(-5, 0, 1), .001); // full cv, untrimmed + assertClose(.25, anim.scale0_1((-5.0f * .5f), 0, 1), .001); // 3/4 cv, untrimmed + + // assertClose(.75, anim.scale0_1(5, 0, .5f), .001); // full cv, half trim + assertClose(0, anim.scale0_1(5, 0, -1), .001); // full cv, full neg trim + +} + + +#if 0 +static void dump(const char * msg, const Animator& anim) +{ + std::cout << "dumping " << msg << "\nfiltFreq" + << " " << std::pow(2, anim.filterFrequencyLog[0]) + << " " << std::pow(2, anim.filterFrequencyLog[1]) + << " " << std::pow(2, anim.filterFrequencyLog[2]) + << " " << std::pow(2, anim.filterFrequencyLog[3]) + << std::endl; +} + +static void x() +{ + Animator anim; + anim.setSampleRate(44100); + anim.init(); + anim.params[anim.FILTER_FC_PARAM].value = 0; + anim.step(); + + dump("init", anim); + + // TODO: assert here + anim.params[anim.FILTER_FC_PARAM].value = 5; + anim.step(); + dump("fc 5", anim); + + anim.params[anim.FILTER_FC_PARAM].value = -5; + anim.step(); + dump("fc -5", anim); + + std::cout << "\nabout to modulate up. maxLFO, def depth\n"; + anim.params[anim.FILTER_FC_PARAM].value = 0; + anim.params[anim.FILTER_MOD_DEPTH_PARAM].value = 0; + anim.jamModForTest = true; + anim.modValueForTest = 5; + anim.step(); + dump("max up def", anim); + + std::cout << "\nabout to modulate up. minLFO, def depth\n"; + anim.params[anim.FILTER_FC_PARAM].value = 0; + anim.params[anim.FILTER_MOD_DEPTH_PARAM].value = 0; + anim.jamModForTest = true; + anim.modValueForTest = -5; + anim.step(); + dump("max down def", anim); + + std::cout << "\nabout to modulate up. maxLFO, max depth\n"; + anim.params[anim.FILTER_FC_PARAM].value = 0; + anim.params[anim.FILTER_MOD_DEPTH_PARAM].value = 5; + anim.jamModForTest = true; + anim.modValueForTest = 5; + anim.step(); + dump(" modulate up. maxLFO, max depthf", anim); + + + std::cout << "\nabout to modulate down. minLFO, max depth\n"; + anim.params[anim.FILTER_FC_PARAM].value = 0; + anim.params[anim.FILTER_MOD_DEPTH_PARAM].value = 5; + anim.jamModForTest = true; + anim.modValueForTest = -5; + anim.step(); + dump(" modulate up. maxLFO, max depthf", anim); + + +#if 0 + // TODO: would be nice to be able to inject an LFO voltage + anim.params[anim.FILTER_FC_PARAM].value = 0; + anim.params[anim.FILTER_MOD_DEPTH_PARAM].value = 5; + for (int i = 0; i < 40000; ++i) { + anim.step(); + } + dump("fc 0 depth 1", anim); + + std::cout << "about to to depth -\n"; + // TODO: would be nice to be able to inject an LFO voltage + anim.params[anim.FILTER_FC_PARAM].value = 0; + anim.params[anim.FILTER_MOD_DEPTH_PARAM].value = -5; + for (int i = 0; i < 4000; ++i) { + anim.step(); + } + dump("fc 0 depth -5", anim); +#endif +} +#endif + +/** +*Interpolates the frequency using lookups +* @param model = 0(bass) 1(tenor) 2(countertenor) 3(alto) 4(soprano) +* @param index = 0..4 (formant F1..F5) +* @param vowel is the continuous index into the per / vowel lookup tables(0..4) +* 0 = a, 1 = e, 2 = i, 3 = o 4 = u +*/ +//float getLogFrequency(int model, int index, float vowel) +static void testFormantTables() +{ + FormantTables2 ff; + float x = ff.getLogFrequency(0, 0, 0); + assert(x > 0); + + x = ff.getNormalizedBandwidth(0, 0, 0); + assert(x > 0); + + x = ff.getGain(0, 0, 0); +#if 1 // store DB, not gain + assert(x <= 0); + assert(x >= -62); +#else + assert(x > 0) +#endif + + // spot check a few freq + // formant F2 of alto, 'u' + x = ff.getLogFrequency(3, 1, 4); + assertClose(x, std::log2(700), .0001); + // formant F3 of soprano, 'o' + x = ff.getLogFrequency(4, 2, 3); + assertClose(x, std::log2(2830), .0001); +} + +static void testFormantTables2() +{ + FormantTables2 ff; + for (int model = 0; model < FormantTables2::numModels; ++model) { + for (int formantBand = 0; formantBand < FormantTables2::numFormantBands; ++formantBand) { + for (int vowel = 0; vowel < FormantTables2::numVowels; ++vowel) { + const float f = ff.getLogFrequency(model, formantBand, float(vowel)); + + // check that the frequencies are possible formants + assert(std::pow(2, f) > 100); + assert(std::pow(2, f) < 5500); + + const float nBw = ff.getNormalizedBandwidth(model, formantBand, float(vowel)); + assert(nBw < .5); + assert(nBw > .01); + + // db now + const float gain = ff.getGain(model, formantBand, float(vowel)); + assertLE(gain, 0); + assertGT(gain, -70); + // assertLE(gain, 1); + // assert(gain > 0); + } + } + } +} + + +static void testVocalFilter() +{ + VocalFilter vf; + vf.setSampleRate(44100); + vf.init(); + + vf.outputs[VocalFilter::AUDIO_OUTPUT].value = 0; + vf.inputs[VocalFilter::AUDIO_INPUT].value = 1; + vf.step(); // prime it + // with input, should have output + for (int i = 0; i < 50; ++i) { + vf.step(); + assert(vf.outputs[VocalFilter::AUDIO_OUTPUT].value != 0); + } +} + +static void testInputExtremes() +{ + VocalAnimator va; + va.setSampleRate(44100); + va.init(); + + using fp = std::pair; + std::vector< std::pair > paramLimits; + + paramLimits.resize(va.NUM_PARAMS); + paramLimits[va.LFO_RATE_PARAM] = fp(-5.0f, 5.0f); + // paramLimits[va.LFO_SPREAD_PARAM] = fp(-5.0f, 5.0f); + paramLimits[va.FILTER_FC_PARAM] = fp(-5.0f, 5.0f); + paramLimits[va.FILTER_Q_PARAM] = fp(-5.0f, 5.0f); + paramLimits[va.FILTER_MOD_DEPTH_PARAM] = fp(-5.0f, 5.0f); + + + paramLimits[va.LFO_RATE_TRIM_PARAM] = fp(-1.0f, 1.0f); + paramLimits[va.FILTER_Q_TRIM_PARAM] = fp(-1.0f, 1.0f); + paramLimits[va.FILTER_FC_TRIM_PARAM] = fp(-1.0f, 1.0f); + paramLimits[va.FILTER_MOD_DEPTH_TRIM_PARAM] = fp(-1.0f, 1.0f); + + paramLimits[va.BASS_EXP_PARAM] = fp(0.f, 1.0f); + paramLimits[va.TRACK_EXP_PARAM] = fp(0.f, 2.0f); + paramLimits[va.LFO_MIX_PARAM] = fp(0.f, 1.0f); + + // TODO: why is output going so high? + ExtremeTester< VocalAnimator>::test(va, paramLimits, false, "vocal animator"); +} + + +static void testVocalExtremes() +{ + + VocalFilter va; + va.setSampleRate(44100); + va.init(); + + using fp = std::pair; + std::vector< std::pair > paramLimits; + + paramLimits.resize(va.NUM_PARAMS); + + paramLimits[va.FILTER_Q_PARAM] = fp(-5.0f, 5.0f); + paramLimits[va.FILTER_Q_TRIM_PARAM] = fp(-1.0f, 1.0f); + paramLimits[va.FILTER_FC_PARAM] = fp(-5.0f, 5.0f); + + paramLimits[va.FILTER_FC_TRIM_PARAM] = fp(-1.0f, 1.0f); + paramLimits[va.FILTER_VOWEL_PARAM] = fp(-5.f, 5.0f); + paramLimits[va.FILTER_VOWEL_TRIM_PARAM] = fp(-1.f, 1.0f); + paramLimits[va.FILTER_MODEL_SELECT_PARAM] = fp(0.f, 4.0f); + + paramLimits[va.FILTER_BRIGHTNESS_PARAM] = fp(-5.f, 5.0f); + paramLimits[va.FILTER_BRIGHTNESS_TRIM_PARAM] = fp(-1.0f, 1.0f); + + ExtremeTester< VocalFilter>::test(va, paramLimits, false, "vocal filter"); + +} +void testVocalAnimator() +{ + test0(); + test1(); + test2(); + test3(); + testScalers(); + testFormantTables(); + testFormantTables2(); + + testVocalFilter(); +#if defined(_DEBUG) && true + printf("skipping extremes\n"); +#else + testVocalExtremes(); + testInputExtremes(); +#endif + +} \ No newline at end of file diff --git a/plugins/makefile.msvc b/plugins/makefile.msvc index e42d6502..e2af06f7 100644 --- a/plugins/makefile.msvc +++ b/plugins/makefile.msvc @@ -24,6 +24,7 @@ bin: $(call run_make,LindenbergResearch,bin) $(call run_make,Qwelk,bin) $(call run_make,SonusModular,bin) + $(call run_make,squinkylabs-plug1,bin) $(call run_make,SubmarineFree,bin) $(call run_make,Template,bin) $(call run_make,Valley,bin) @@ -46,6 +47,7 @@ clean: $(call run_make,LindenbergResearch,clean) $(call run_make,Qwelk,clean) $(call run_make,SonusModular,clean) + $(call run_make,squinkylabs-plug1,clean) $(call run_make,SubmarineFree,clean) $(call run_make,Template,clean) $(call run_make,Valley,clean) diff --git a/src/plugin.cpp b/src/plugin.cpp index 0b67c885..e7bff1b4 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -581,6 +581,7 @@ extern void init_plugin_Koralfx (rack::Plugin *p); extern void init_plugin_LindenbergResearch (rack::Plugin *p); extern void init_plugin_Qwelk (rack::Plugin *p); extern void init_plugin_SonusModular (rack::Plugin *p); +extern void init_plugin_squinkylabs_plug1 (rack::Plugin *p); extern void init_plugin_SubmarineFree (rack::Plugin *p); extern void init_plugin_Template (rack::Plugin *p); extern void init_plugin_Valley (rack::Plugin *p); @@ -627,6 +628,7 @@ void vst2_load_static_rack_plugins(void) { vst2_load_static_rack_plugin("LindenbergResearch", &init_plugin_LindenbergResearch); vst2_load_static_rack_plugin("Qwelk", &init_plugin_Qwelk); vst2_load_static_rack_plugin("SonusModular", &init_plugin_SonusModular); + vst2_load_static_rack_plugin("squinkylabs-plug1", &init_plugin_squinkylabs_plug1); vst2_load_static_rack_plugin("SubmarineFree", &init_plugin_SubmarineFree); vst2_load_static_rack_plugin("Template", &init_plugin_Template); vst2_load_static_rack_plugin("Valley", &init_plugin_Valley); diff --git a/vst2_bin/CHANGELOG_VST.txt b/vst2_bin/CHANGELOG_VST.txt index 9080c871..3a862aa4 100644 --- a/vst2_bin/CHANGELOG_VST.txt +++ b/vst2_bin/CHANGELOG_VST.txt @@ -12,6 +12,13 @@ - add module VultModules.Tangents - add module VultModules.Tohe - add module VultModules.Trummor +- add module squinkylabs-plug1.Booty +- add module squinkylabs-plug1.Vocal +- add module squinkylabs-plug1.VocalFilter +- add module squinkylabs-plug1.ColoredNoise +- add module squinkylabs-plug1.Tremolo +- add module squinkylabs-plug1.CPU_Hog +- add module squinkylabs-plug1.ThreadBoost ** July 1st, 2018 diff --git a/vst2_bin/README_vst2.txt b/vst2_bin/README_vst2.txt index cc3ca14b..fc42c7bf 100644 --- a/vst2_bin/README_vst2.txt +++ b/vst2_bin/README_vst2.txt @@ -238,6 +238,13 @@ The VST2 plugin includes the following add-on modules: - SonusModular.Scramblase - SonusModular.Twoff - SonusModular.Yabp + - squinkylabs-plug1.Booty + - squinkylabs-plug1.Vocal + - squinkylabs-plug1.VocalFilter + - squinkylabs-plug1.ColoredNoise + - squinkylabs-plug1.Tremolo + - squinkylabs-plug1.CPU_Hog + - squinkylabs-plug1.ThreadBoost - SubmarineFree.AG106 - SubmarineFree.BB120 - SubmarineFree.FF110 @@ -274,6 +281,14 @@ The VST2 plugin includes the following add-on modules: - Valley.UGraph - Valley.Dexter - Valley.Plateau + - VultModules.Debriatus + - VultModules.Lateralus + - VultModules.Rescomb + - VultModules.Splie + - VultModules.Stabile + - VultModules.Tangents + - VultModules.Tohe + - VultModules.Trummor Please notice that the Audible/Mutable Instruments modules appear under a different name in the UI. For example, "Clouds" is listed as "Texture Synthesizer". diff --git a/vst2_bin/log.txt b/vst2_bin/log.txt index 815ac499..9ed8779e 100644 --- a/vst2_bin/log.txt +++ b/vst2_bin/log.txt @@ -1,55 +1,70 @@ [0.000 info src/main.cpp:58] VeeSeeVST Rack 0.6.1 [0.000 info src/main.cpp:61] Global directory: f:\git\VeeSeeVSTRack\vst2_bin\/ [0.000 info src/main.cpp:62] Local directory: f:\git\VeeSeeVSTRack\vst2_bin\/ -[0.000 info src/plugin.cpp:613] vcvrack: Loaded static plugin AS 0.6.1 -[0.001 info src/plugin.cpp:613] vcvrack: Loaded static plugin AudibleInstruments 0.6.1 -[0.001 info src/plugin.cpp:613] vcvrack: Loaded static plugin Befaco 0.6.1 -[0.002 info src/plugin.cpp:613] vcvrack: Loaded static plugin Bogaudio 0.6.1 -[0.002 info src/plugin.cpp:613] vcvrack: Loaded static plugin cf 0.6.1 -[0.002 info src/plugin.cpp:613] vcvrack: Loaded static plugin ErraticInstruments 0.6.1 -[0.002 info src/plugin.cpp:613] vcvrack: Loaded static plugin ESeries 0.6.1 -[0.003 info src/plugin.cpp:613] vcvrack: Loaded static plugin Fundamental 0.6.1 -[0.003 info src/plugin.cpp:613] vcvrack: Loaded static plugin HetrickCV 0.6.1 -[0.003 info src/plugin.cpp:613] vcvrack: Loaded static plugin Koralfx-Modules 0.6.1 -[0.003 info src/plugin.cpp:613] vcvrack: Loaded static plugin LindenbergResearch 0.6.1 -[0.003 info src/plugin.cpp:613] vcvrack: Loaded static plugin Qwelk 0.6.1 -[0.004 info src/plugin.cpp:613] vcvrack: Loaded static plugin SonusModular 0.6.1 -[0.004 info src/plugin.cpp:613] vcvrack: Loaded static plugin SubmarineFree 0.6.1 -[0.004 info src/plugin.cpp:613] vcvrack: Loaded static plugin Template 0.6.1 -[0.004 info src/plugin.cpp:613] vcvrack: Loaded static plugin Valley 0.6.1 -[0.004 info src/plugin.cpp:613] vcvrack: Loaded static plugin VultModules 0.6.1 +[0.000 info src/plugin.cpp:614] vcvrack: Loaded static plugin AS 0.6.1 +[0.000 info src/plugin.cpp:614] vcvrack: Loaded static plugin AudibleInstruments 0.6.1 +[0.001 info src/plugin.cpp:614] vcvrack: Loaded static plugin Befaco 0.6.1 +[0.002 info src/plugin.cpp:614] vcvrack: Loaded static plugin Bogaudio 0.6.1 +[0.002 info src/plugin.cpp:614] vcvrack: Loaded static plugin cf 0.6.1 +[0.002 info src/plugin.cpp:614] vcvrack: Loaded static plugin ErraticInstruments 0.6.1 +[0.002 info src/plugin.cpp:614] vcvrack: Loaded static plugin ESeries 0.6.1 +[0.002 info src/plugin.cpp:614] vcvrack: Loaded static plugin Fundamental 0.6.1 +[0.003 info src/plugin.cpp:614] vcvrack: Loaded static plugin HetrickCV 0.6.1 +[0.003 info src/plugin.cpp:614] vcvrack: Loaded static plugin Koralfx-Modules 0.6.1 +[0.003 info src/plugin.cpp:614] vcvrack: Loaded static plugin LindenbergResearch 0.6.1 +[0.003 info src/plugin.cpp:614] vcvrack: Loaded static plugin Qwelk 0.6.1 +[0.003 info src/plugin.cpp:614] vcvrack: Loaded static plugin SonusModular 0.6.1 +[0.004 info src/plugin.cpp:614] vcvrack: Loaded static plugin squinkylabs-plug1 0.6.1 +[0.004 info src/plugin.cpp:614] vcvrack: Loaded static plugin SubmarineFree 0.6.1 +[0.004 info src/plugin.cpp:614] vcvrack: Loaded static plugin Template 0.6.1 +[0.004 info src/plugin.cpp:614] vcvrack: Loaded static plugin Valley 0.6.1 +[0.004 info src/plugin.cpp:614] vcvrack: Loaded static plugin VultModules 0.6.1 [0.005 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_146097_cc.svg [0.005 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_31859_cc.svg [0.005 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_1343816_cc.svg [0.005 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_1343811_cc.svg -[0.006 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_1084369_cc.svg +[0.005 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_1084369_cc.svg [0.006 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_1745061_cc.svg [0.006 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_1240789_cc.svg [0.006 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_305536_cc.svg [0.006 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_468341_cc.svg -[0.200 info src/window.cpp:690] Loaded font f:\git\VeeSeeVSTRack\vst2_bin\/res/fonts/DejaVuSans.ttf -[0.303 info src/app/RackWidget.cpp:192] Loading patch from string -[0.304 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/Core/AudioInterface.svg -[0.304 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/ScrewSilver.svg -[0.305 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/PJ301M.svg -[0.305 info src/window.cpp:690] Loaded font f:\git\VeeSeeVSTRack\vst2_bin\/res/fonts/ShareTechMono-Regular.ttf -[0.306 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/Core/MIDIToCVInterface.svg -[0.308 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/XCO.svg -[0.309 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/knob_68px.svg -[0.309 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/knob_16px.svg -[0.309 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/button_9px_0.svg -[0.309 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/button_9px_1.svg -[0.310 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/knob_38px.svg -[0.310 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/slider_switch_2_14px_0.svg -[0.310 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/slider_switch_2_14px_1.svg -[0.310 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/port.svg -[0.311 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Fundamental/res/VCA.svg -[0.311 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/RoundLargeBlackKnob.svg -[0.312 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Fundamental/res/VCF.svg -[0.312 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/RoundHugeBlackKnob.svg -[0.313 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/ADSR.svg -[0.314 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/as-hexscrew.svg -[0.314 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/as-SlidePot.svg -[0.314 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/as-SlidePotHandle.svg -[0.314 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/as-PJ301M.svg -[3.116 info src/app/RackWidget.cpp:154] Saving patch to string +[0.186 info src/window.cpp:690] Loaded font f:\git\VeeSeeVSTRack\vst2_bin\/res/fonts/DejaVuSans.ttf +[0.288 info src/app/RackWidget.cpp:192] Loading patch from string +[0.289 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/Core/AudioInterface.svg +[0.290 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/ScrewSilver.svg +[0.290 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/PJ301M.svg +[0.290 info src/window.cpp:690] Loaded font f:\git\VeeSeeVSTRack\vst2_bin\/res/fonts/ShareTechMono-Regular.ttf +[0.291 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/Core/MIDIToCVInterface.svg +[0.294 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/XCO.svg +[0.294 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/knob_68px.svg +[0.294 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/knob_16px.svg +[0.294 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/button_9px_0.svg +[0.295 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/button_9px_1.svg +[0.295 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/knob_38px.svg +[0.295 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/slider_switch_2_14px_0.svg +[0.295 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/slider_switch_2_14px_1.svg +[0.296 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/port.svg +[0.296 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Fundamental/res/VCA.svg +[0.296 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/RoundLargeBlackKnob.svg +[0.297 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Fundamental/res/VCF.svg +[0.298 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/RoundHugeBlackKnob.svg +[0.299 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/ADSR.svg +[0.299 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/as-hexscrew.svg +[0.299 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/as-SlidePot.svg +[0.300 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/as-SlidePotHandle.svg +[0.300 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/as-PJ301M.svg +[5.065 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/squinkylabs-plug1/res/thread_booster_panel.svg +[5.065 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/NKK_0.svg +[5.066 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/NKK_1.svg +[5.066 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/NKK_2.svg +[9.920 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/squinkylabs-plug1/res/colors_panel.svg +[9.920 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/Rogan2PSWhite.svg +[9.920 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/Trimpot.svg +[14.657 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/squinkylabs-plug1/res/trem_panel.svg +[14.657 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/RoundBlackKnob.svg +[14.658 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/Rogan1PSBlue.svg +[19.194 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/squinkylabs-plug1/res/formants_panel.svg +[26.901 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/squinkylabs-plug1/res/vocal_animator_panel.svg +[40.630 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/squinkylabs-plug1/res/booty_panel.svg +[40.630 info src/window.cpp:741] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/Rogan3PSBlue.svg +[67.401 info src/app/RackWidget.cpp:154] Saving patch to string diff --git a/vst2_bin/plugins/squinkylabs-plug1/LICENSE b/vst2_bin/plugins/squinkylabs-plug1/LICENSE new file mode 100644 index 00000000..30d73b3d --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 squinkylabs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vst2_bin/plugins/squinkylabs-plug1/LICENSE-dist.txt b/vst2_bin/plugins/squinkylabs-plug1/LICENSE-dist.txt new file mode 100644 index 00000000..636c5ee5 --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/LICENSE-dist.txt @@ -0,0 +1,67 @@ +# kiss FFT + +Copyright (c) 2003-2010 Mark Borgerding + +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. + * 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. + + +# Vincent Falco + +"A Collection of Useful C++ Classes for Digital Signal Processing" +By Vincent Falco + +Official project location: +http://code.google.com/p/dspfilterscpp/ + +See DspFilter.cpp for notes and bibliography. + +-------------------------------------------------------------------------------- + +License: MIT License (http://www.opensource.org/licenses/mit-license.php) +Copyright (c) 2009 by Vincent Falco + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +# 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: + + +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 Rack Component Library + +Component Library graphics by Grayscale (http://grayscale.info/) +Licensed under CC BY-NC 4.0 (https://creativecommons.org/licenses/by-nc/4.0/) diff --git a/vst2_bin/plugins/squinkylabs-plug1/README.md b/vst2_bin/plugins/squinkylabs-plug1/README.md new file mode 100644 index 00000000..13a7bbcd --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/README.md @@ -0,0 +1,21 @@ +# About SquinkyVCV + +This project is a growing collection of modules for the VCV Rack vritual modular synthesizer. You can find more information about VCV Rack [here](https://vcvrack.com/). + +You can find us on Facebook [here](https://www.facebook.com/SquinkyLabs). + +## Manuals + +Here is the user's manual for our modules: [instruction manual](./docs/booty-shifter.md). It contains descriptions of all of them. + +## 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. + +For general communications, you may use our [Facebook Page](https://www.facebook.com/SquinkyLabs). + +We are not currently accepting pull requests. + +## More information for programmers, builders, and experimenters + +There are "secret" modules, extra code and other things scattered around this repo. Pointers to some of it can be found [in the docs folder](./docs/README.md). diff --git a/vst2_bin/plugins/squinkylabs-plug1/docs/README.md b/vst2_bin/plugins/squinkylabs-plug1/docs/README.md new file mode 100644 index 00000000..71d640ab --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/docs/README.md @@ -0,0 +1,31 @@ +# Squinky Labs modules for VCV Rack + +All of our plugins are free and open source. The [instruction manual](booty-shifter.md) describes all of the released modules. + +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). + +## Information for developer and experimenters + +There are various test modules, test code, and other good things hidden away in this repo. We will try to point you to some that may be of interest. + +Most of the documentation may be found in the [docs folder](../docs/.). + +## Building source + +As with all third-party modules for VCV, you must: + +* Clone the VCV Rack repo. +* Build Rack from source. +* Clone SquinkyVCV in Rack’s plugins folder. +* `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). \ No newline at end of file diff --git a/vst2_bin/plugins/squinkylabs-plug1/docs/booty-shifter.md b/vst2_bin/plugins/squinkylabs-plug1/docs/booty-shifter.md new file mode 100644 index 00000000..44c078a4 --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/docs/booty-shifter.md @@ -0,0 +1,207 @@ +# Table of contents + +[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) + +# Chopper tremolo / programmable LFO + +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 + +![chopper image](../docs/lfo-waveforms.png) + +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 + +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 + +![noise image](../docs/colors.png) + +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 + +![vocal formant filter image](./growler.jpg) + +**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 +![growler scope](./growler.png) + +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 + +**Booty Shifter** is a frequency shifter inspired by the Moog/Bode frequency shifter module. + +![booty shifter image](./booty-shifter.png) + +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 + +![formants image](./formants.png) + +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 + +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 + +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. diff --git a/vst2_bin/plugins/squinkylabs-plug1/docs/booty-shifter.png b/vst2_bin/plugins/squinkylabs-plug1/docs/booty-shifter.png new file mode 100644 index 00000000..1d5c6553 Binary files /dev/null and b/vst2_bin/plugins/squinkylabs-plug1/docs/booty-shifter.png differ diff --git a/vst2_bin/plugins/squinkylabs-plug1/docs/colors.png b/vst2_bin/plugins/squinkylabs-plug1/docs/colors.png new file mode 100644 index 00000000..81660f76 Binary files /dev/null and b/vst2_bin/plugins/squinkylabs-plug1/docs/colors.png differ diff --git a/vst2_bin/plugins/squinkylabs-plug1/docs/composites.md b/vst2_bin/plugins/squinkylabs-plug1/docs/composites.md new file mode 100644 index 00000000..6a6a2e0d --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/docs/composites.md @@ -0,0 +1,67 @@ +# About composites + +This is a somewhat ugly "architecture" that lets the same module "guts" run inside a VCV plugin, and also turn inside our unit test framework (which does not link against VCV Rack). + +While this is slightly ugly, it is incredibly useful to us to be able to test and benchmark our plugins from a simple command line program. + +You may look at the [FrequencyShifter composite](../composites/FrequencyShifter.h) as an example. + +Here is [more on our tests](unit-test.md). + +## To make a composite + +Make a new class in the composites folder. This class is your composite class. This class must work in the test environment and the VCV Widget environment. + +Templatize the class so that its base class is the template parameter. For example, in the FrequencyShifter class: +``` +template +class FrequencyShifter : public TBase +{ +... +} +``` +Follow the link to look at [FrequencyShifter](composites/FrequencyShifter.h) + +Create two constructors. One with no arguments for the tests to use, and the other with a `Module *` to use in the VCV plugin. + +Put all the Input, Output, Param, Light enums into the composite class, rather than the normal Widget class. + +To use this composite in a test, derive concrete class from TestComposite +```c++ +using Shifter = FrequencyShifter; +``` +Now you may use this class in a test. The TestComposite base class give your tests access to the inputs and outputs of your "widget" +```c++ + Shifter fs; + + fs.setSampleRate(44100); + fs.init(); + fs.inputs[Shifter::AUDIO_INPUT].value = 0; +``` + +## How it works + +The class hierarchy is something like these crudely drawn UML diagrams +``` +TestWidget -> I/O + | + ^ + / \ + --- + | + FrequencyShifter -> enums + ``` + + ``` + ModuleWidget -> I/O -> Module + | + ^ + / \ + --- + | + FrequencyShifter -> enums +``` + +The purpose of the composite's base class is to provide the inputs/outputs/etc... to the composite. The TestComposite base class has vectors of I/O exposed as public members that the tests may directly manipulate. The WidgetComposite base class just marshals references to the I/O that is already in the VCV provided Widget base class. + +All of the enums for module I/O goes into the composite. So they are available to tests as composite.ENUM_NAME. They are also available to Modules as this->composite.ENUM_NAME. diff --git a/vst2_bin/plugins/squinkylabs-plug1/docs/experimental.md b/vst2_bin/plugins/squinkylabs-plug1/docs/experimental.md new file mode 100644 index 00000000..b5fbbbd5 --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/docs/experimental.md @@ -0,0 +1,15 @@ +# Experimental Modules + +Usually we have one or two modules that work, but are not ready for release. Often they have very ugly panels, are missing features, aren't tuned yet for performance or playability. + +I most cases there are no pre-built binaries for these plugins, you must build them yourself. + +Never the less, we encourage the curious to build these, try them out, and report back to us on any bugs or desired features. + +## Building experiments + +To build all the modules, including the experimental ones, simply run `make _EXP=1`. + +## Current experiments + +The Thread Booster is quite experimental. It is described [here](thread-booster.md). This one does have a pre-built binary. diff --git a/vst2_bin/plugins/squinkylabs-plug1/docs/formants.png b/vst2_bin/plugins/squinkylabs-plug1/docs/formants.png new file mode 100644 index 00000000..c96ca7f2 Binary files /dev/null and b/vst2_bin/plugins/squinkylabs-plug1/docs/formants.png differ diff --git a/vst2_bin/plugins/squinkylabs-plug1/docs/growler.jpg b/vst2_bin/plugins/squinkylabs-plug1/docs/growler.jpg new file mode 100644 index 00000000..b8fef84b Binary files /dev/null and b/vst2_bin/plugins/squinkylabs-plug1/docs/growler.jpg differ diff --git a/vst2_bin/plugins/squinkylabs-plug1/docs/growler.png b/vst2_bin/plugins/squinkylabs-plug1/docs/growler.png new file mode 100644 index 00000000..89609c72 Binary files /dev/null and b/vst2_bin/plugins/squinkylabs-plug1/docs/growler.png differ diff --git a/vst2_bin/plugins/squinkylabs-plug1/docs/installing-binaries.md b/vst2_bin/plugins/squinkylabs-plug1/docs/installing-binaries.md new file mode 100644 index 00000000..064f0b2b --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/docs/installing-binaries.md @@ -0,0 +1,32 @@ +# Installing binaries + +This should rarely be needed, as the current versions of our modules are in the plugin manager. + +Download the current release from our [releases page](https://github.com/squinkylabs/SquinkyVCV/releases), or get them from someone who built them for you. Warning: often we do not post all the current releases. + +Follow the standard instructions for installing third-party plugins in rack: [here](https://vcvrack.com/manual/Installing.html). These instructions will tell you how to put the zip file in a location where VCV Rack will unzip it for you. + +## Unzipping yourself + +Usually letting VCV Rack unzip your plugins is easiest, so you shouldn't need this information. + +Note that the built-in unzip in windows explorer will create an extra folder called with the same name as the zip file. But when everything is unzipped you want your folder structure to look like this: + +``` +plugins/ + + squinkylabs-plug1/ + plugin.dll + plugin.dylib (optional, unused) + plugin.so (optional, unused) + LICENSE + res/ + + +``` + +The plugins will be under the **Rack** folder. Rack folders are here: + +* MacOS: Documents/Rack/ +* Windows: My Documents/Rack/ +* Linux: ~/.Rack/ diff --git a/vst2_bin/plugins/squinkylabs-plug1/docs/lfo-waveforms.png b/vst2_bin/plugins/squinkylabs-plug1/docs/lfo-waveforms.png new file mode 100644 index 00000000..e9cd9539 Binary files /dev/null and b/vst2_bin/plugins/squinkylabs-plug1/docs/lfo-waveforms.png differ diff --git a/vst2_bin/plugins/squinkylabs-plug1/docs/thread-booster.md b/vst2_bin/plugins/squinkylabs-plug1/docs/thread-booster.md new file mode 100644 index 00000000..89ebebc0 --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/docs/thread-booster.md @@ -0,0 +1,66 @@ +# Thread Booster + +Thread booster boosts the thread priority of the audio streaming thread in VCV Rack. The intention is to make sure that if your computer strains to produce all that audio, audio will get priority in the competition for the CPU over other things like drawing the screen, or showing ads in your web browser. + +If you aren't a computer programmer, this may sound like some hocus-pocus. But it is a very basic technique used in many audio applications. Google "audio thread priority" for some random instances. + +If some other task is competing for CPU and the audio loses out, it may be unable to keep up with the audio interface. This will cause occasional data underruns, which often sound like pops and clicks. + +Now, while raising the audio thread priority is a fundamentally sound thing to do, it is not going to solve all audio problems. Some users report that Thread Booster does not help at all. Others report significant improvement. + +On all three operating systems we have been able to get the "real-time" setting to work (although read below - it's not always technically "real time"). + +## Notes for Linux + +Linux has two challenges for thread booster: + +* The non-real-time settings set by the POSIX Pthread API don't work. +* Raising thread priority to real-time in Linux is a privileged operation. + +The impact of the first challenge is that the middle "boosted" setting might not do much in Linux. We recommend the real-time setting. + +You can always set the real time priority of Rack if run as root, but we do not recommend that. Instead you may use `setcap` to give VCV Rack permission to use real-time priority. + +```bash +sudo setcap cap_sys_nice=ep +``` + +After giving Rack this ability, it will stay set until the Rack executable file is changed, either by downloading a new one, or building a new one on top of it. + +## Notes on Windows + +Unlike Linux, the middle boost setting works well on Windows. The realtime setting works well also, although it is not the very highest setting that windows calls realtime. Instead it sets the Rack process as a whole to HIGH_PRIORITY_CLASS, and the sets the audio thread to THREAD_PRIORITY_TIME_CRITICAL. We have found this to give a very good boost without requiring that you run Rack as administrator (which is not recommended). + +We only test on Windows 7. If you have issues with other versions of Windows, please let us know. + +## Notes on OS X + +As usual, it just works on OS X. + +## Caveats and limitations + +If you try this plugin, be aware that changing Rack’s thread priority behind its back may cause some things to misbehave. We have not seen this happen, but it's a real possibility. Running at an elevated priority could make something in VCV or the plugins misbehave or glitch. + +Another limitation is that Thread Booster does not boost the thread(s) that move data between Rack's audio engine and the Audio (sound card) Module(s). + +## Is it dangerous to run at "realtime" priority? + +If you scour the Internet you will find some warnings about boosting thread priorities to super high thread priorities, saying that there is a danger your computer will become unresponsive, possibly even needing to be re-booted. + +While in general this is true, it is not true that raising the priority of a single thread will do this, as a single thread can at worst use all the cycles of a single core. For the last 10 years or so all CPUs have more than one core, so there will always be plenty of CPU left over. + +Most audio programs in fact do raise the priority of their audio threads, and of course work fine. So, while we don't guarantee that Thread Booster will fix all clicks and pops, we do believe that it is very unlikely to do any harm. + +## Questions for testers + +Please use our [GitHub issues](https://github.com/squinkylabs/SquinkyVCV/issues) to send us feedback. Some things we are curious about: + +* With a fully loaded session that starts to make pops and clicks, does thread booster fix it? +* If you are able to run realtime, is there any noticeable difference between boosted and real-time? +* For all reports, please list operating system and version (i.e. Windows-10, OSX 10.12, Ubuntu 16.04). + +## Known issues + +On the Mac, Boosting and the switching back to normal will not restore the original priority. It will actually be running at a lower priority. + +When the Thread Booster comes up initially, no LEDs are illuminated - the normal LED should be. diff --git a/vst2_bin/plugins/squinkylabs-plug1/docs/unit-test.md b/vst2_bin/plugins/squinkylabs-plug1/docs/unit-test.md new file mode 100644 index 00000000..3f19698f --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/docs/unit-test.md @@ -0,0 +1,28 @@ +# Some notes on our unit tests + +The ./test folder contains a unit test program and test-support classes. + +In general most of this software is fairly unremarkable code that supports unit testing of the various parts of our plugins. + +We find that reasonably thorough unit testing is even more valuable for plugins than for "normal" code. Some of the reasons are: + +* Without unit tests, it can be very easy to make filters that don’t have the response they should ("oops – forget to multiply by 2 pi"), lookup tables aren’t as accurate as they should be, numeric approximations have gross discontinuities, etc. +* Unit tests may be built and debugged with a conventional IDE, whereas (at least for us) debugging a plugin with gdb from inside a running instance of VCV Rack is more difficult. +* It is easy to measure CPU usage in a unit test, whereas it is difficult to impossible to measure while running in VCV Rack. + +This last point is very important, as one of the basic rules of code optimization is that a programmers intuition (guess) as to where the bottlenecks are is remarkably unreliable – you must drive your optimizations from real data. For example, we discovered that over half the CPU time in our vocal filter was in evaluating assertions! + +As stated above, much of the test code is unremarkable, other than its existence. There are a couple of modules however that might be useful to others. + +[MeasureTime](../test/MeasureTime.h) is used to measure the CPU usage of any arbitrary code. It takes a simple lambda and profiles it. + +[Composite pattern](composites.md) allows us to run our plugin code inside a test application as well as inside a VCV Track plugin module. + +[Assert Library](../test/asserts.h) is a very basic collection of assertion macros loosely based on the Chai Assert framework. + +We have enhanced [Makefile](../Makefile) in several useful ways with the addition of [test.mk](../test.mk): + +* Default `make` will generate a plugin with assertions disabled. +* `make test` will generate test.exe, our unit test application, with asserts enabled. +* `make perf` will generate perf.exe, our unit test application, with asserts disabled. +* `make cleantest` is like `make clean`, but for out test code. diff --git a/vst2_bin/plugins/squinkylabs-plug1/gfx/ThreadBoost.xd b/vst2_bin/plugins/squinkylabs-plug1/gfx/ThreadBoost.xd new file mode 100644 index 00000000..2f9fcbfd Binary files /dev/null and b/vst2_bin/plugins/squinkylabs-plug1/gfx/ThreadBoost.xd differ diff --git a/vst2_bin/plugins/squinkylabs-plug1/gfx/booty-panel-design.xd b/vst2_bin/plugins/squinkylabs-plug1/gfx/booty-panel-design.xd new file mode 100644 index 00000000..80da6266 Binary files /dev/null and b/vst2_bin/plugins/squinkylabs-plug1/gfx/booty-panel-design.xd differ diff --git a/vst2_bin/plugins/squinkylabs-plug1/gfx/chopper-panel-design.xd b/vst2_bin/plugins/squinkylabs-plug1/gfx/chopper-panel-design.xd new file mode 100644 index 00000000..f27a2e25 Binary files /dev/null and b/vst2_bin/plugins/squinkylabs-plug1/gfx/chopper-panel-design.xd differ diff --git a/vst2_bin/plugins/squinkylabs-plug1/gfx/formants_panel.xd b/vst2_bin/plugins/squinkylabs-plug1/gfx/formants_panel.xd new file mode 100644 index 00000000..53c45a4f Binary files /dev/null and b/vst2_bin/plugins/squinkylabs-plug1/gfx/formants_panel.xd differ diff --git a/vst2_bin/plugins/squinkylabs-plug1/gfx/noun_938401.svg b/vst2_bin/plugins/squinkylabs-plug1/gfx/noun_938401.svg new file mode 100644 index 00000000..8e711ed4 --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/gfx/noun_938401.svg @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/squinkylabs-plug1/gfx/vocal-anim-panel.xd b/vst2_bin/plugins/squinkylabs-plug1/gfx/vocal-anim-panel.xd new file mode 100644 index 00000000..ddd12e99 Binary files /dev/null and b/vst2_bin/plugins/squinkylabs-plug1/gfx/vocal-anim-panel.xd differ diff --git a/vst2_bin/plugins/squinkylabs-plug1/res/blank_panel.svg b/vst2_bin/plugins/squinkylabs-plug1/res/blank_panel.svg new file mode 100644 index 00000000..4e30c038 --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/res/blank_panel.svg @@ -0,0 +1,17 @@ + + + + + + + panel-6b-flat + + + + + + + + + + diff --git a/vst2_bin/plugins/squinkylabs-plug1/res/booty_panel.svg b/vst2_bin/plugins/squinkylabs-plug1/res/booty_panel.svg new file mode 100644 index 00000000..1e0183d6 --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/res/booty_panel.svg @@ -0,0 +1,102 @@ + + + + + + + panel-6b-flat + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/squinkylabs-plug1/res/colors_panel.svg b/vst2_bin/plugins/squinkylabs-plug1/res/colors_panel.svg new file mode 100644 index 00000000..1c4a661f --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/res/colors_panel.svg @@ -0,0 +1,45 @@ + + + + + + + colors-panel-2-ai + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/squinkylabs-plug1/res/cpu_hog_panel.svg b/vst2_bin/plugins/squinkylabs-plug1/res/cpu_hog_panel.svg new file mode 100644 index 00000000..f2e31cda --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/res/cpu_hog_panel.svg @@ -0,0 +1,40 @@ + + + + + + + cpu_hog_panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/squinkylabs-plug1/res/formants_panel.svg b/vst2_bin/plugins/squinkylabs-plug1/res/formants_panel.svg new file mode 100644 index 00000000..de591576 --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/res/formants_panel.svg @@ -0,0 +1,44 @@ + + + + + + + formants_panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/squinkylabs-plug1/res/thread_booster_panel.svg b/vst2_bin/plugins/squinkylabs-plug1/res/thread_booster_panel.svg new file mode 100644 index 00000000..c73c52ef --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/res/thread_booster_panel.svg @@ -0,0 +1,47 @@ + + + + + + + thread_booster_panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/squinkylabs-plug1/res/trem_panel.svg b/vst2_bin/plugins/squinkylabs-plug1/res/trem_panel.svg new file mode 100644 index 00000000..eb70aaa7 --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/res/trem_panel.svg @@ -0,0 +1,46 @@ + + + + + + + trem_panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/squinkylabs-plug1/res/vocal_animator_panel.svg b/vst2_bin/plugins/squinkylabs-plug1/res/vocal_animator_panel.svg new file mode 100644 index 00000000..a567e16f --- /dev/null +++ b/vst2_bin/plugins/squinkylabs-plug1/res/vocal_animator_panel.svg @@ -0,0 +1,88 @@ + + + + + + + vocal_animator_panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/veeseevstrack_instr.dll__ b/vst2_bin/veeseevstrack_instr.dll__ index 1c9dfe1f..0eb588f0 100644 Binary files a/vst2_bin/veeseevstrack_instr.dll__ and b/vst2_bin/veeseevstrack_instr.dll__ differ diff --git a/vst2_common_staticlibs.mk b/vst2_common_staticlibs.mk index 8afa422d..5ba7e972 100644 --- a/vst2_common_staticlibs.mk +++ b/vst2_common_staticlibs.mk @@ -12,6 +12,7 @@ EXTRALIBS+= $(call plugin_lib,Koralfx-Modules) EXTRALIBS+= $(call plugin_lib,LindenbergResearch) EXTRALIBS+= $(call plugin_lib,Qwelk) EXTRALIBS+= $(call plugin_lib,SonusModular) +EXTRALIBS+= $(call plugin_lib,squinkylabs-plug1) EXTRALIBS+= $(call plugin_lib,SubmarineFree) EXTRALIBS+= $(call plugin_lib,Template) EXTRALIBS+= $(call plugin_lib,Valley)