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