Skip to content

Commit bc770e2

Browse files
committed
DRAFT FFT Effects
1 parent 784e531 commit bc770e2

File tree

5 files changed

+229
-6
lines changed

5 files changed

+229
-6
lines changed

src/AudioTools.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
#include "AudioTools/CoreAudio/AudioEffects/SoundGenerator.h"
9595
#include "AudioTools/CoreAudio/AudioEffects/AudioEffects.h"
9696
#include "AudioTools/CoreAudio/AudioEffects/PitchShift.h"
97+
#include "AudioTools/CoreAudio/AudioEffects/FFTEffects.h"
9798
#include "AudioTools/CoreAudio/AudioMetaData/MetaData.h"
9899
#include "AudioTools/CoreAudio/AudioHttp/AudioHttp.h"
99100
#include "AudioTools/CoreAudio/Fade.h"

src/AudioTools/AudioLibs/AudioFFT.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ struct AudioFFTConfig : public AudioInfo {
5757
WindowFunction *window_function_ifft = nullptr;
5858
/// TX_MODE = FFT, RX_MODE = IFFT
5959
RxTxMode rxtx_mode = TX_MODE;
60+
/// caller
61+
void* ref = nullptr;
6062
};
6163

6264
/// And individual FFT Bin
@@ -427,9 +429,6 @@ class AudioFFTBase : public AudioStream {
427429
return atan2(fft_bin.img, fft_bin.real);
428430
}
429431

