Skip to content

Instantly share code, notes, and snippets.

@dlawle
Created September 19, 2023 06:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dlawle/5280c92922e1ba98a26e1063b8216186 to your computer and use it in GitHub Desktop.
Save dlawle/5280c92922e1ba98a26e1063b8216186 to your computer and use it in GitHub Desktop.
fft handler with some chatgpt examples
#pragma once
#include "shy_fft.h"
template <size_t FFT_SIZE>
class FFTHandler
{
public:
FFTHandler()
{
fft.Init();
SAMPLE_BUFFER_SIZE = FFT_SIZE;
mod_ = 0.2;
}
void Process(float input, float * output_buffer)
{
if (fft_buf_index < SAMPLE_BUFFER_SIZE)
{
fftInput[fft_buf_index++] = input;
}
if (ifft_buf_index < SAMPLE_BUFFER_SIZE)
{
*output_buffer = ifftOutput[ifft_buf_index++];
}
if (fft_buf_index >= SAMPLE_BUFFER_SIZE)
{
fft_buf_index = 0;
fft_proc();
}
if (ifft_buf_index >= SAMPLE_BUFFER_SIZE)
{
ifft_buf_index = 0;
}
}
/**========================================================================================================**/
/* TESTING AREA */
/**========================================================================================================**/
void SetMod(float mod) { mod_ = mod; }
private:
ShyFFT<float, FFT_SIZE, RotationPhasor> fft;
float fftInput[FFT_SIZE];
float fftOutput[FFT_SIZE];
float ifftOutput[FFT_SIZE];
float pitch_ratio_;
int fft_buf_index = 0;
int ifft_buf_index = 0;
int SAMPLE_BUFFER_SIZE = FFT_SIZE;
float mod_;
void fft_proc()
{
fft.Direct(fftInput, fftOutput);
applySpectralReverb(fftOutput, FFT_SIZE,0.5);
fft.Inverse(fftOutput, ifftOutput);
normalize_ifft(ifftOutput, FFT_SIZE);
}
void normalize_ifft(float* input, size_t size)
{
// Calculate the scaling factor (1 / N)
float scalingFactor = 1.0f / static_cast<float>(size);
// Apply the scaling factor to each element
for (size_t i = 0; i < size; i++)
{
input[i] *= scalingFactor;
}
}
/**========================================================================================================**/
/* TESTING AREA */
/**========================================================================================================**/
// please note a large majority of these do NOT work out of the box and will require a lot of testing/modding
// I've worked through a few already to get working (mostly the ones that only take fftdata and size).
// most mod_ values should be between 0.0 and 1.0, while some will take negatives. Delay/Reverb do not work
// as I believe that we will need to create a secondary buffer for these
void shiftFrequencyPhase(float* fftData, size_t size)
{
for (size_t i = 0; i < size; i += 2)
{
// Real and Imaginary parts of the complex number
float realPart = fftData[i];
float imagPart = fftData[i + 1];
// Calculate the magnitude and phase
float magnitude = sqrt(realPart * realPart + imagPart * imagPart);
float phase = atan2(imagPart, realPart);
// Apply the phase offset
phase += mod_;
// Update the real and imaginary parts with the modified phase
fftData[i] = magnitude * cos(phase);
fftData[i + 1] = magnitude * sin(phase);
}
}
void stretchSpectrum(float* fftData, size_t size)
{
for (size_t i = 0; i < size; i += 2)
{
// Real and Imaginary parts of the complex number
float realPart = fftData[i];
float imagPart = fftData[i + 1];
// Calculate the magnitude
float magnitude = sqrt(realPart * realPart + imagPart * imagPart);
// Calculate the frequency of the current bin
float frequency = static_cast<float>(i / 2) / size;
// Apply spectral stretching (scaling the frequencies)
float stretchedFrequency = frequency * mod_;
// Map the stretched frequency back to the FFT bins
size_t stretchedBin = static_cast<size_t>(stretchedFrequency * size * 2);
// Update the real and imaginary parts with the modified magnitude
fftData[stretchedBin] = magnitude * (realPart / magnitude); // Ensure phase consistency
fftData[stretchedBin + 1] = magnitude * (imagPart / magnitude);
}
}
void applyLowPassFilter(float* fftData, size_t size, float cutoffFrequency)
{
// Calculate the Nyquist frequency (half of the sampling rate)
float nyquistFrequency = 0.5f;
for (size_t i = 0; i < size; i += 2)
{
// Calculate the frequency of the current bin
float frequency = static_cast<float>(i / 2) / size;
// Calculate the magnitude
float magnitude = sqrt(fftData[i] * fftData[i] + fftData[i + 1] * fftData[i + 1]);
// Apply the low-pass filter
if (frequency > cutoffFrequency / nyquistFrequency)
{
// If the frequency is above the cutoff, attenuate it (set magnitude to zero)
magnitude = 0.0f;
}
// Update the real and imaginary parts with the modified magnitude
fftData[i] = magnitude * (fftData[i] / magnitude); // Ensure phase consistency
fftData[i + 1] = magnitude * (fftData[i + 1] / magnitude);
}
}
void pitchShiftSpectrum(float* fftData, size_t size)
{
for (size_t i = 0; i < size; i += 2)
{
// Real and Imaginary parts of the complex number
float realPart = fftData[i];
float imagPart = fftData[i + 1];
// Calculate the magnitude
float magnitude = sqrt(realPart * realPart + imagPart * imagPart);
// Calculate the frequency of the current bin
float frequency = static_cast<float>(i / 2) / size;
// Apply spectral pitch shifting (changing pitch)
float shiftedFrequency = frequency * mod_;
// Map the shifted frequency back to the FFT bins
size_t shiftedBin = static_cast<size_t>(shiftedFrequency * size * 2);
// Update the real and imaginary parts with the modified magnitude
fftData[shiftedBin] = magnitude * (realPart / magnitude); // Ensure phase consistency
fftData[shiftedBin + 1] = magnitude * (imagPart / magnitude);
}
}
void applySpectralDelay(float* fftData, size_t size)
{
// Create a copy of the original FFT data
float originalData[size];
memcpy(originalData, fftData, sizeof(float) * size);
for (size_t i = 0; i < size; i += 2)
{
// Real and Imaginary parts of the complex number in the original data
float originalRealPart = originalData[i];
float originalImagPart = originalData[i + 1];
// Calculate the magnitude of the original data
float originalMagnitude = sqrt(originalRealPart * originalRealPart + originalImagPart * originalImagPart);
// Calculate the frequency of the current bin
float frequency = static_cast<float>(i / 2) / size;
// Apply spectral delay (shift certain frequencies in time)
float delayedFrequency = frequency * mod_;
// Map the delayed frequency back to the FFT bins
size_t delayedBin = static_cast<size_t>(delayedFrequency * size * 2);
// Update the real and imaginary parts in the current bin with the delayed data
fftData[i] = originalData[delayedBin];
fftData[i + 1] = originalData[delayedBin + 1];
// Preserve magnitude (optional)
// You can adjust the magnitude as needed to control the intensity of the effect
float scaleFactor = originalMagnitude / sqrt(fftData[i] * fftData[i] + fftData[i + 1] * fftData[i + 1]);
fftData[i] *= scaleFactor;
fftData[i + 1] *= scaleFactor;
}
}
void applySpectralTremolo(float* fftData, size_t size)
{
float phaseOffset = 0.0f;
for (size_t i = 0; i < size; i += 2)
{
// Real and Imaginary parts of the complex number
float realPart = fftData[i];
float imagPart = fftData[i + 1];
// Calculate the magnitude
float magnitude = sqrt(realPart * realPart + imagPart * imagPart);
// Apply spectral tremolo modulation
float modulation = sin(phaseOffset);
phaseOffset += 2.0f * M_PI * 0.5 / size;
// Modify the magnitude of the current bin
float scaledMagnitude = magnitude * (1.0f + modulation * mod_);
// Update the real and imaginary parts with the modified magnitude
fftData[i] = scaledMagnitude * (realPart / magnitude); // Ensure phase consistency
fftData[i + 1] = scaledMagnitude * (imagPart / magnitude);
}
}
void enhanceHarmonics(float* fftData, size_t size)
{
for (size_t i = 0; i < size; i += 2)
{
// Real and Imaginary parts of the complex number
float realPart = fftData[i];
float imagPart = fftData[i + 1];
// Calculate the magnitude
float magnitude = sqrt(realPart * realPart + imagPart * imagPart);
// Calculate the frequency of the current bin
float frequency = static_cast<float>(i / 2) / size;
// Apply harmonic enhancement (boost specific harmonics)
float harmonic = 1.0f - exp(-0.5f * pow((frequency - 0.5f), 2) / pow(0.05f, 2));
harmonic = 1.0f + harmonic * mod_;
// Modify the magnitude of the current bin
float enhancedMagnitude = magnitude * harmonic;
// Update the real and imaginary parts with the modified magnitude
fftData[i] = enhancedMagnitude * (realPart / magnitude); // Ensure phase consistency
fftData[i + 1] = enhancedMagnitude * (imagPart / magnitude);
}
}
void applySpectralSaturation(float* fftData, size_t size, float saturationFactor)
{
for (size_t i = 0; i < size; i += 2)
{
// Real and Imaginary parts of the complex number
float realPart = fftData[i];
float imagPart = fftData[i + 1];
// Calculate the magnitude
float magnitude = sqrt(realPart * realPart + imagPart * imagPart);
// Apply spectral saturation (non-linear distortion)
float saturatedMagnitude = tanh(magnitude * saturationFactor) / magnitude;
// Update the real and imaginary parts with the modified magnitude
fftData[i] = saturatedMagnitude * (realPart / magnitude); // Ensure phase consistency
fftData[i + 1] = saturatedMagnitude * (imagPart / magnitude);
}
}
void applySpectralChorus(float* fftData, size_t size, float chorusDepth, float chorusRate, float chorusWidth)
{
for (size_t i = 0; i < size; i += 2)
{
// Real and Imaginary parts of the complex number
float realPart = fftData[i];
float imagPart = fftData[i + 1];
// Calculate the magnitude
float magnitude = sqrt(realPart * realPart + imagPart * imagPart);
// Calculate the frequency of the current bin
float frequency = static_cast<float>(i / 2) / size;
// Apply spectral chorus modulation
float delay = chorusDepth * sin(2.0f * M_PI * chorusRate * frequency);
// Calculate the delayed bin index
size_t delayedBin = static_cast<size_t>((frequency + delay * chorusWidth) * size * 2);
// Interpolate the delayed magnitude from adjacent bins
float delayedMagnitude = magnitude * (1.0f - chorusWidth) * fftData[delayedBin] +
magnitude * chorusWidth * fftData[delayedBin + 2];
// Update the real and imaginary parts with the modified magnitude
fftData[i] = delayedMagnitude * (realPart / magnitude); // Ensure phase consistency
fftData[i + 1] = delayedMagnitude * (imagPart / magnitude);
}
}
void applySpectralReverb(float* fftData, size_t size, float reverbIntensity)
{
// Simulate a simple reverb by adding delayed and scaled versions of the signal
for (size_t i = 0; i < size; i += 2)
{
// Real and Imaginary parts of the complex number
float realPart = fftData[i];
float imagPart = fftData[i + 1];
// Calculate the magnitude
float magnitude = sqrt(realPart * realPart + imagPart * imagPart);
// Apply spectral reverb
// Simulate a simple reverb by adding a delayed and scaled version of the signal
size_t delayAmount = static_cast<size_t>(mod_ * size * 2);
if (i + delayAmount < size)
{
// Apply reverb only within the bounds of the array
float delayedRealPart = fftData[i + delayAmount];
float delayedImagPart = fftData[i + delayAmount + 1];
// Scale the delayed signal to control the reverb intensity
float scaledRealPart = delayedRealPart * reverbIntensity;
float scaledImagPart = delayedImagPart * reverbIntensity;
// Add the delayed and scaled signal to the original signal
fftData[i] += scaledRealPart;
fftData[i + 1] += scaledImagPart;
}
// Ensure the magnitude is within bounds to avoid distortion
if (magnitude > 1.0f)
{
fftData[i] /= magnitude;
fftData[i + 1] /= magnitude;
}
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment