@@ -15,3 +15,5 @@ | |||
*.exp | |||
*.lib | |||
*.map | |||
*.o | |||
@@ -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 | |||
@@ -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 | |||
} |
@@ -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. |
@@ -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/) |
@@ -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 | |||
@@ -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). |
@@ -0,0 +1,309 @@ | |||
#pragma once | |||
#include <memory> | |||
#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 <class TBase> | |||
/** | |||
* 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<T>(-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<T> 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<ThreadClient> thread; | |||
/** | |||
* Messages moved between thread, messagePool, and crossFader | |||
* as new noise slopes are requested in response to CV/knob changes. | |||
*/ | |||
ManagedPool<NoiseMessage, 2> 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<FFTDataReal> dataBuffer; | |||
}; | |||
class NoiseServer : public ThreadServer | |||
{ | |||
public: | |||
NoiseServer(std::shared_ptr<ThreadSharedState> 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<NoiseMessage*>(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<FFTDataCpx> 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 <class TBase> | |||
float ColoredNoise<TBase>::getSlope() const | |||
{ | |||
const NoiseMessage* curMsg = crossFader.playingMessage(); | |||
return curMsg ? curMsg->noiseSpec.slope : 0; | |||
} | |||
template <class TBase> | |||
void ColoredNoise<TBase>::commonConstruct() | |||
{ | |||
crossFader.enableMakeupGain(true); | |||
std::shared_ptr<ThreadSharedState> threadState = std::make_shared<ThreadSharedState>(); | |||
std::unique_ptr<ThreadServer> server(new NoiseServer(threadState)); | |||
std::unique_ptr<ThreadClient> client(new ThreadClient(threadState, std::move(server))); | |||
this->thread = std::move(client); | |||
} | |||
template <class TBase> | |||
int ColoredNoise<TBase>::_msgCount() const | |||
{ | |||
return messageCount; | |||
} | |||
template <class TBase> | |||
void ColoredNoise<TBase>::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<NoiseMessage*>(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 <class TBase> | |||
void ColoredNoise<TBase>::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 <class TBase> | |||
void ColoredNoise<TBase>::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 <class TBase> | |||
void ColoredNoise<TBase>::step() | |||
{ | |||
serviceFFTServer(); | |||
serviceAudio(); | |||
serviceInputs(); | |||
} |
@@ -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 TBase> | |||
class FrequencyShifter : public TBase | |||
{ | |||
public: | |||
FrequencyShifter(struct Module * module) : TBase(module) | |||
{ | |||
} | |||
FrequencyShifter() : TBase() | |||
{ | |||
} | |||
void setSampleRate(float rate) | |||
{ | |||
reciprocalSampleRate = 1 / rate; | |||
HilbertFilterDesigner<T>::design(rate, hilbertFilterParamsSin, hilbertFilterParamsCos); | |||
} | |||
// must be called after setSampleRate | |||
void init() | |||
{ | |||
SinOscillator<T, true>::setFrequency(oscParams, T(.01)); | |||
exponential2 = ObjectCache<T>::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<T> oscParams; | |||
SinOscillatorState<T> oscState; | |||
BiquadParams<T, 3> hilbertFilterParamsSin; | |||
BiquadParams<T, 3> hilbertFilterParamsCos; | |||
BiquadState<T, 3> hilbertFilterStateSin; | |||
BiquadState<T, 3> hilbertFilterStateCos; | |||
std::shared_ptr<LookupTableParams<T>> exponential2; | |||
float reciprocalSampleRate; | |||
}; | |||
template <class TBase> | |||
inline void FrequencyShifter<TBase>::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<T>::lookup(*exponential2, cvTotal); | |||
freqHz /= 2; // down to 2..2k range that we want. | |||
} | |||
SinOscillator<float, true>::setFrequency(oscParams, freqHz * reciprocalSampleRate); | |||
// Generate the quadrature sin oscillators. | |||
T x, y; | |||
SinOscillator<T, true>::runQuadrature(x, y, oscState, oscParams); | |||
// Filter the input through th quadrature filter | |||
const T input = TBase::inputs[AUDIO_INPUT].value; | |||
const T hilbertSin = BiquadFilter<T>::run(input, hilbertFilterStateSin, hilbertFilterParamsSin); | |||
const T hilbertCos = BiquadFilter<T>::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; | |||
} |
@@ -0,0 +1,65 @@ | |||
#pragma once | |||
#include <vector> | |||
/** | |||
* 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<Input> inputs; | |||
std::vector<Output> outputs; | |||
std::vector<Param> params; | |||
std::vector<Light> lights; | |||
}; |
@@ -0,0 +1,230 @@ | |||
#pragma once | |||
#include <vector> | |||
#include "ClockMult.h" | |||
#include "ObjectCache.h" | |||
#include "AsymRampShaper.h" | |||
#include "GateTrigger.h" | |||
/** | |||
*/ | |||
template <class TBase> | |||
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<LookupTableParams<float>> tanhLookup; | |||
float reciprocalSampleRate = 0; | |||
AsymRampShaperParams rampShaper; | |||
std::shared_ptr<LookupTableParams<float>> exp2 = ObjectCache<float>::getExp2(); | |||
// make some bootstrap scalers | |||
AudioMath::ScaleFun<float> scale_rate; | |||
AudioMath::ScaleFun<float> scale_skew; | |||
AudioMath::ScaleFun<float> scale_shape; | |||
AudioMath::ScaleFun<float> scale_depth; | |||
AudioMath::ScaleFun<float> scale_phase; | |||
GateTrigger gateTrigger; | |||
}; | |||
template <class TBase> | |||
inline void Tremolo<TBase>::init() | |||
{ | |||
tanhLookup = ObjectCache<float>::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 <class TBase> | |||
inline void Tremolo<TBase>::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<float>::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<float>::lookup(*tanhLookup.get(), mod); | |||
TBase::outputs[LFO_OUTPUT].value = mod; | |||
const float gain = modDepth / | |||
LookupTable<float>::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<vec_t>::gen_v(*sawState, *sawParams, tempBuffer, sampleFrames); | |||
// step 2: apply skew and phase shift | |||
// range still 0..1 | |||
AsymRampShaper<vec_t>::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<vec_t>::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<vec_t>::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<vec_t>::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) | |||
*/ |
@@ -0,0 +1,307 @@ | |||
#pragma once | |||
#include <algorithm> | |||
#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 TBase> | |||
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<T, numTriangle, numModOutputs>; | |||
typename osc::State modulatorState; | |||
typename osc::Params modulatorParams; | |||
StateVariableFilterState<T> filterStates[numFilters]; | |||
StateVariableFilterParams<T> filterParams[numFilters]; | |||
std::shared_ptr<LookupTableParams<T>> expLookup; | |||
// We need a bunch of scalers to convert knob, CV, trim into the voltage | |||
// range each parameter needs. | |||
AudioMath::ScaleFun<T> scale0_1; | |||
AudioMath::ScaleFun<T> scalem2_2; | |||
AudioMath::ScaleFun<T> scaleQ; | |||
AudioMath::ScaleFun<T> scalen5_5; | |||
}; | |||
template <class TBase> | |||
inline void VocalAnimator<TBase>::init() | |||
{ | |||
for (int i = 0; i < numFilters; ++i) { | |||
filterParams[i].setMode(StateVariableFilterParams<T>::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<T>::getExp2(); | |||
} | |||
template <class TBase> | |||
inline void VocalAnimator<TBase>::step() | |||
{ | |||
const bool bass = TBase::params[BASS_EXP_PARAM].value > .5; | |||
const auto mode = bass ? | |||
StateVariableFilterParams<T>::Mode::LowPass : | |||
StateVariableFilterParams<T>::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<T>::lookup(*expLookup, logFreq, true) * reciprocalSampleRate; | |||
normFreq = std::min(normFreq, T(.2)); | |||
normalizedFilterFreq[i] = normFreq; | |||
filterParams[i].setFreq(normFreq); | |||
filterParams[i].setNormalizedBandwidth(filterNormalizedBandwidth); | |||
filterMix += StateVariableFilter<T>::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); | |||
} |
@@ -0,0 +1,212 @@ | |||
#pragma once | |||
#include <algorithm> | |||
#include <cmath> | |||
#include "AudioMath.h" | |||
#include "FormantTables2.h" | |||
#include "LookupTable.h" | |||
#include "LookupTableFactory.h" | |||
#include "ObjectCache.h" | |||
#include "StateVariableFilter.h" | |||
/** | |||
* | |||
*/ | |||
template <class TBase> | |||
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<T> filterStates[numFilters]; | |||
StateVariableFilterParams<T> filterParams[numFilters]; | |||
FormantTables2 formantTables; | |||
std::shared_ptr<LookupTableParams<T>> expLookup; | |||
std::shared_ptr<LookupTableParams<T>> db2GainLookup; | |||
AudioMath::ScaleFun<T> scaleCV_to_formant; | |||
AudioMath::ScaleFun<T> scaleQ; | |||
AudioMath::ScaleFun<T> scaleFc; | |||
AudioMath::ScaleFun<T> scaleBrightness; | |||
}; | |||
template <class TBase> | |||
inline void VocalFilter<TBase>::init() | |||
{ | |||
for (int i = 0; i < numFilters; ++i) { | |||
filterParams[i].setMode(StateVariableFilterParams<T>::Mode::BandPass); | |||
filterParams[i].setQ(15); // or should it be 5? | |||
filterParams[i].setFreq(T(.1)); | |||
} | |||
scaleCV_to_formant = AudioMath::makeLinearScaler<T>(0, formantTables.numVowels - 1); | |||
scaleFc = AudioMath::makeLinearScaler<T>(-2, 2); | |||
scaleBrightness = AudioMath::makeLinearScaler<T>(0, 1); | |||
AudioMath::ScaleFun<T> rawQKnob = AudioMath::makeLinearScaler<T>(-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<T>::getExp2(); | |||
db2GainLookup = ObjectCache<T>::getDb2Gain(); | |||
} | |||
template <class TBase> | |||
inline void VocalFilter<TBase>::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<T>::lookup(*db2GainLookup, modifiedGainDB) * normalizedBw; | |||
T fcFinalLog = fcLog + fPara; | |||
T fcFinal = LookupTable<T>::lookup(*expLookup, fcFinalLog); | |||
filterParams[i].setFreq(fcFinal * reciprocalSampleRate); | |||
filterParams[i].setNormalizedBandwidth(normalizedBw); | |||
filterMix += gain * StateVariableFilter<T>::run(input, filterStates[i], filterParams[i]); | |||
} | |||
TBase::outputs[AUDIO_OUTPUT].value = 3 * filterMix; | |||
} |
@@ -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<Input>& inputs; | |||
std::vector<Output>& outputs; | |||
std::vector<Param>& params; | |||
std::vector<Light>& lights; | |||
}; |
@@ -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). |
@@ -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 <a name="chopper"></a> | |||
In its simplest use, Chopper produces a very wide range of **tremolo** effects. The built-in LFO can produce a wide range of waveforms that cover many of the waveforms produced by the tremolo circuits built into **vintage guitar amplifiers**. | |||
The LFO is sent to an output so that it may modulate other modules. | |||
There is also a **clock synchronizer** and multiplier. | |||
To use Chopper as a tremolo, send a signal to the *in* jack, and listen to the *out* jack. Leave the *clock* control at the default *int* setting. Most of the knob settings will now affect the tremolo effect. | |||
## Chopper LFO | |||
 | |||