430-
431-
432-
433432
/// Provides the magnitudes as array of size size(). Please note that this
434433
/// method is allocating additinal memory!
435434
float *magnitudes() {

src/AudioTools/AudioLibs/FFT/FFTReal.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ To Public License, Version 2, as published by Sam Hocevar. See
1515
#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion"
1616

1717
// We use the DefaultAllocator which supports PSRAM
18-
#define FFT_CUSTOM_ALLOC DefaultAllocator
18+
#include "AudioTools/CoreAudio/AudioBasic/Collections/Allocator.h"
19+
#define FFT_CUSTOM_ALLOC audio_tools::DefaultAllocator
1920

2021

2122
#if ! defined (ffft_FFTReal_HEADER_INCLUDED)

src/AudioTools/AudioLibs/FFT/FFTWindows.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,6 @@ class WindowFunction {
6767
class BufferedWindow : public WindowFunction {
6868
public:
6969
BufferedWindow(WindowFunction* wf) { p_wf = wf; }
70-
BufferedWindow(BufferedWindow const&) = delete;
71-
BufferedWindow& operator=(BufferedWindow const&) = delete;
7270

7371
virtual void begin(int samples) override {
7472
// process only if there is a change
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
#pragma once
2+
3+
#include "AudioTools/AudioLibs/AudioRealFFT.h" // using RealFFT
4+
#include "AudioTools/CoreAudio/AudioOutput.h"
5+
#include "AudioTools/CoreAudio/StreamCopy.h"
6+
7+
namespace audio_tools {
8+
9+
/**
10+
* @brief Common configuration for FFT effects
11+
* @ingroup transform
12+
* @author phil schatzmann
13+
*/
14+
struct FFTEffectConfig : public AudioInfo {
15+
int length = 1024;
16+
int stride = 512;
17+
WindowFunction *window_function = &buffered_window;
18+
19+
protected:
20+
Hann hann;
21+
BufferedWindow buffered_window{&hann};
22+
};
23+
24+
/***
25+
* @brief Abstract class for common Logic for FFT based effects. The effect is
26+
* applied after the fft to the frequency domain before executing the ifft.
27+
* @ingroup transform
28+
* @author phil schatzmann
29+
*/
30+
31+
class FFTEffect : public AudioOutput {
32+
public:
33+
FFTEffect(Print &out) {
34+
p_out = &out;
35+
fft_cfg.ref = this;
36+
}
37+
38+
FFTEffectConfig defaultConfig() {
39+
FFTEffectConfig c;
40+
return c;
41+
}
42+
43+
bool begin(FFTEffectConfig info) {
44+
setAudioInfo(info);
45+
fft_cfg.length = info.length;
46+
fft_cfg.stride = info.stride;
47+
fft_cfg.window_function = info.window_function;
48+
return begin();
49+
}
50+
51+
bool begin() override {
52+
// copy result to output
53+
copier.begin(*p_out, fft);
54+
55+
// setup fft
56+
fft_cfg.copyFrom(audioInfo());
57+
fft_cfg.length = 1024;
58+
fft_cfg.stride = 512;
59+
fft_cfg.window_function = &buffered;
60+
fft_cfg.callback = effect_callback;
61+
return fft.begin(fft_cfg);
62+
}
63+
64+
size_t write(const uint8_t *data, size_t len) override {
65+
return fft.write(data, len);
66+
}
67+
68+
protected:
69+
Print *p_out = nullptr;
70+
AudioRealFFT fft;
71+
AudioFFTConfig fft_cfg{fft.defaultConfig()};
72+
Hann hann;
73+
BufferedWindow buffered{&hann};
74+
StreamCopy copier;
75+
76+
virtual void effect(AudioFFTBase &fft) = 0;
77+
78+
static void effect_callback(AudioFFTBase &fft) {
79+
FFTEffect *ref = (FFTEffect *)fft.config().ref;
80+
// execute effect
81+
ref->effect(fft);
82+
// write ifft to output
83+
ref->processOutput();
84+
}
85+
86+
void processOutput() { while (copier.copy()); }
87+
};
88+
89+
/**
90+
* @brief Apply Robotize FFT Effect on frequency domain data. See
91+
* https://learn.bela.io/tutorials/c-plus-plus-for-real-time-audio-programming/phase-vocoder-part-3/
92+
* @ingroup transform
93+
* @author phil schatzmann
94+
*/
95+
class FFTRobotize : public FFTEffect {
96+
friend FFTEffect;
97+
98+
public:
99+
FFTRobotize(AudioStream &out) : FFTEffect(out) { addNotifyAudioChange(out); };
100+
FFTRobotize(AudioOutput &out) : FFTEffect(out) { addNotifyAudioChange(out); };
101+
FFTRobotize(Print &out) : FFTEffect(out) {};
102+
103+
protected:
104+
/// Robotise the output
105+
void effect(AudioFFTBase &fft) {
106+
FFTBin bin;
107+
for (int n = 0; n < fft.size(); n++) {
108+
float amplitude = fft.magnitudeFast(n);
109+
// update new bin value
110+
bin.real = amplitude;
111+
bin.img = 0;
112+
fft.setBin(n, bin);
113+
}
114+
}
115+
};
116+
117+
/**
118+
* @brief Apply Robotize FFT Effect on frequency domain data. See
119+
* https://learn.bela.io/tutorials/c-plus-plus-for-real-time-audio-programming/phase-vocoder-part-3/
120+
* @ingroup transform
121+
* @author phil schatzmann
122+
*/
123+
class FFTWhisper : public FFTEffect {
124+
friend FFTEffect;
125+
126+
public:
127+
FFTWhisper(AudioStream &out) : FFTEffect(out) { addNotifyAudioChange(out); };
128+
FFTWhisper(AudioOutput &out) : FFTEffect(out) { addNotifyAudioChange(out); };
129+
FFTWhisper(Print &out) : FFTEffect(out) {};
130+
131+
protected:
132+
/// Robotise the output
133+
void effect(AudioFFTBase &fft) {
134+
FFTBin bin;
135+
for (int n = 0; n < fft.size(); n++) {
136+
float amplitude = fft.magnitudeFast(n);
137+
float phase = rand() / (float)RAND_MAX * 2.f * M_PI;
138+
139+
// update new bin value
140+
bin.real = cosf(phase) * amplitude;
141+
bin.img = sinf(phase) * amplitude;
142+
fft.setBin(n, bin);
143+
}
144+
}
145+
};
146+
147+
/**
148+
* @brief Pitch Shift FFT Effect Configuration
149+
* @ingroup transform
150+
* @author phil schatzmann
151+
*/
152+
153+
struct FFTPitchShiftConfig : public FFTEffectConfig {
154+
int shift = 1;
155+
};
156+
157+
/**
158+
* @brief Apply Pitch Shift FFT Effect on frequency domain data: we just move
159+
* the bins up or down
160+
* @ingroup transform
161+
* @author phil schatzmann
162+
*/
163+
class FFTPitchShift : public FFTEffect {
164+
friend FFTEffect;
165+
166+
public:
167+
FFTPitchShift(AudioStream &out) : FFTEffect(out) {
168+
addNotifyAudioChange(out);
169+
};
170+
FFTPitchShift(AudioOutput &out) : FFTEffect(out) {
171+
addNotifyAudioChange(out);
172+
};
173+
FFTPitchShift(Print &out) : FFTEffect(out) {};
174+
175+
FFTPitchShiftConfig defaultConfig() {
176+
FFTPitchShiftConfig result;
177+
return result;
178+
}
179+
180+
bool begin(FFTPitchShiftConfig psConfig) {
181+
setShift(psConfig.shift);
182+
return FFTEffect::begin(psConfig);
183+
}
184+
185+
/// defines how many bins should be shifted up (>0) or down (<0);
186+
void setShift(int bins) { shift = bins; }
187+
188+
protected:
189+
int shift = 1;
190+
191+
/// Pitch Shift
192+
void effect(AudioFFTBase &fft) {
193+
FFTBin bin;
194+
int max = fft.size();
195+
196+
if (shift < 0) {
197+
// copy bins: left shift
198+
for (int n = -shift; n < max; n++) {
199+
int to_bin = n + shift;
200+
fft.getBin(n, bin);
201+
fft.setBin(to_bin, bin);
202+
}
203+
// clear tail
204+
bin.clear();
205+
for (int n = max + shift; n < max; n++) {
206+
fft.setBin(n, bin);
207+
}
208+
} else if (shift > 0) {
209+
// copy bins: right shift
210+
for (int n = max - shift; n <= 0; n--) {
211+
int to_bin = n + shift;
212+
fft.getBin(n, bin);
213+
fft.setBin(to_bin, bin);
214+
}
215+
// clear head
216+
bin.clear();
217+
for (int n = 0; n < shift; n++) {
218+
fft.setBin(n, bin);
219+
}
220+
}
221+
}
222+
};
223+
224+
} // namespace audio_tools

0 commit comments

Comments
 (0)