To understand all the LFO settings, it helps to watch the outputs on a scope. | |||
The LFO starts as **skewed** sawtooth. In the middle position it is a symmetric triangle wave, at one end a positive sawtooth and at the other a negative sawtooth. The signal is sent to the **saw** output. | |||
The skewed saw then goes to a **waveshaper**. As the shape control is increased the LFO is gradually rounded and then flattened. The shaped LFO is send to the *lfo* output, and used internally to modulate the audio input. | |||
LFO Controls: | |||
* **Shape** Flattens the LFO waveform. | |||
* **Skew** Dials in the amount of asymmetry in the LFO. | |||
* **Depth** Shifts and scales the LFO. | |||
When used as a tremolo effect, you will hear **more tremolo** when these controls are turned up. | |||
## Chopper clock | |||
The LFO in Chopper may be synchronized with the ckin signal. There is a built-in **clock multiplier**. To use the synchronization, patch a clock to the ckin, and select x1 from the **clock** knob. To run at a multiple of the input clock, select x2, x3, or x4. | |||
When Chopper is being synched, the **Phase** control sets the phase difference between the external clock and the synchronized LFO. This may be used to "dial in" the tremolo so that it sounds exactly on the beat (or off the beat). | |||
There is also an internal LFO that is controlled by the **Rate** control. Set the clock control to *int* to use the internal clock. | |||
# Thread Booster<a name="booster"></a> | |||
Thread booster raises the priority of VCV Rack's audio rendering thread. In many cases this decreases the annoying pops, ticks, and dropouts that many users are experiencing. | |||
Many users have reported that Thread Booster helps significantly. Others have reported that it does not help at all. No one has reported a detrimental effect. | |||
For a deeper dive into the Thread Booster, you should read [this document](./thread-booster.md). | |||
Thread Booster has a UI that lets you boost the priority of the audio thread. There are three arbitrary settings: normal, boosted, and real time. When the switch is in the bottom position, the plugin does nothing; the audio thread keeps its default priority. In the boost (middle) position, it sets the thread priority to the highest priority non-real-time setting. In the real-time position it attempts to set it to the highest possible priority, or near it. | |||
If setting the priority fails, the red error light lights up, and the priority stays where it was last. | |||
To use Thread Booster, just insert an instance into VCV Rack, then adjust the boost switch. In general we recommend the "real time" setting, if it is available on your computer. | |||
Once Thread booster is in your session, it will boost all the audio processing - it doesn't matter if other modules are added before or after - they all get boosted. | |||
Linux users - you must read [the detailed document](./thread-booster.md) to use this module. | |||
Note to users who downloaded the original version of Thread Booster: we've improved it a bit since then, especially on Linux and Windows. | |||
# Colors variable slope noise generator<a name="colors"></a> | |||
 | |||
Colors is a colored noise generator. It can generate all the common **"colors"** of noise, including white, pink, red, blue, and violet. It can also produce all the colors in between, as it has a **continuously variable slope**. | |||
Colors has a single control, "slope." This is the slope of the noise spectrum, from -8 dB/octave to +8 dB/octave. | |||
The slope of the noise is quite accurate in the mid-band, but at the extremes we flatten the slope to keep from boosting super-low frequencies too much, and to avoid putting out enormous amounts of highs. So the slope is flat below 40hz, and above 6kHz. | |||
## Things to be aware of | |||
When the **slope** changes, Color needs to do a lot of calculations. While this is normally not a problem, it’s possible that quickly changing the slope of many instances of Colors could cause pops and dropouts. | |||
The slope control does not respond instantly. If you turn the knob, you will hear the change, but if you were to modulate the CV very quickly you might notice the slowness. | |||
# Growler | |||
 | |||
**Growler** is a re-creation of the Vocal Animator circuit invented by Bernie Hutchins, and published in Electronotes magazine in the late 70's. It continuously morphs between different vaguely voice like tones. | |||
**To get a good sound:** run any harmonically rich signal into the input, and something good will come out. Low frequency pulse waves and distorted sounds make great input. | |||
The controls do pretty much what you would expect: | |||
* **LFO** controls the speed of the modulation LFOs. | |||
* **Fc** controls the average frequency of the multiple filters. | |||
* **Q** controls the sharpness of the filters. | |||
* **Depth** controls how much of the modulation LFOs are applied to the filters. | |||
## How Growler works | |||
 | |||
There are four **bandpass filters**, roughly tuned to some typical vocal formant frequencies: 522, 1340, 2570, and 3700 Hz. The filters are run in parallel, with their outputs summed together. | |||
The first three filter frequencies are modulated by an LFO comprised of **4 triangle wave LFOs** running at different frequencies. They are summed together in various combinations to drive each of the filters. | |||
Each **CV input stage** is the same: a knob that supplies a fixed offset and a CV input that is processed by an attenuverter. The processed CV is added to the knob voltage. See below for more on [Attenuverters](#atten) and [CV ranges](#cv). | |||
The **LFO** Rate control shifts the speed of all 4 LFOs while maintaining the ratio of their frequencies. | |||
The **Fc** control moves the frequencies of the first three filters, but not by equal amounts. The lowest filter moves at 1V/Oct, but the middle two move less. The top filter is fixed at 3700 Hz. | |||
The **Q** control does just what it says - controls the Q (resonance) of the filters. | |||
The **Modulation Depth** controls how much of the summed LFOs get to each filter. Again, the lower filters move farther, and the top filter is fixed. | |||
The smaller knobs next to the main knobs are **attenuverters**, which scale control voltages. For more on attenuverters, [see below](#atten) | |||
There are three LFO outputs next to the blinking LFOs. These may be used to modulate other modules, or as semi-random voltage sources. | |||
**Bass boost** switch. When it’s in the up position (on) there should be more bass. This is done by switching some or all of the filters from bandpass to lowpass. | |||
LFO **Matrix** switch. This is the unlabeled switch in the LFO section. When it’s down (default position) the LFOs are closely correlated. In the middle we try to make them a little bit more independent. When it’s in the up position the LFOs will often go in different directions. | |||
# Booty Shifter frequency shifter <a name="shifter"></a> | |||
**Booty Shifter** is a frequency shifter inspired by the Moog/Bode frequency shifter module. | |||
 | |||
The name "Booty Shifter" is a nod to the classic analog module, as well as to a black cat named Booty. | |||
Booty Shifter will take an audio input and shift the frequencies up or down. This is not like a pitch shift where harmonics will remain in tune; it is an absolute frequency shift in Hz, so in general **harmonics will go way out of tune.** It is similar to a ring-modulator, but less extreme and more versatile. | |||
## Getting good sounds from Booty Shifter | |||
Feed in music and shift the frequency a good amount. | |||
Feed in **speech or radio** and shift it. | |||
Feed the CV from a **sequencer** to sequence the mayhem. | |||
Shift **drums** up or down a little bit to re-tune them without the usual pitch-shifting artifacts. | |||
Small shifts in conjunction with delays can make a chorus-like effect to thicken music. | |||
## Inputs and outputs | |||
* **IN** is the audio input. | |||
* **CV** is the pitch shift control voltage. -5V will give minimum shift, +5 will give maximum. | |||
* **DN** is the down-shifted output. | |||
* **UP** is the up-shifted output. | |||
## Controls | |||
**RANGE** sets the total shift range in Hz. For example, the 50 Hz setting means that the minimum shift is 50 Hz down, and the maximum is 50 Hz up. | |||
Range value **Exp is different**. Here minimum shift is 2 Hz, maximum is 2 kHz, with an exponential response. As of version 0.6.2 the response is an accurate 1 Volt per Octave. | |||
Shift **AMT** is added to the control voltage, with a range of -5..5. | |||
## Oddities and limitations | |||
If you shift the frequency up too far, it will alias. There is no anti-aliasing, so if the highest input frequency + shift amount > sample_rate / 2, you will get aliasing. Of course the Bode analog original did not alias. | |||
If you shift the input down a lot, frequencies will go **below zero and wrap around**. Taken far enough this will completely **reverse the spectrum** of the input. This was a prized feature of the Bode original. | |||
As you shift the input down, you may start to generate a lot of subsonic energy. A **High Pass filter** may clean this up. | |||
The down shift **frequency fold-over**, while true to the original, does cause problems when trying to pitch drum tracks down a lot. High pass filtering the input before it is down-shifted can control this. | |||
# Formants vocal filter <a name="formants"></a> | |||
 | |||
Like the **Vocal Animator**, this is a filter bank tuned to the formant frequencies of typical **singing voices**. Unlike Growler, however, the filters do not animate on their own. In addition, the filters are preset to frequencies, bandwidths, and gains that are taken from **measurements of human singers**. | |||
One of the easiest ways to **get a good sound** from Formants is to use it like a regular VCF. For example, control Fc with an ADSR. Then put a second modulation source into the vowel CV - something as simple as a slow LFO will add interest. | |||
Use it as a **filter bank**. Just set the knobs for a good sound and leave it fixed to add vocal tones to a pad. Again, modulating the vowel CV can easily give great results. | |||
Try to synthesize something like **singing** by sequencing the vowel CV of several formants. Leave the Fc in place, or move it slightly as the input pitches move. | |||
Controls: | |||
* **Fc** control moves all the filters up and down by the standard one "volt" per octave. | |||
* **Vowel** control smoothly interpolates between 'a', 'e', 'i', 'o', and 'u'. | |||
* **Model** control selects different vocal models: bass, tenor, countertenor, alto, and soprano. | |||
* **Brightness** control gradually boosts the level of the higher formants. When it is all the way down, the filter gains are set by the singing models in the module, which typically fall off with increasing frequency. As this control is increased the gain of the high formant filters is brought up to match the F1 formant filter. | |||
The **LEDs across the top** indicate which formant is currently being "sung". | |||
## About Attenuverters <a name="atten"></a> | |||
The small knobs next to the bigger knobs are **attenuverters**. They scale and/or invert the control voltage inputs next to them. When they are turned all the way up the full CV comes through. As they are turned down less CV comes through. Straight up none passes. As they are turned down further the CV comes back, but inverted. | |||
Sometimes we use attenuverters with a *linear taper*, and sometimes we use an *audio taper*. If you find that on a particular module you do not like the response of the attenuverters, please log a github issue. | |||
## Control voltage ranges <a name="cv"></a> | |||
Our modules usually expect a control voltage range of **-5 to +5**. The associated offset knobs will also add -5 to +5. After attenuverters are applied to CV the knob value is added. After all that, the result is usually clipped to the -5 to +5 range. |
@@ -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 TBase> | |||
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<TestComposite>; | |||
``` | |||
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<TestWidget> -> enums | |||
``` | |||
``` | |||
ModuleWidget -> I/O -> Module | |||
| | |||
^ | |||
/ \ | |||
--- | |||
| | |||
FrequencyShifter<ModuleWidget> -> 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. |
@@ -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. |
@@ -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/ | |||
<other plugins> | |||
squinkylabs-plug1/ | |||
plugin.dll | |||
plugin.dylib (optional, unused) | |||
plugin.so (optional, unused) | |||
LICENSE | |||
res/ | |||
<one or more svg files> | |||
<possibly other resources> | |||
``` | |||
The plugins will be under the **Rack** folder. Rack folders are here: | |||
* MacOS: Documents/Rack/ | |||
* Windows: My Documents/Rack/ | |||
* Linux: ~/.Rack/ |
@@ -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 <path to Rack> | |||
``` | |||
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. |
@@ -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. |
@@ -0,0 +1,201 @@ | |||
#include "AudioMath.h" | |||
#include "FFT.h" | |||
#include "FFTData.h" | |||
#include <assert.h> | |||
#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<kiss_fftr_cfg>(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<kiss_fft_cpx *>(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<kiss_fftr_cfg>(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<const kiss_fft_cpx *>(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); | |||
} | |||
} |
@@ -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); | |||
}; |
@@ -0,0 +1,82 @@ | |||
#include "ColoredNoise.h" | |||
#include "FFTCrossFader.h" | |||
#include <assert.h> | |||
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; | |||
} |
@@ -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); | |||
}; |
@@ -0,0 +1,67 @@ | |||
#include "FFTData.h" | |||
#include "kiss_fft.h" | |||
#include "kiss_fftr.h" | |||
#include <assert.h> | |||
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; | |||
} |
@@ -0,0 +1,70 @@ | |||
#pragma once | |||
#include <complex> | |||
#include <vector> | |||
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<float>; | |||
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<cpx> 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<float> 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; | |||
}; |
@@ -0,0 +1,75 @@ | |||
#pragma once | |||
template<typename T, int N> class BiquadState; | |||
template<typename T, int N> 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<typename T> | |||
class BiquadFilter | |||
{ | |||
public: | |||
BiquadFilter() = delete; // we are only static | |||
template<int N> | |||
static T run(T input, BiquadState<T, N>& state, const BiquadParams<T, N>& params); | |||
/** | |||
* Translate filter coefficients from Dsp:: conventions to DspParam structure | |||
*/ | |||
template<int N> | |||
static void fillFromStages(BiquadParams<T, N>& params, Dsp::Cascade::Stage * stages, int numStages); | |||
}; | |||
template <typename T> | |||
template<int N> | |||
inline void BiquadFilter<T>::fillFromStages(BiquadParams<T, N>& 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<typename T> | |||
template<int N> | |||
inline T BiquadFilter<T>::run(T input, BiquadState<T, N>& state, const BiquadParams<T, N>& 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; | |||
} |
@@ -0,0 +1,137 @@ | |||
#pragma once | |||
#include <assert.h> | |||
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 <typename T, int N> | |||
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 <typename T, int N> | |||
inline void BiquadParams<T, N>::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 <typename T, int N> | |||
inline BiquadParams<T, N>::BiquadParams() | |||
{ | |||
assert(N > 0); | |||
for (int i = 0; i < N * 5; ++i) { | |||
_taps[i] = 0; | |||
} | |||
++_numBiquads; | |||
} | |||
template <typename T, int N> | |||
inline BiquadParams<T, N>::~BiquadParams() | |||
{ | |||
--_numBiquads; | |||
} | |||
template <typename T, int N> | |||
inline T& BiquadParams<T, N>::B0(int stage) | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5]; | |||
} | |||
template <typename T, int N> | |||
inline T& BiquadParams<T, N>::B1(int stage) | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5 + 1]; | |||
} | |||
template <typename T, int N> | |||
inline T& BiquadParams<T, N>::B2(int stage) | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5 + 2]; | |||
} | |||
template <typename T, int N> | |||
T BiquadParams<T, N>::A1(int stage) const | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5 + 3]; | |||
} | |||
template <typename T, int N> | |||
T BiquadParams<T, N>::A2(int stage) const | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5 + 4]; | |||
} | |||
template <typename T, int N> | |||
T BiquadParams<T, N>::B0(int stage) const | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5]; | |||
} | |||
template <typename T, int N> | |||
T BiquadParams<T, N>::B1(int stage) const | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5 + 1]; | |||
} | |||
template <typename T, int N> | |||
T BiquadParams<T, N>::B2(int stage) const | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5 + 2]; | |||
} | |||
template <typename T, int N> | |||
T& BiquadParams<T, N>::A1(int stage) | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5 + 3]; | |||
} | |||
template <typename T, int N> | |||
T& BiquadParams<T, N>::A2(int stage) | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _taps[stage * 5 + 4]; | |||
} |
@@ -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 <typename T, int N> | |||
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 <typename T, int N> | |||
inline BiquadState<T, N>::BiquadState() | |||
{ | |||
assert(N > 0); | |||
for (int i = 0; i < N * 2; ++i) { | |||
_state[i] = 0; | |||
} | |||
_numBiquads++; | |||
} | |||
template <typename T, int N> | |||
inline BiquadState<T, N>::~BiquadState() | |||
{ | |||
_numBiquads--; | |||
} | |||
template <typename T, int N> | |||
inline T& BiquadState<T, N>::z0(int stage) | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _state[stage * 2]; | |||
} | |||
template <typename T, int N> | |||
inline T& BiquadState<T, N>::z1(int stage) | |||
{ | |||
assert(stage >= 0 && stage < N); | |||
return _state[stage * 2 + 1]; | |||
} |
@@ -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 <memory> | |||
template <typename T> | |||
void ButterworthFilterDesigner<T>::designThreePoleLowpass(BiquadParams<T, 2>& outParams, T frequency) | |||
{ | |||
using Filter = Dsp::ButterLowPass<3, 1>; | |||
std::unique_ptr<Filter> lp3(new Filter()); // std::make_unique is not until C++14 | |||
lp3->SetupAs(frequency); | |||
assert(lp3->GetStageCount() == 2); | |||
BiquadFilter<T>::fillFromStages(outParams, lp3->Stages(), lp3->GetStageCount()); | |||
} | |||
template <typename T> | |||
void ButterworthFilterDesigner<T>::designTwoPoleLowpass(BiquadParams<T, 1>& outParams, T frequency) | |||
{ | |||
using Filter = Dsp::ButterLowPass<2, 1>; | |||
std::unique_ptr<Filter> lp2(new Filter()); | |||
lp2->SetupAs(frequency); | |||
assert(lp2->GetStageCount() == 1); | |||
BiquadFilter<T>::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<double>; | |||
template class ButterworthFilterDesigner<float>; |
@@ -0,0 +1,14 @@ | |||
#pragma once | |||
template<typename, int> | |||
class BiquadParams; | |||
template <typename T> | |||
class ButterworthFilterDesigner | |||
{ | |||
public: | |||
ButterworthFilterDesigner() = delete; // we are only static | |||
static void designThreePoleLowpass(BiquadParams<T, 2>& pOut, T frequency); | |||
static void designTwoPoleLowpass(BiquadParams<T, 1>& pOut, T frequency); | |||
}; |
@@ -0,0 +1,283 @@ | |||
#include <assert.h> | |||
#include <cmath> | |||
#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<float>& 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<float>::initDiscrete(fparams, numVowels, temp); | |||
// Init Wb lookups with normalized bw ( f2-f1 / fc) | |||
LookupTableParams<float>& bwparams = bwInterpolators[model][formantBand]; | |||
const float *bwValues = bwLookup[model][formantBand]; | |||
for (int vowel = 0; vowel < numVowels; ++vowel) { | |||
temp[vowel] = bwValues[vowel] / freqValues[vowel]; | |||
} | |||
LookupTable<float>::initDiscrete(bwparams, numVowels, temp); | |||
LookupTableParams<float>& 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<float>::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<float>& params = freqInterpolators[model][formantBand]; | |||
return LookupTable<float>::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<float>& params = bwInterpolators[model][formantBand]; | |||
return LookupTable<float>::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<float>& params = gainInterpolators[model][formantBand]; | |||
return LookupTable<float>::lookup(params, vowel); | |||
} |
@@ -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<float> freqInterpolators[numModels][numFormantBands]; | |||
LookupTableParams<float> bwInterpolators[numModels][numFormantBands]; | |||
LookupTableParams<float> gainInterpolators[numModels][numFormantBands]; | |||
}; |
@@ -0,0 +1,220 @@ | |||
/** | |||
* HilbertFilterDesigner | |||
* generate a pair of Hilbert filters | |||
*/ | |||
#include "HilbertFilterDesigner.h" | |||
#include "DspFilter.h" | |||
#include "BiquadFilter.h" | |||
#include <memory> | |||
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<Hilb, LowPass, hilbertOrder, 1> | |||
{ | |||
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<Hilb, LowPass, hilbertOrder, 1>::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<HilbTest, LowPass, hilbertOrderTest, 1> | |||
{ | |||
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<HilbTest, LowPass, hilbertOrderTest, 1>::Setup(spec); | |||
} | |||
}; | |||
#endif | |||
} | |||
template <typename T> | |||
void HilbertFilterDesigner<T>::design(double sampleRate, BiquadParams<T, 3>& outSin, BiquadParams<T, 3>& outCos) | |||
{ | |||
Dsp::Hilbert hilbert; | |||
hilbert.SetupAs(false, sampleRate); | |||
BiquadFilter<T>::fillFromStages(outSin, hilbert.Stages(), hilbert.GetStageCount()); | |||
hilbert.SetupAs(true, sampleRate); | |||
BiquadFilter<T>::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<double>; | |||
template class HilbertFilterDesigner<float>; | |||
@@ -0,0 +1,15 @@ | |||
#pragma once | |||
template<typename, int> | |||
class BiquadParams; | |||
template<typename T> | |||
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<T, 3>& pOutSin, BiquadParams<T, 3>& pOutCos); | |||
}; |
@@ -0,0 +1,156 @@ | |||
#pragma once | |||
#include "AudioMath.h" | |||
template <typename T> class StateVariableFilterState; | |||
template <typename T> class StateVariableFilterParams; | |||
/** | |||
* | |||
* |-----------------------------------------------------> hi pass | |||
* | |->Band (+)----->Notch | |||
* | | | | |||
* input ->( + )------|---> Fc >--(+)-|-> Z**-1 >-|-> Fc >->(+)------>-.--|--> LowPass | |||
* |(-1) | (-1) | | | | | | |||
* | | |-<--------<---| |-<Z**-1<-| | | |||
* | | | | | |||
* | -<-------------------------< Qc <---- | | |||
* | | | |||
* |---<-----------------------------------------------------------<-- | |||
* | |||
* | |||
* | |||
* | |||
* | |||
* | |||
* | |||
*/ | |||
template <typename T> | |||
class StateVariableFilter | |||
{ | |||
public: | |||
StateVariableFilter() = delete; // we are only static | |||
static T run(T input, StateVariableFilterState<T>& state, const StateVariableFilterParams<T>& params); | |||
}; | |||
template <typename T> | |||
inline T StateVariableFilter<T>::run(T input, StateVariableFilterState<T>& state, const StateVariableFilterParams<T>& 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<T>::Mode::LowPass: | |||
d = dLow; | |||
break; | |||
case StateVariableFilterParams<T>::Mode::HiPass: | |||
d = dHi; | |||
break; | |||
case StateVariableFilterParams<T>::Mode::BandPass: | |||
d = dBand; | |||
break; | |||
case StateVariableFilterParams<T>::Mode::Notch: | |||
d = dLow + dHi; | |||
break; | |||
default: | |||
assert(false); | |||
d = 0.0; | |||
} | |||
state.z1 = dBand; | |||
state.z2 = dLow; | |||
return d; | |||
} | |||
/****************************************************************/ | |||
template <typename T> | |||
class StateVariableFilterParams | |||
{ | |||
public: | |||
friend StateVariableFilter<T>; | |||
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 <typename T> | |||
inline void StateVariableFilterParams<T>::setQ(T q) | |||
{ | |||
if (q < .49) { | |||
assert(false); | |||
q = T(.6); | |||
} | |||
qGain = 1 / q; | |||
} | |||
template <typename T> | |||
inline void StateVariableFilterParams<T>::setNormalizedBandwidth(T bw) | |||
{ | |||
qGain = bw; | |||
} | |||
template <typename T> | |||
inline void StateVariableFilterParams<T>::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 <typename T> | |||
class StateVariableFilterState | |||
{ | |||
public: | |||
T z1 = 0; // the delay line buffer | |||
T z2 = 0; // the delay line buffer | |||
}; |
@@ -0,0 +1,128 @@ | |||
#pragma once | |||
#include <cmath> | |||
#include "SawOscillator.h" | |||
/** | |||
* A bunch of LFOs at slightly different frequencies added together in different ways. | |||
* Taken from Bernie Hutchins' ElectroNotes. | |||
*/ | |||
template <typename T, int NOsc, int NOut> | |||
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<T> 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<T> params[NOsc]; | |||
int matrixMode = 0; | |||
}; | |||
static void run(T * output, State&, const Params&); | |||
}; | |||
template <typename T, int NOsc, int NOut> | |||
inline MultiModOsc<T, NOsc, NOut>::Params::Params() | |||
{ | |||
setRateAndSpread(.5, .5, 0, T(1.0 / 44100)); | |||
} | |||
template <typename T, int NOsc, int NOut> | |||
inline void MultiModOsc<T, NOsc, NOut>::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<T, false>::setFrequency(params[i], actual); | |||
this->matrixMode = inMatrixMode; | |||
} | |||
} | |||
template <typename T, int NOsc, int NOut> | |||
inline void MultiModOsc<T, NOsc, NOut>::run(T* output, State& state, const Params& params) | |||
{ | |||
T modulators[NOsc]; | |||
for (int i = 0; i < NOsc; ++i) { | |||
modulators[i] = SawOscillator<T, false>::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 | |||
} | |||
} |
@@ -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<typename T> class SawOscillatorParams; | |||
template<typename T> class SawOscillatorState; | |||
template<typename T, bool frequencyCanBeNegative> | |||
class SawOscillator | |||
{ | |||
public: | |||
SawOscillator() = delete; // we are only static | |||
static void setFrequency(SawOscillatorParams<T>& params, T freq); // TODO: so we want setters on params? | |||
// Generates a saw wave from 0..1 | |||
static T runSaw(SawOscillatorState<T>& state, const SawOscillatorParams<T>& params); | |||
// Generates a triangle wave from -1..1 | |||
static T runTri(SawOscillatorState<T>& state, const SawOscillatorParams<T>& params); | |||
/** | |||
* gets the regular output and the quadrature output | |||
*/ | |||
static void runQuadrature(T& out, T& outQuad, SawOscillatorState<T>& state, const SawOscillatorParams<T>& params); | |||
}; | |||
template<typename T, bool frequencyCanBeNegative> | |||
inline T SawOscillator<T, frequencyCanBeNegative>::runSaw(SawOscillatorState<T>& state, const SawOscillatorParams<T>& 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<typename T, bool frequencyCanBeNegative> | |||
inline T SawOscillator<T, frequencyCanBeNegative>::runTri(SawOscillatorState<T>& state, const SawOscillatorParams<T>& 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<typename T, bool frequencyCanBeNegative> | |||
inline void SawOscillator<T, frequencyCanBeNegative>::runQuadrature(T& out, T& outQuad, SawOscillatorState<T>& state, const SawOscillatorParams<T>& params) | |||
{ | |||
out = runSaw(state, params); | |||
T quad = out + T(.25); | |||
if (quad >= 1) { | |||
quad -= 1; | |||
} | |||
outQuad = quad; | |||
} | |||
template<typename T, bool frequencyCanBeNegative> | |||
inline void SawOscillator<T, frequencyCanBeNegative>::setFrequency(SawOscillatorParams<T>& params, T freq) | |||
{ | |||
if (frequencyCanBeNegative) { | |||
assert(freq >= -.5 && freq < .5); | |||
} else { | |||
assert(freq >= 0 && freq < .5); | |||
} | |||
params.phaseIncrement = freq; | |||
} | |||
template<typename T> | |||
class SawOscillatorParams | |||
{ | |||
public: | |||
T phaseIncrement = 0; | |||
}; | |||
template<typename T> | |||
class SawOscillatorState | |||
{ | |||
public: | |||
/** | |||
* phase increments from 0...1 | |||
*/ | |||
T phase = 0; | |||
}; |
@@ -0,0 +1,87 @@ | |||
#pragma once | |||
#include "AudioMath.h" | |||
#include "LookupTable.h" | |||
#include "ObjectCache.h" | |||
#include "SawOscillator.h" | |||
template<typename T> class SinOscillatorParams; | |||
template<typename T> 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<typename T, bool frequencyCanBeNegative> | |||
class SinOscillator | |||
{ | |||
public: | |||
SinOscillator() = delete; // we are only static | |||
static void setFrequency(SinOscillatorParams<T>&, T frequency); | |||
static T run(SinOscillatorState<T>&, const SinOscillatorParams<T>&); | |||
static void runQuadrature(T& output, T& outputQuadrature, SinOscillatorState<T>&, const SinOscillatorParams<T>&); | |||
}; | |||
template<typename T, bool frequencyCanBeNegative> | |||
inline void SinOscillator<T, frequencyCanBeNegative>::setFrequency(SinOscillatorParams<T>& params, T frequency) | |||
{ | |||
std::function<double(double)> f = AudioMath::makeFunc_Sin(); | |||
// TODO: figure out a better initialization strategy | |||
// and a better strategy for table size | |||
// with 4096 thd was -130 db. let's use less memory! | |||
// if (!params.lookupParams.isValid()) { | |||
// LookupTable<T>::init(params.lookupParams, 256, 0, 1, f); | |||
// } | |||
assert(params.lookupParams->isValid()); | |||
SawOscillator<T, true>::setFrequency(params.sawParams, frequency); | |||
} | |||
template<typename T, bool frequencyCanBeNegative> | |||
inline T SinOscillator<T, frequencyCanBeNegative>::run( | |||
SinOscillatorState<T>& state, const SinOscillatorParams<T>& params) | |||
{ | |||
const T temp = SawOscillator<T, frequencyCanBeNegative>::runSaw(state.sawState, params.sawParams); | |||
const T ret = LookupTable<T>::lookup(*params.lookupParams, temp); | |||
return ret; | |||
} | |||
template<typename T, bool frequencyCanBeNegative> | |||
inline void SinOscillator<T, frequencyCanBeNegative>::runQuadrature( | |||
T& output, T& outputQuadrature, SinOscillatorState<T>& state, const SinOscillatorParams<T>& params) | |||
{ | |||
T saw, quadratureSaw; | |||
SawOscillator<T, frequencyCanBeNegative>::runQuadrature(saw, quadratureSaw, state.sawState, params.sawParams); | |||
output = LookupTable<T>::lookup(*params.lookupParams, saw); | |||
outputQuadrature = LookupTable<T>::lookup(*params.lookupParams, quadratureSaw); | |||
}; | |||
template<typename T> | |||
class SinOscillatorParams | |||
{ | |||
public: | |||
SawOscillatorParams<T> sawParams; | |||
// LookupTableParams<T> lookupParams; | |||
std::shared_ptr<LookupTableParams<T>> lookupParams; | |||
SinOscillatorParams() | |||
{ | |||
lookupParams = ObjectCache<T>::getSinLookup(); | |||
} | |||
SinOscillatorParams(const SinOscillatorParams&) = delete; | |||
}; | |||
template<typename T> | |||
class SinOscillatorState | |||
{ | |||
public: | |||
SawOscillatorState<T> sawState; | |||
}; |
@@ -0,0 +1,4 @@ | |||
repo: d844c818f599aea64fe86745cdd2ef9b3d1910dc | |||
node: b354a59534b0a77c43c67deb1eb1bc39eb99b487 | |||
branch: default | |||
tag: v130 |
@@ -0,0 +1,10 @@ | |||
syntax:glob | |||
test/bm_* | |||
test/st_* | |||
test/tkfc_* | |||
test/tr_* | |||
tools/fastconv_* | |||
tools/fastconvr_* | |||
tools/fft_* | |||
*.swp | |||
*~ |
@@ -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 |
@@ -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 |
@@ -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. |
@@ -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 |
@@ -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 |
@@ -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<pDestEnd) | |||
{ | |||
*pDest=_mm_set_ps(*source3,*source2,*source1,*source0); | |||
source0++; | |||
source1++; | |||
source2++; | |||
source3++; | |||
pDest++; | |||
} | |||
} | |||
void SSETools::unpack128(float* target, float* source, unsigned long size128) | |||
{ | |||
float* pSrc = source; | |||
float* pSrcEnd = pSrc+size128*4; | |||
float* target0=target; | |||
float* target1=target0+size128; | |||
float* target2=target1+size128; | |||
float* target3=target2+size128; | |||
while(pSrc<pSrcEnd) | |||
{ | |||
*target0=pSrc[0]; | |||
*target1=pSrc[1]; | |||
*target2=pSrc[2]; | |||
*target3=pSrc[3]; | |||
target0++; | |||
target1++; | |||
target2++; | |||
target3++; | |||
pSrc+=4; | |||
} | |||
} |
@@ -0,0 +1,39 @@ | |||
Speed: | |||
* If you want to use multiple cores, then compile with -openmp or -fopenmp (see your compiler docs). | |||
Realize that larger FFTs will reap more benefit than smaller FFTs. This generally uses more CPU time, but | |||
less wall time. | |||
* experiment with compiler flags | |||
Special thanks to Oscar Lesta. He suggested some compiler flags | |||
for gcc that make a big difference. They shave 10-15% off | |||
execution time on some systems. Try some combination of: | |||
-march=pentiumpro | |||
-ffast-math | |||
-fomit-frame-pointer | |||
* If the input data has no imaginary component, use the kiss_fftr code under tools/. | |||
Real ffts are roughly twice as fast as complex. | |||
* If you can rearrange your code to do 4 FFTs in parallel and you are on a recent Intel or AMD machine, | |||
then you might want to experiment with the USE_SIMD code. See README.simd | |||
Reducing code size: | |||
* remove some of the butterflies. There are currently butterflies optimized for radices | |||
2,3,4,5. It is worth mentioning that you can still use FFT sizes that contain | |||
other factors, they just won't be quite as fast. You can decide for yourself | |||
whether to keep radix 2 or 4. If you do some work in this area, let me | |||
know what you find. | |||
* For platforms where ROM/code space is more plentiful than RAM, | |||
consider creating a hardcoded kiss_fft_state. In other words, decide which | |||
FFT size(s) you want and make a structure with the correct factors and twiddles. | |||
* Frank van der Hulst offered numerous suggestions for smaller code size and correct operation | |||
on embedded targets. "I'm happy to help anyone who is trying to implement KISSFFT on a micro" | |||
Some of these were rolled into the mainline code base: | |||
- using long casts to promote intermediate results of short*short multiplication | |||
- delaying allocation of buffers that are sometimes unused. | |||
In some cases, it may be desirable to limit capability in order to better suit the target: | |||
- predefining the twiddle tables for the desired fft size. |
@@ -0,0 +1,164 @@ | |||
/* | |||
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. | |||
*/ | |||
/* kiss_fft.h | |||
defines kiss_fft_scalar as either short or a float type | |||
and defines | |||
typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */ | |||
#include "kiss_fft.h" | |||
#include <limits.h> | |||
#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 <alloca.h> | |||
#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 |
@@ -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; u<m; ++u ) { | |||
C_FIXDIV( *Fout0,5); C_FIXDIV( *Fout1,5); C_FIXDIV( *Fout2,5); C_FIXDIV( *Fout3,5); C_FIXDIV( *Fout4,5); | |||
scratch[0] = *Fout0; | |||
C_MUL(scratch[1] ,*Fout1, tw[u*fstride]); | |||
C_MUL(scratch[2] ,*Fout2, tw[2*u*fstride]); | |||
C_MUL(scratch[3] ,*Fout3, tw[3*u*fstride]); | |||
C_MUL(scratch[4] ,*Fout4, tw[4*u*fstride]); | |||
C_ADD( scratch[7],scratch[1],scratch[4]); | |||
C_SUB( scratch[10],scratch[1],scratch[4]); | |||
C_ADD( scratch[8],scratch[2],scratch[3]); | |||
C_SUB( scratch[9],scratch[2],scratch[3]); | |||
Fout0->r += 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<m; ++u ) { | |||
k=u; | |||
for ( q1=0 ; q1<p ; ++q1 ) { | |||
scratch[q1] = Fout[ k ]; | |||
C_FIXDIV(scratch[q1],p); | |||
k += m; | |||
} | |||
k=u; | |||
for ( q1=0 ; q1<p ; ++q1 ) { | |||
int twidx=0; | |||
Fout[ k ] = scratch[0]; | |||
for (q=1;q<p;++q ) { | |||
twidx += (int)(fstride * k); | |||
if (twidx>=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<p;++k) | |||
kf_work( Fout +k*m, f+ fstride*in_stride*k,fstride*p,in_stride,factors,st); | |||
// all threads have joined by this point | |||
switch (p) { | |||
case 2: kf_bfly2(Fout,fstride,st,m); break; | |||
case 3: kf_bfly3(Fout,fstride,st,m); break; | |||
case 4: kf_bfly4(Fout,fstride,st,m); break; | |||
case 5: kf_bfly5(Fout,fstride,st,m); break; | |||
default: kf_bfly_generic(Fout,fstride,st,m,p); break; | |||
} | |||
return; | |||
} | |||
#endif | |||
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( Fout , f, fstride*p, in_stride, factors,st); | |||
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,st,m); break; | |||
case 3: kf_bfly3(Fout,fstride,st,m); break; | |||
case 4: kf_bfly4(Fout,fstride,st,m); break; | |||
case 5: kf_bfly5(Fout,fstride,st,m); break; | |||
default: kf_bfly_generic(Fout,fstride,st,m,p); break; | |||
} | |||
} | |||
/* facbuf is populated by p1,m1,p2,m2, ... | |||
where | |||
p[i] * m[i] = m[i-1] | |||
m0 = n */ | |||
static | |||
void kf_factor(int n,int * facbuf) | |||
{ | |||
int p=4; | |||
double floor_sqrt; | |||
floor_sqrt = floor( sqrt((double)n) ); | |||
/*factor out powers of 4, powers of 2, then any remaining primes */ | |||
do { | |||
while (n % p) { | |||
switch (p) { | |||
case 4: p = 2; break; | |||
case 2: p = 3; break; | |||
default: p += 2; break; | |||
} | |||
if (p > 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;i<nfft;++i) { | |||
const double pi=3.141592653589793238462643383279502884197169399375105820974944; | |||
double phase = -2*pi*i / nfft; | |||
if (st->inverse) | |||
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; | |||
} |
@@ -0,0 +1,124 @@ | |||
#ifndef KISS_FFT_H | |||
#define KISS_FFT_H | |||
#include <stdlib.h> | |||
#include <stdio.h> | |||
#include <math.h> | |||
#include <string.h> | |||
#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 <xmmintrin.h> | |||
# 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 <sys/types.h> | |||
# 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 |
@@ -0,0 +1,299 @@ | |||
#ifndef KISSFFT_CLASS_HH | |||
#include <complex> | |||
#include <vector> | |||
namespace kissfft_utils { | |||
template <typename T_scalar> | |||
struct traits | |||
{ | |||
typedef T_scalar scalar_type; | |||
typedef std::complex<scalar_type> cpx_type; | |||
void fill_twiddles( std::complex<T_scalar> * dst ,int nfft,bool inverse) | |||
{ | |||
T_scalar phinc = (inverse?2:-2)* acos( (T_scalar) -1) / nfft; | |||
for (int i=0;i<nfft;++i) | |||
dst[i] = exp( std::complex<T_scalar>(0,i*phinc) ); | |||
} | |||
void prepare( | |||
std::vector< std::complex<T_scalar> > & dst, | |||
int nfft,bool inverse, | |||
std::vector<int> & stageRadix, | |||
std::vector<int> & 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<cpx_type> _twiddles; | |||
const cpx_type twiddle(int i) { return _twiddles[i]; } | |||
}; | |||
} | |||
template <typename T_Scalar, | |||
typename T_traits=kissfft_utils::traits<T_Scalar> | |||
> | |||
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;k<m;++k) { | |||
cpx_type t = Fout[m+k] * _traits.twiddle(k*fstride); | |||
Fout[m+k] = Fout[k] - t; | |||
Fout[k] += t; | |||
} | |||
} | |||
void kf_bfly4( cpx_type * Fout, const size_t fstride, const size_t m) | |||
{ | |||
cpx_type scratch[7]; | |||
int negative_if_inverse = _inverse * -2 +1; | |||
for (size_t k=0;k<m;++k) { | |||
scratch[0] = Fout[k+m] * _traits.twiddle(k*fstride); | |||
scratch[1] = Fout[k+2*m] * _traits.twiddle(k*fstride*2); | |||
scratch[2] = Fout[k+3*m] * _traits.twiddle(k*fstride*3); | |||
scratch[5] = Fout[k] - scratch[1]; | |||
Fout[k] += scratch[1]; | |||
scratch[3] = scratch[0] + scratch[2]; | |||
scratch[4] = scratch[0] - scratch[2]; | |||
scratch[4] = cpx_type( scratch[4].imag()*negative_if_inverse , -scratch[4].real()* negative_if_inverse ); | |||
Fout[k+2*m] = Fout[k] - scratch[3]; | |||
Fout[k] += scratch[3]; | |||
Fout[k+m] = scratch[5] + scratch[4]; | |||
Fout[k+3*m] = scratch[5] - scratch[4]; | |||
} | |||
} | |||
void kf_bfly3( cpx_type * Fout, const size_t fstride, const size_t m) | |||
{ | |||
size_t k=m; | |||
const size_t m2 = 2*m; | |||
cpx_type *tw1,*tw2; | |||
cpx_type scratch[5]; | |||
cpx_type epi3; | |||
epi3 = _twiddles[fstride*m]; | |||
tw1=tw2=&_twiddles[0]; | |||
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] = cpx_type( Fout->real() - 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<m; ++u ) { | |||
C_FIXDIV( *Fout0,5); C_FIXDIV( *Fout1,5); C_FIXDIV( *Fout2,5); C_FIXDIV( *Fout3,5); C_FIXDIV( *Fout4,5); | |||
scratch[0] = *Fout0; | |||
C_MUL(scratch[1] ,*Fout1, tw[u*fstride]); | |||
C_MUL(scratch[2] ,*Fout2, tw[2*u*fstride]); | |||
C_MUL(scratch[3] ,*Fout3, tw[3*u*fstride]); | |||
C_MUL(scratch[4] ,*Fout4, tw[4*u*fstride]); | |||
C_ADD( scratch[7],scratch[1],scratch[4]); | |||
C_SUB( scratch[10],scratch[1],scratch[4]); | |||
C_ADD( scratch[8],scratch[2],scratch[3]); | |||
C_SUB( scratch[9],scratch[2],scratch[3]); | |||
C_ADDTO( *Fout0, scratch[7]); | |||
C_ADDTO( *Fout0, scratch[8]); | |||
scratch[5] = scratch[0] + cpx_type( | |||
S_MUL(scratch[7].real(),ya.real() ) + S_MUL(scratch[8].real() ,yb.real() ), | |||
S_MUL(scratch[7].imag(),ya.real()) + S_MUL(scratch[8].imag(),yb.real()) | |||
); | |||
scratch[6] = cpx_type( | |||
S_MUL(scratch[10].imag(),ya.imag()) + S_MUL(scratch[9].imag(),yb.imag()), | |||
-S_MUL(scratch[10].real(),ya.imag()) - S_MUL(scratch[9].real(),yb.imag()) | |||
); | |||
C_SUB(*Fout1,scratch[5],scratch[6]); | |||
C_ADD(*Fout4,scratch[5],scratch[6]); | |||
scratch[11] = scratch[0] + | |||
cpx_type( | |||
S_MUL(scratch[7].real(),yb.real()) + S_MUL(scratch[8].real(),ya.real()), | |||
S_MUL(scratch[7].imag(),yb.real()) + S_MUL(scratch[8].imag(),ya.real()) | |||
); | |||
scratch[12] = cpx_type( | |||
-S_MUL(scratch[10].imag(),yb.imag()) + S_MUL(scratch[9].imag(),ya.imag()), | |||
S_MUL(scratch[10].real(),yb.imag()) - S_MUL(scratch[9].real(),ya.imag()) | |||
); | |||
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 */ | |||
void kf_bfly_generic( | |||
cpx_type * Fout, | |||
const size_t fstride, | |||
int m, | |||
int p | |||
) | |||
{ | |||
int u,k,q1,q; | |||
cpx_type * twiddles = &_twiddles[0]; | |||
cpx_type t; | |||
int Norig = _nfft; | |||
cpx_type scratchbuf[p]; | |||
for ( u=0; u<m; ++u ) { | |||
k=u; | |||
for ( q1=0 ; q1<p ; ++q1 ) { | |||
scratchbuf[q1] = Fout[ k ]; | |||
C_FIXDIV(scratchbuf[q1],p); | |||
k += m; | |||
} | |||
k=u; | |||
for ( q1=0 ; q1<p ; ++q1 ) { | |||
int twidx=0; | |||
Fout[ k ] = scratchbuf[0]; | |||
for (q=1;q<p;++q ) { | |||
twidx += fstride * k; | |||
if (twidx>=Norig) twidx-=Norig; | |||
C_MUL(t,scratchbuf[q] , twiddles[twidx] ); | |||
C_ADDTO( Fout[ k ] ,t); | |||
} | |||
k += m; | |||
} | |||
} | |||
} | |||
int _nfft; | |||
bool _inverse; | |||
std::vector<cpx_type> _twiddles; | |||
std::vector<int> _stageRadix; | |||
std::vector<int> _stageRemainder; | |||
traits_type _traits; | |||
}; | |||
#endif |
@@ -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 |
@@ -0,0 +1,94 @@ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <fftw3.h> | |||
#include <unistd.h> | |||
#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<nfft;++i ) { | |||
in[i][0] = rand() - RAND_MAX/2; | |||
in[i][1] = rand() - RAND_MAX/2; | |||
} | |||
if ( isinverse ) | |||
p = MAKEPLAN(nfft, in, out, FFTW_BACKWARD, FFTW_ESTIMATE); | |||
else | |||
p = MAKEPLAN(nfft, in, out, FFTW_FORWARD, FFTW_ESTIMATE); | |||
for (i=0;i<numffts;++i) | |||
DOFFT(p); | |||
DESTROYPLAN(p); | |||
FFTFREE(in); FFTFREE(out); | |||
fprintf(stderr,"fftw\tnfft=%d\tnumffts=%d\n", nfft,numffts); | |||
pstats_report(); | |||
return 0; | |||
} | |||
#endif |
@@ -0,0 +1,122 @@ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <sys/times.h> | |||
#include <unistd.h> | |||
#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<ndims;++k) | |||
nbytes *= nfft[k]; | |||
#ifdef USE_SIMD | |||
numffts /= 4; | |||
fprintf(stderr,"since SIMD implementation does 4 ffts at a time, numffts is being reduced to %d\n",numffts); | |||
#endif | |||
buf=(kiss_fft_cpx*)KISS_FFT_MALLOC(nbytes); | |||
bufout=(kiss_fft_cpx*)KISS_FFT_MALLOC(nbytes); | |||
memset(buf,0,nbytes); | |||
pstats_init(); | |||
if (ndims==1) { | |||
if (real) { | |||
kiss_fftr_cfg st = kiss_fftr_alloc( nfft[0] ,isinverse ,0,0); | |||
if (isinverse) | |||
for (i=0;i<numffts;++i) | |||
kiss_fftri( st ,(kiss_fft_cpx*)buf,(kiss_fft_scalar*)bufout ); | |||
else | |||
for (i=0;i<numffts;++i) | |||
kiss_fftr( st ,(kiss_fft_scalar*)buf,(kiss_fft_cpx*)bufout ); | |||
free(st); | |||
}else{ | |||
kiss_fft_cfg st = kiss_fft_alloc( nfft[0] ,isinverse ,0,0); | |||
for (i=0;i<numffts;++i) | |||
kiss_fft( st ,buf,bufout ); | |||
free(st); | |||
} | |||
}else{ | |||
if (real) { | |||
kiss_fftndr_cfg st = kiss_fftndr_alloc( nfft,ndims ,isinverse ,0,0); | |||
if (isinverse) | |||
for (i=0;i<numffts;++i) | |||
kiss_fftndri( st ,(kiss_fft_cpx*)buf,(kiss_fft_scalar*)bufout ); | |||
else | |||
for (i=0;i<numffts;++i) | |||
kiss_fftndr( st ,(kiss_fft_scalar*)buf,(kiss_fft_cpx*)bufout ); | |||
free(st); | |||
}else{ | |||
kiss_fftnd_cfg st= kiss_fftnd_alloc(nfft,ndims,isinverse ,0,0); | |||
for (i=0;i<numffts;++i) | |||
kiss_fftnd( st ,buf,bufout ); | |||
free(st); | |||
} | |||
} | |||
free(buf); free(bufout); | |||
fprintf(stderr,"KISS\tnfft="); | |||
for (k=0;k<ndims;++k) | |||
fprintf(stderr, "%d,",nfft[k]); | |||
fprintf(stderr,"\tnumffts=%d\n" ,numffts); | |||
pstats_report(); | |||
kiss_fft_cleanup(); | |||
return 0; | |||
} | |||
@@ -0,0 +1,92 @@ | |||
#!/usr/bin/env python | |||
# use FFTPACK as a baseline | |||
import FFT | |||
from Numeric import * | |||
import math | |||
import random | |||
import sys | |||
import struct | |||
import fft | |||
pi=math.pi | |||
e=math.e | |||
j=complex(0,1) | |||
lims=(-32768,32767) | |||
def randbuf(n,cpx=1): | |||
res = array( [ random.uniform( lims[0],lims[1] ) for i in range(n) ] ) | |||
if cpx: | |||
res = res + j*randbuf(n,0) | |||
return res | |||
def main(): | |||
from getopt import getopt | |||
import popen2 | |||
opts,args = getopt( sys.argv[1:],'u:n:Rt:' ) | |||
opts=dict(opts) | |||
exitcode=0 | |||
util = opts.get('-u','./kf_float') | |||
try: | |||
dims = [ int(d) for d in opts['-n'].split(',')] | |||
cpx = opts.get('-R') is None | |||
fmt=opts.get('-t','f') | |||
except KeyError: | |||
sys.stderr.write(""" | |||
usage: compfft.py | |||
-n d1[,d2,d3...] : FFT dimension(s) | |||
-u utilname : see sample_code/fftutil.c, default = ./kf_float | |||
-R : real-optimized version\n""") | |||
sys.exit(1) | |||
x = fft.make_random( dims ) | |||
cmd = '%s -n %s ' % ( util, ','.join([ str(d) for d in dims]) ) | |||
if cpx: | |||
xout = FFT.fftnd(x) | |||
xout = reshape(xout,(size(xout),)) | |||
else: | |||
cmd += '-R ' | |||
xout = FFT.real_fft(x) | |||
proc = popen2.Popen3( cmd , bufsize=len(x) ) | |||
proc.tochild.write( dopack( x , fmt ,cpx ) ) | |||
proc.tochild.close() | |||
xoutcomp = dounpack( proc.fromchild.read( ) , fmt ,1 ) | |||
#xoutcomp = reshape( xoutcomp , dims ) | |||
sig = xout * conjugate(xout) | |||
sigpow = sum( sig ) | |||
diff = xout-xoutcomp | |||
noisepow = sum( diff * conjugate(diff) ) | |||
snr = 10 * math.log10(abs( sigpow / noisepow ) ) | |||
if snr<100: | |||
print xout | |||
print xoutcomp | |||
exitcode=1 | |||
print 'NFFT=%s,SNR = %f dB' % (str(dims),snr) | |||
sys.exit(exitcode) | |||
def dopack(x,fmt,cpx): | |||
x = reshape( x, ( size(x),) ) | |||
if cpx: | |||
s = ''.join( [ struct.pack('ff',c.real,c.imag) for c in x ] ) | |||
else: | |||
s = ''.join( [ struct.pack('f',c) for c in x ] ) | |||
return s | |||
def dounpack(x,fmt,cpx): | |||
uf = fmt * ( len(x) / 4 ) | |||
s = struct.unpack(uf,x) | |||
if cpx: | |||
return array(s[::2]) + array( s[1::2] )*j | |||
else: | |||
return array(s ) | |||
if __name__ == "__main__": | |||
main() |
@@ -0,0 +1,129 @@ | |||
/* this program is in the public domain | |||
A program that helps the authors of the fine fftw library benchmark kiss | |||
*/ | |||
#include "bench-user.h" | |||
#include <math.h> | |||
#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;i<p->rank;++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); | |||
} |
@@ -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>',yslow,'</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() |
@@ -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) |
@@ -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<n;++k) { | |||
sigpow += test_vec_out[k].r * test_vec_out[k].r + | |||
test_vec_out[k].i * test_vec_out[k].i; | |||
C_SUB(err,test_vec_out[k],testbuf[k].r); | |||
noisepow += err.r * err.r + err.i + err.i; | |||
if (test_vec_out[k].r) | |||
scale += testbuf[k].r / test_vec_out[k].r; | |||
} | |||
snr = 10*log10( sigpow / noisepow ); | |||
scale /= n; | |||
if (snr<10) | |||
printf( "\\npoor snr, try a scaling factor %f\\n" , scale ); | |||
return snr; | |||
} | |||
""" | |||
return s | |||
def main(): | |||
from getopt import getopt | |||
opts,args = getopt(sys.argv[1:],'s') | |||
opts = dict(opts) | |||
short = int( opts.has_key('-s') ) | |||
fftsizes = args | |||
if not fftsizes: | |||
fftsizes = [ 1800 ] | |||
print '#include "kiss_fft.h"' | |||
print compare_func() | |||
print "int main() { int exit_code=0;\n" | |||
for n in fftsizes: | |||
n = int(n) | |||
print test_cpx(n,0,short) | |||
print test_cpx(n,1,short) | |||
print """ | |||
return exit_code; | |||
} | |||
""" | |||
if __name__ == "__main__": | |||
main() |
@@ -0,0 +1,49 @@ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <sys/times.h> | |||
#include <sys/types.h> | |||
#include <unistd.h> | |||
#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(); | |||
} | |||
@@ -0,0 +1,7 @@ | |||
#ifndef PSTATS_H | |||
#define PSTATS_H | |||
void pstats_init(void); | |||
void pstats_report(void); | |||
#endif |
@@ -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 |
@@ -0,0 +1,172 @@ | |||
#include "kiss_fftr.h" | |||
#include "_kiss_fft_guts.h" | |||
#include <sys/times.h> | |||
#include <time.h> | |||
#include <unistd.h> | |||
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;k<n;++k) { | |||
sigpow += (double)vec1[k].r * (double)vec1[k].r + | |||
(double)vec1[k].i * (double)vec1[k].i; | |||
err = (double)vec1[k].r - (double)vec2[k].r; | |||
noisepow += err * err; | |||
err = (double)vec1[k].i - (double)vec2[k].i; | |||
noisepow += err * err; | |||
if (vec1[k].r) | |||
scale +=(double) vec2[k].r / (double)vec1[k].r; | |||
} | |||
#endif | |||
snr = 10*log10( sigpow / noisepow ); | |||
scale /= n; | |||
if (snr<10) { | |||
printf( "\npoor snr, try a scaling factor %f\n" , scale ); | |||
exit(1); | |||
} | |||
return snr; | |||
} | |||
#ifndef NUMFFTS | |||
#define NUMFFTS 10000 | |||
#endif | |||
int main(int argc,char ** argv) | |||
{ | |||
int nfft = 8*3*5; | |||
double ts,tfft,trfft; | |||
int i; | |||
if (argc>1) | |||
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;i<nfft;++i) { | |||
rin[i] = rand_scalar(); | |||
cin[i].r = rin[i]; | |||
cin[i].i = zero; | |||
} | |||
kiss_fft_state = kiss_fft_alloc(nfft,0,0,0); | |||
kiss_fftr_state = kiss_fftr_alloc(nfft,0,0,0); | |||
kiss_fft(kiss_fft_state,cin,cout); | |||
kiss_fftr(kiss_fftr_state,rin,sout); | |||
/* | |||
printf(" results from kiss_fft : (%f,%f), (%f,%f), (%f,%f) ...\n " | |||
, (float)cout[0].r , (float)cout[0].i | |||
, (float)cout[1].r , (float)cout[1].i | |||
, (float)cout[2].r , (float)cout[2].i); | |||
printf(" results from kiss_fftr: (%f,%f), (%f,%f), (%f,%f) ...\n " | |||
, (float)sout[0].r , (float)sout[0].i | |||
, (float)sout[1].r , (float)sout[1].i | |||
, (float)sout[2].r , (float)sout[2].i); | |||
*/ | |||
printf( "nfft=%d, inverse=%d, snr=%g\n", | |||
nfft,0, snr_compare(cout,sout,(nfft/2)+1) ); | |||
ts = cputime(); | |||
for (i=0;i<NUMFFTS;++i) { | |||
kiss_fft(kiss_fft_state,cin,cout); | |||
} | |||
tfft = cputime() - ts; | |||
ts = cputime(); | |||
for (i=0;i<NUMFFTS;++i) { | |||
kiss_fftr( kiss_fftr_state, rin, cout ); | |||
/* kiss_fftri(kiss_fftr_state,cout,rin); */ | |||
} | |||
trfft = cputime() - ts; | |||
printf("%d complex ffts took %gs, real took %gs\n",NUMFFTS,tfft,trfft); | |||
free(kiss_fft_state); | |||
free(kiss_fftr_state); | |||
kiss_fft_state = kiss_fft_alloc(nfft,1,0,0); | |||
kiss_fftr_state = kiss_fftr_alloc(nfft,1,0,0); | |||
memset(cin,0,sizeof(cin)); | |||
#if 1 | |||
for (i=1;i< nfft/2;++i) { | |||
//cin[i].r = (kiss_fft_scalar)(rand()-RAND_MAX/2); | |||
cin[i].r = rand_scalar(); | |||
cin[i].i = rand_scalar(); | |||
} | |||
#else | |||
cin[0].r = 12000; | |||
cin[3].r = 12000; | |||
cin[nfft/2].r = 12000; | |||
#endif | |||
// conjugate symmetry of real signal | |||
for (i=1;i< nfft/2;++i) { | |||
cin[nfft-i].r = cin[i].r; | |||
cin[nfft-i].i = - cin[i].i; | |||
} | |||
kiss_fft(kiss_fft_state,cin,cout); | |||
kiss_fftri(kiss_fftr_state,cin,rout); | |||
/* | |||
printf(" results from inverse kiss_fft : (%f,%f), (%f,%f), (%f,%f), (%f,%f), (%f,%f) ...\n " | |||
, (float)cout[0].r , (float)cout[0].i , (float)cout[1].r , (float)cout[1].i , (float)cout[2].r , (float)cout[2].i , (float)cout[3].r , (float)cout[3].i , (float)cout[4].r , (float)cout[4].i | |||
); | |||
printf(" results from inverse kiss_fftr: %f,%f,%f,%f,%f ... \n" | |||
,(float)rout[0] ,(float)rout[1] ,(float)rout[2] ,(float)rout[3] ,(float)rout[4]); | |||
*/ | |||
for (i=0;i<nfft;++i) { | |||
sout[i].r = rout[i]; | |||
sout[i].i = zero; | |||
} | |||
printf( "nfft=%d, inverse=%d, snr=%g\n", | |||
nfft,1, snr_compare(cout,sout,nfft/2) ); | |||
free(kiss_fft_state); | |||
free(kiss_fftr_state); | |||
return 0; | |||
} |
@@ -0,0 +1,74 @@ | |||
#include "kiss_fft.h" | |||
void check(kiss_fft_cpx * in,kiss_fft_cpx * out,int nfft,int isinverse) | |||
{ | |||
int bin,k; | |||
double errpow=0,sigpow=0; | |||
for (bin=0;bin<nfft;++bin) { | |||
double ansr = 0; | |||
double ansi = 0; | |||
double difr; | |||
double difi; | |||
for (k=0;k<nfft;++k) { | |||
double phase = -2*M_PI*bin*k/nfft; | |||
double re = cos(phase); | |||
double im = sin(phase); | |||
if (isinverse) | |||
im = -im; | |||
#ifdef FIXED_POINT | |||
re /= nfft; | |||
im /= nfft; | |||
#endif | |||
ansr += in[k].r * re - in[k].i * im; | |||
ansi += in[k].r * im + in[k].i * re; | |||
} | |||
difr = ansr - out[bin].r; | |||
difi = ansi - out[bin].i; | |||
errpow += difr*difr + difi*difi; | |||
sigpow += ansr*ansr+ansi*ansi; | |||
} | |||
printf("nfft=%d inverse=%d,snr = %f\n",nfft,isinverse,10*log10(sigpow/errpow) ); | |||
} | |||
void test1d(int nfft,int isinverse) | |||
{ | |||
size_t buflen = sizeof(kiss_fft_cpx)*nfft; | |||
kiss_fft_cpx * in = (kiss_fft_cpx*)malloc(buflen); | |||
kiss_fft_cpx * out= (kiss_fft_cpx*)malloc(buflen); | |||
kiss_fft_cfg cfg = kiss_fft_alloc(nfft,isinverse,0,0); | |||
int k; | |||
for (k=0;k<nfft;++k) { | |||
in[k].r = (rand() % 65536) - 32768; | |||
in[k].i = (rand() % 65536) - 32768; | |||
} | |||
kiss_fft(cfg,in,out); | |||
check(in,out,nfft,isinverse); | |||
free(in); | |||
free(out); | |||
free(cfg); | |||
} | |||
int main(int argc,char ** argv) | |||
{ | |||
if (argc>1) { | |||
int k; | |||
for (k=1;k<argc;++k) { | |||
test1d(atoi(argv[k]),0); | |||
test1d(atoi(argv[k]),1); | |||
} | |||
}else{ | |||
test1d(32,0); | |||
test1d(32,1); | |||
} | |||
return 0; | |||
} |
@@ -0,0 +1,73 @@ | |||
#include "kissfft.hh" | |||
#include <iostream> | |||
#include <cstdlib> | |||
#include <typeinfo> | |||
#include <sys/time.h> | |||
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 <class T> | |||
void dotest(int nfft) | |||
{ | |||
typedef kissfft<T> FFT; | |||
typedef std::complex<T> cpx_type; | |||
cout << "type:" << typeid(T).name() << " nfft:" << nfft; | |||
FFT fft(nfft,false); | |||
vector<cpx_type> inbuf(nfft); | |||
vector<cpx_type> outbuf(nfft); | |||
for (int k=0;k<nfft;++k) | |||
inbuf[k]= cpx_type( | |||
(T)(rand()/(double)RAND_MAX - .5), | |||
(T)(rand()/(double)RAND_MAX - .5) ); | |||
fft.transform( &inbuf[0] , &outbuf[0] ); | |||
long double totalpower=0; | |||
long double difpower=0; | |||
for (int k0=0;k0<nfft;++k0) { | |||
complex<long double> acc = 0; | |||
long double phinc = 2*k0* M_PIl / nfft; | |||
for (int k1=0;k1<nfft;++k1) { | |||
complex<long double> x(inbuf[k1].real(),inbuf[k1].imag()); | |||
acc += x * exp( complex<long double>(0,-k1*phinc) ); | |||
} | |||
totalpower += norm(acc); | |||
complex<long double> x(outbuf[k0].real(),outbuf[k0].imag()); | |||
complex<long double> dif = acc - x; | |||
difpower += norm(dif); | |||
} | |||
cout << " RMSE:" << sqrt(difpower/totalpower) << "\t"; | |||
double t0 = curtime(); | |||
int nits=20e6/nfft; | |||
for (int k=0;k<nits;++k) { | |||
fft.transform( &inbuf[0] , &outbuf[0] ); | |||
} | |||
double t1 = curtime(); | |||
cout << " MSPS:" << ( (nits*nfft)*1e-6/ (t1-t0) ) << endl; | |||
} | |||
int main(int argc,char ** argv) | |||
{ | |||
if (argc>1) { | |||
for (int k=1;k<argc;++k) { | |||
int nfft = atoi(argv[k]); | |||
dotest<float>(nfft); dotest<double>(nfft); dotest<long double>(nfft); | |||
} | |||
}else{ | |||
dotest<float>(32); dotest<double>(32); dotest<long double>(32); | |||
dotest<float>(1024); dotest<double>(1024); dotest<long double>(1024); | |||
dotest<float>(840); dotest<double>(840); dotest<long double>(840); | |||
} | |||
return 0; | |||
} |
@@ -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<minsnr: | |||
print 'xver=',xver | |||
print 'x2=',x2 | |||
print 'err',err | |||
sys.exit(1) | |||
def dofft(x,isreal): | |||
dims=list( numpy.shape(x) ) | |||
x = flatten(x) | |||
scale=1 | |||
if datatype=='int16_t': | |||
x = 32767 * x | |||
scale = len(x) / 32767.0 | |||
elif datatype=='int32_t': | |||
x = 2147483647.0 * x | |||
scale = len(x) / 2147483647.0 | |||
cmd='%s -n ' % util | |||
cmd += ','.join([str(d) for d in dims]) | |||
if doreal: | |||
cmd += ' -R ' | |||
print cmd | |||
p = popen2.Popen3(cmd ) | |||
open('/tmp/fftin.dat','w').write(dopack( x , isreal==False ) ) | |||
p.tochild.write( dopack( x , isreal==False ) ) | |||
p.tochild.close() | |||
res = dounpack( p.fromchild.read() , 1 ) | |||
open('/tmp/fftout.dat','w').write(dopack( flatten(res) , True ) ) | |||
if doreal: | |||
dims[-1] = int( dims[-1]/2 ) + 1 | |||
res = scale * res | |||
p.wait() | |||
return numpy.reshape(res,dims) | |||
def main(): | |||
opts,args = getopt.getopt(sys.argv[1:],'r') | |||
opts=dict(opts) | |||
global doreal | |||
doreal = opts.has_key('-r') | |||
if doreal: | |||
print 'Testing multi-dimensional real FFTs' | |||
else: | |||
print 'Testing multi-dimensional FFTs' | |||
for dim in range(1,4): | |||
test_fft( dim ) | |||
if __name__ == "__main__": | |||
main() | |||
@@ -0,0 +1,94 @@ | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <stdio.h> | |||
#include "kiss_fft.h" | |||
#include "kiss_fftr.h" | |||
#include <limits.h> | |||
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<nfft/2;i+= (nfft>>4)+1) { | |||
for (j=i;j<nfft/2;j+=(nfft>>4)+7) { | |||
snr = two_tone_test(nfft,i,j); | |||
if (snr<minsnr) { | |||
minsnr=snr; | |||
} | |||
if (snr>maxsnr) { | |||
maxsnr=snr; | |||
} | |||
} | |||
} | |||
snr = two_tone_test(nfft,nfft/2,nfft/2); | |||
if (snr<minsnr) minsnr=snr; | |||
if (snr>maxsnr) 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; | |||
} |
@@ -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_* |
@@ -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 <stdlib.h> | |||
#include <math.h> | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#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<ndims;++i) | |||
dimprod *= dims[i]; | |||
buf = (kiss_fft_cpx *) malloc (sizeof (kiss_fft_cpx) * dimprod); | |||
st = kiss_fftnd_alloc (dims, ndims, isinverse, 0, 0); | |||
while (fread (buf, sizeof (kiss_fft_cpx) * dimprod, 1, fin) > 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<ndims;++i) | |||
dimprod *= dims[i]; | |||
insize = outsize = dimprod; | |||
int rdim = dims[ndims-1]; | |||
if (isinverse) | |||
insize = insize*2*(rdim/2+1)/rdim; | |||
else | |||
outsize = outsize*2*(rdim/2+1)/rdim; | |||
ibuf = malloc(insize*sizeof(kiss_fft_scalar)); | |||
obuf = malloc(outsize*sizeof(kiss_fft_scalar)); | |||
st = kiss_fftndr_alloc(dims, ndims, isinverse, 0, 0); | |||
while ( fread (ibuf, sizeof(kiss_fft_scalar), insize, fin) > 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; | |||
} |
@@ -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 |
@@ -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 |
@@ -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;i<n_imp_resp - 1; ++i) { | |||
st->tmpbuf[ 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; i<st->n_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 <unistd.h> | |||
#include <sys/types.h> | |||
#include <sys/mman.h> | |||
#include <assert.h> | |||
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 |
@@ -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;i<ndims;++i) { | |||
size_t sublen=0; | |||
kiss_fft_alloc (dims[i], inverse_fft, NULL, &sublen); | |||
memneeded += sublen; /* st->states[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;i<ndims;++i) { | |||
size_t len; | |||
st->dims[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 ; i<stride ; ++i ) | |||
kiss_fft_stride( st->states[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; | |||
} | |||
} | |||
} |
@@ -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 |
@@ -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;k1<dimOther;++k1) { | |||
kiss_fftr( st->cfg_r, timedata + k1*dimReal , tmp1 ); // tmp1 now holds nrbins complex points | |||
for (k2=0;k2<nrbins;++k2) | |||
tmp2[ k2*dimOther+k1 ] = tmp1[k2]; | |||
} | |||
for (k2=0;k2<nrbins;++k2) { | |||
kiss_fftnd(st->cfg_nd, tmp2+k2*dimOther, tmp1); // tmp1 now holds dimOther complex points | |||
for (k1=0;k1<dimOther;++k1) | |||
freqdata[ k1*(nrbins) + k2] = tmp1[k1]; | |||
} | |||
} | |||
void kiss_fftndri(kiss_fftndr_cfg st,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata) | |||
{ | |||
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); | |||
for (k2=0;k2<nrbins;++k2) { | |||
for (k1=0;k1<dimOther;++k1) | |||
tmp1[k1] = freqdata[ k1*(nrbins) + k2 ]; | |||
kiss_fftnd(st->cfg_nd, tmp1, tmp2+k2*dimOther); | |||
} | |||
for (k1=0;k1<dimOther;++k1) { | |||
for (k2=0;k2<nrbins;++k2) | |||
tmp1[k2] = tmp2[ k2*dimOther+k1 ]; | |||
kiss_fftri( st->cfg_r,tmp1,timedata + k1*dimReal); | |||
} | |||
} |
@@ -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 |
@@ -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); | |||
} |
@@ -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 |
@@ -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 <stdlib.h> | |||
#include <math.h> | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include <png.h> | |||
#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<nfft;++i) | |||
tbuf[i] = inbuf[2*i] + inbuf[2*i+1]; | |||
}else{ | |||
n = fread(inbuf,sizeof(short),nfft,fin); | |||
if (n != nfft ) | |||
break; | |||
for (i=0;i<nfft;++i) | |||
tbuf[i] = inbuf[i]; | |||
} | |||
if (remove_dc) { | |||
float avg = 0; | |||
for (i=0;i<nfft;++i) avg += tbuf[i]; | |||
avg /= nfft; | |||
for (i=0;i<nfft;++i) tbuf[i] -= (kiss_fft_scalar)avg; | |||
} | |||
/* do FFT */ | |||
kiss_fftr(cfg,tbuf,fbuf); | |||
for (i=0;i<nfreqs;++i) | |||
mag2buf[i] += fbuf[i].r * fbuf[i].r + fbuf[i].i * fbuf[i].i; | |||
if (++avgctr == navg) { | |||
avgctr=0; | |||
++nrows; | |||
vals = (float*)realloc(vals,sizeof(float)*nrows*nfreqs); | |||
float eps = 1; | |||
for (i=0;i<nfreqs;++i) | |||
vals[(nrows - 1) * nfreqs + i] = 10 * log10 ( mag2buf[i] / navg + eps ); | |||
memset(mag2buf,0,sizeof(mag2buf[0])*nfreqs); | |||
} | |||
} | |||
free(cfg); | |||
free(inbuf); | |||
free(tbuf); | |||
free(fbuf); | |||
free(mag2buf); | |||
} | |||
static | |||
void make_png(void) | |||
{ | |||
png_bytepp row_pointers=NULL; | |||
rgb_t * row_data=NULL; | |||
int i; | |||
int nfreqs = nfft/2+1; | |||
png_structp png_ptr=NULL; | |||
png_infop info_ptr=NULL; | |||
CHECKNULL( png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,0,0,0) ); | |||
CHECKNULL( info_ptr = png_create_info_struct(png_ptr) ); | |||
png_init_io(png_ptr, fout ); | |||
png_set_IHDR(png_ptr, info_ptr ,nfreqs,nrows,8,PNG_COLOR_TYPE_RGB,PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT ); | |||
row_data = (rgb_t*)malloc(sizeof(rgb_t) * nrows * nfreqs) ; | |||
cpx2pixels(row_data, vals, nfreqs*nrows ); | |||
row_pointers = realloc(row_pointers, nrows*sizeof(png_bytep)); | |||
for (i=0;i<nrows;++i) { | |||
row_pointers[i] = (png_bytep)(row_data + i*nfreqs); | |||
} | |||
png_set_rows(png_ptr, info_ptr, row_pointers); | |||
fprintf(stderr,"creating %dx%d png\n",nfreqs,nrows); | |||
fprintf(stderr,"bitdepth %d \n",png_get_bit_depth(png_ptr,info_ptr ) ); | |||
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY , NULL); | |||
} | |||
int main(int argc,char ** argv) | |||
{ | |||
config(argc,argv); | |||
transform_signal(); | |||
make_png(); | |||
if (fout!=stdout) fclose(fout); | |||
if (fin!=stdin) fclose(fin); | |||
return 0; | |||
} |
@@ -0,0 +1,80 @@ | |||
#pragma once | |||
#include <assert.h> | |||
/** 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; | |||
} | |||
}; | |||
@@ -0,0 +1,87 @@ | |||
#include <assert.h> | |||
#include <memory> | |||
#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<double(double)> AudioMath::makeFunc_Sin() | |||
{ | |||
return [](double x) { | |||
return std::sin(x * 2 * Pi); | |||
}; | |||
} | |||
std::function<double(double)> 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<double(double)> AudioMath::makeFunc_AudioTaper(double dbAtten) | |||
{ | |||
assert(dbAtten < 0); | |||
const double gainAtQuarter = gainFromDb(dbAtten); | |||
std::function<double(double)> linearFunc; | |||
std::function<double(double)> 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<float> AudioMath::makeBipolarAudioScaler(float y0, float y1) | |||
{ | |||
// Use a cached singleton for the lookup table - don't need to have unique copies | |||
std::shared_ptr<LookupTableParams<float>> lookup = ObjectCache<float>::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<float>::lookup(*lookup, trim); | |||
float x = cv * mappedTrim + knob; | |||
x = std::max<float>(-5.0f, x); | |||
x = std::min(5.0f, x); | |||
return a * x + b; | |||
}; | |||
} | |||
// declare some test variables here | |||
int _numLookupParams = 0; | |||
int _numBiquads = 0; |
@@ -0,0 +1,92 @@ | |||
#pragma once | |||
#include <cmath> | |||
#include <functional> | |||
#include <algorithm> | |||
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<double(double)> 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<double(double)> 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<double(double)> 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 <typename T> | |||
using ScaleFun = std::function<T(T cv, T knob, T trim)>; | |||
/** | |||
* 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 <typename T> | |||
static ScaleFun<T> 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<T>(-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<float> makeBipolarAudioScaler(float y0, float y1); | |||
}; |
@@ -0,0 +1,233 @@ | |||
#pragma once | |||
#include <cmath> | |||
#include <memory> | |||
#include <emmintrin.h> | |||
#include <functional> | |||
#include "AudioMath.h" | |||
template <typename T> class LookupTableParams; | |||
/* Lookup table with evenly spaced lookup "bins" | |||
* Uses linear interpolation | |||
*/ | |||
// TODO: templatize on size? | |||
template <typename T> | |||
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<T>& 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<T>& params, int bins, T xMin, T xMax, std::function<double(double)> 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<T>& params, int numEntries, const T * yEntries); | |||
private: | |||
static int cvtt(T *); | |||
#ifdef _DEBUG | |||
static void checkInput(const LookupTableParams<T>& 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<typename T> | |||
inline T LookupTable<T>::lookup(const LookupTableParams<T>& 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<typename T> | |||
inline void LookupTable<T>::init(LookupTableParams<T>& params, | |||
int bins, T x0In, T x1In, std::function<double(double)> 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<typename T> | |||
inline void LookupTable<T>::initDiscrete(LookupTableParams<T>& 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<double>::cvtt(double* input) | |||
{ | |||
auto x = _mm_load_sd(input); | |||
return _mm_cvttsd_si32(x); | |||
} | |||
template<> | |||
inline int LookupTable<float>::cvtt(float* input) | |||
{ | |||
auto x = _mm_load_ss(input); | |||
return _mm_cvttss_si32(x); | |||
} | |||
/***************************************************************************/ | |||
extern int _numLookupParams; | |||
template <typename T> | |||
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; | |||
} | |||
}; | |||
@@ -0,0 +1,64 @@ | |||
#pragma once | |||
#include "LookupTable.h" | |||
// TODO: this class should not be templatized. the functions should | |||
template<typename T> | |||
class LookupTableFactory | |||
{ | |||
public: | |||
static void makeBipolarAudioTaper(LookupTableParams<T>& 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<T>& 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<typename T> | |||
inline void LookupTableFactory<T>::makeExp2(LookupTableParams<T>& 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<T>::init(params, bins, xMin, xMax, [](double x) { | |||
return std::pow(2, x); | |||
}); | |||
} | |||
template<typename T> | |||
inline void LookupTableFactory<T>::makeBipolarAudioTaper(LookupTableParams<T>& params) | |||
{ | |||
const int bins = 32; | |||
std::function<double(double)> audioTaper = AudioMath::makeFunc_AudioTaper(audioTaperKnee()); | |||
const T xMin = -1; | |||
const T xMax = 1; | |||
LookupTable<T>::init(params, bins, xMin, xMax, [audioTaper](double x) { | |||
return (x >= 0) ? audioTaper(x) : -audioTaper(-x); | |||
}); | |||
} |
@@ -0,0 +1,95 @@ | |||
#include <assert.h> | |||
#include "AudioMath.h" | |||
#include "LookupTableFactory.h" | |||
#include "ObjectCache.h" | |||
template <typename T> | |||
std::shared_ptr<LookupTableParams<T>> ObjectCache<T>::getBipolarAudioTaper() | |||
{ | |||
std::shared_ptr< LookupTableParams<T>> ret = bipolarAudioTaper.lock(); | |||
if (!ret) { | |||
ret = std::make_shared<LookupTableParams<T>>(); | |||
LookupTableFactory<T>::makeBipolarAudioTaper(*ret); | |||
bipolarAudioTaper = ret; | |||
} | |||
return ret; | |||
} | |||
template <typename T> | |||
std::shared_ptr<LookupTableParams<T>> ObjectCache<T>::getSinLookup() | |||
{ | |||
std::shared_ptr< LookupTableParams<T>> ret = sinLookupTable.lock(); | |||
if (!ret) { | |||
ret = std::make_shared<LookupTableParams<T>>(); | |||
std::function<double(double)> f = AudioMath::makeFunc_Sin(); | |||
// Used to use 4096, but 256 gives about 80db snr, so let's save memory | |||
LookupTable<T>::init(*ret, 256, 0, 1, f); | |||
sinLookupTable = ret; | |||
} | |||
return ret; | |||
} | |||
template <typename T> | |||
std::shared_ptr<LookupTableParams<T>> ObjectCache<T>::getExp2() | |||
{ | |||
std::shared_ptr< LookupTableParams<T>> ret = exp2.lock(); | |||
if (!ret) { | |||
ret = std::make_shared<LookupTableParams<T>>(); | |||
LookupTableFactory<T>::makeExp2(*ret); | |||
exp2 = ret; | |||
} | |||
return ret; | |||
} | |||
template <typename T> | |||
std::shared_ptr<LookupTableParams<T>> ObjectCache<T>::getDb2Gain() | |||
{ | |||
std::shared_ptr< LookupTableParams<T>> ret = db2Gain.lock(); | |||
if (!ret) { | |||
ret = std::make_shared<LookupTableParams<T>>(); | |||
LookupTable<T>::init(*ret, 32, -80, 20, [](double x) { | |||
return AudioMath::gainFromDb(x); | |||
}); | |||
db2Gain = ret; | |||
} | |||
return ret; | |||
} | |||
template <typename T> | |||
std::shared_ptr<LookupTableParams<T>> ObjectCache<T>::getTanh5() | |||
{ | |||
std::shared_ptr< LookupTableParams<T>> ret = tanh5.lock(); | |||
if (!ret) { | |||
ret = std::make_shared<LookupTableParams<T>>(); | |||
LookupTable<T>::init(*ret, 256, -5, 5, [](double x) { | |||
return std::tanh(x); | |||
}); | |||
tanh5 = ret; | |||
} | |||
return ret; | |||
} | |||
// The weak pointer that hold our singletons. | |||
template <typename T> | |||
std::weak_ptr<LookupTableParams<T>> ObjectCache<T>::bipolarAudioTaper; | |||
template <typename T> | |||
std::weak_ptr<LookupTableParams<T>> ObjectCache<T>::sinLookupTable; | |||
template <typename T> | |||
std::weak_ptr<LookupTableParams<T>> ObjectCache<T>::exp2; | |||
template <typename T> | |||
std::weak_ptr<LookupTableParams<T>> ObjectCache<T>::db2Gain; | |||
template <typename T> | |||
std::weak_ptr<LookupTableParams<T>> ObjectCache<T>::tanh5; | |||
// Explicit instantiation, so we can put implementation into .cpp file | |||
template class ObjectCache<double>; | |||
template class ObjectCache<float>; |
@@ -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 <typename T> | |||
class ObjectCache | |||
{ | |||
public: | |||
static std::shared_ptr<LookupTableParams<T>> getBipolarAudioTaper(); | |||
static std::shared_ptr<LookupTableParams<T>> 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<LookupTableParams<T>> getExp2(); | |||
static std::shared_ptr<LookupTableParams<T>> getDb2Gain(); | |||
/** | |||
* tanh, unscaled, from -5 to 5 | |||
*/ | |||
static std::shared_ptr<LookupTableParams<T>> 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<LookupTableParams<T>> bipolarAudioTaper; | |||
static std::weak_ptr<LookupTableParams<T>> sinLookupTable; | |||
static std::weak_ptr<LookupTableParams<T>> exp2; | |||
static std::weak_ptr<LookupTableParams<T>> db2Gain; | |||
static std::weak_ptr<LookupTableParams<T>> tanh5; | |||
}; |