Skip to content

Commit 91eea9f

Browse files
committed
example fft-effect
1 parent 288bf19 commit 91eea9f

File tree

10 files changed

+93635
-46
lines changed

10 files changed

+93635
-46
lines changed

src/AudioTools/AudioLibs/AudioFFT.h

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ class FFTInverseOverlapAdder {
108108
if (window_function != nullptr) {
109109
add_value = value * window_function->factor(pos);
110110
}
111+
assert(pos < len);
111112
data[pos] += add_value;
112113
}
113114

@@ -228,7 +229,8 @@ class AudioFFTBase : public AudioStream {
228229
if (cfg.window_function_fft != nullptr) {
229230
cfg.window_function_fft->begin(cfg.length);
230231
}
231-
if (cfg.window_function_ifft != nullptr) {
232+
if (cfg.window_function_ifft != nullptr
233+
&& cfg.window_function_ifft != cfg.window_function_fft) {
232234
cfg.window_function_ifft->begin(cfg.length);
233235
}
234236

@@ -457,14 +459,13 @@ class AudioFFTBase : public AudioStream {
457459
bool setBin(int idx, float real, float img) {
458460
has_rfft_data = true;
459461
if (idx < 0 || idx >= size()) return false;
460-
bool rc = p_driver->setBin(idx, real, img);
461-
bool rc1 = p_driver->setBin(cfg.length - idx, real, img);
462-
return rc && rc1;
462+
bool rc_first_half = p_driver->setBin(idx, real, img);
463+
bool rc_2nd_half = p_driver->setBin(cfg.length - idx, real, img);
464+
return rc_first_half && rc_2nd_half;
463465
}
464466
/// sets the value of a bin
465467
bool setBin(int pos, FFTBin &bin) {
466-
has_rfft_data = true;
467-
return p_driver->setBin(pos, bin.real, bin.img);
468+
return setBin(pos, bin.real, bin.img);
468469
}
469470
/// gets the value of a bin
470471
bool getBin(int pos, FFTBin &bin) { return p_driver->getBin(pos, bin); }
@@ -483,15 +484,15 @@ class AudioFFTBase : public AudioStream {
483484
protected:
484485
FFTDriver *p_driver = nullptr;
485486
int current_pos = 0;
486-
AudioFFTConfig cfg;
487+
int bins = 0;
487488
unsigned long timestamp_begin = 0l;
488489
unsigned long timestamp = 0l;
489-
SingleBuffer<uint8_t> stride_buffer{0};
490+
AudioFFTConfig cfg;
491+
FFTInverseOverlapAdder rfft_add{0};
490492
Vector<float> l_magnitudes{0};
491493
Vector<float> step_data{0};
492-
int bins = 0;
494+
SingleBuffer<uint8_t> stride_buffer{0};
493495
RingBuffer<uint8_t> rfft_data{0};
494-
FFTInverseOverlapAdder rfft_add{0};
495496
bool has_rfft_data = false;
496497

497498
// Add samples to input data p_x - and process them if full
@@ -621,6 +622,7 @@ class AudioFFTBase : public AudioStream {
621622

622623
// adds samples to stride buffer, returns true if the buffer is full
623624
bool writeStrideBuffer(uint8_t *buffer, size_t len) {
625+
assert(stride_buffer.availableForWrite() >= len);
624626
stride_buffer.writeArray(buffer, len);
625627
return stride_buffer.isFull();
626628
}

src/AudioTools/AudioLibs/AudioRealFFT.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class FFTDriverRealFFT : public FFTDriver {
5858

5959
/// magnitude w/o sqrt
6060
float magnitudeFast(int idx) override {
61-
return (v_x[idx] * v_x[idx] + v_f[idx] * v_f[idx]);
61+
return ((v_x[idx] * v_x[idx]) + (v_f[idx] * v_f[idx]));
6262
}
6363

6464
bool isValid() override{ return p_fft_object!=nullptr; }

src/AudioTools/AudioLibs/FFT/FFTWindows.h

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,13 @@ class WindowFunction {
3131
this->i_half_samples = samples / 2;
3232
}
3333

34-
/// Provides the multipication factor at the indicated position. The result is symetrically mirrored around the center
34+
/// Provides the multipication factor at the indicated position. The result is
35+
/// symetrically mirrored around the center
3536
inline float factor(int idx) {
36-
float result = idx <= i_half_samples ? factor_internal(idx) : factor_internal(i_samples-idx-1);
37-
return result>1.0f ? 1.0f : result;
37+
assert(i_half_samples == i_samples / 2);
38+
float result = idx <= i_half_samples ? factor_internal(idx)
39+
: factor_internal(i_samples - idx - 1);
40+
return result > 1.0f ? 1.0f : result;
3841
}
3942

4043
/// Provides the number of samples (fft length)
@@ -57,7 +60,6 @@ class WindowFunction {
5760
inline float ratio(int idx) {
5861
return (static_cast<float>(idx)) / samples_minus_1;
5962
}
60-
6163
};
6264

6365
/**
@@ -70,28 +72,30 @@ class BufferedWindow : public WindowFunction {
7072
public:
7173
BufferedWindow(WindowFunction* wf) { p_wf = wf; }
7274

73-
const char* name() {
75+
const char* name() {
7476
static char buffer[80] = "Buffered ";
75-
strncpy(buffer+9, p_wf->name(),69);
76-
return buffer;
77+
strncpy(buffer + 9, p_wf->name(), 69);
78+
return buffer;
7779
}
7880

7981
virtual void begin(int samples) override {
8082
// process only if there is a change
8183
WindowFunction::begin(samples);
8284
if (p_wf->samples() != samples) {
8385
p_wf->begin(samples);
84-
buffer.resize(i_half_samples + 1);
85-
for (int j = 0; j <= i_half_samples; j++) {
86-
buffer[j] = p_wf->factor(j);
86+
int to_be_size = i_half_samples + 1;
87+
if (buffer.size() != to_be_size) {
88+
buffer.resize(to_be_size);
89+
for (int j = 0; j <= i_half_samples; j++) {
90+
buffer[j] = p_wf->factor(j);
91+
}
8792
}
8893
}
8994
}
9095

9196
protected:
9297
WindowFunction* p_wf = nullptr;
9398
Vector<float> buffer{0};
94-
int len;
9599

96100
float factor_internal(int idx) override {
97101
if (idx < 0 || idx > i_half_samples) return 0.0;
@@ -107,11 +111,12 @@ class BufferedWindow : public WindowFunction {
107111
class Rectange : public WindowFunction {
108112
public:
109113
Rectange() = default;
110-
float factor_internal(int idx) {
114+
float factor_internal(int idx) {
111115
if (idx < 0 || idx >= i_samples) return 0;
112-
return 1.0f; }
113-
const char* name() { return "Rectange"; }
114-
};
116+
return 1.0f;
117+
}
118+
const char* name() { return "Rectange"; }
119+
};
115120

116121
/**
117122
* @brief Hamming FFT Window function
@@ -136,7 +141,7 @@ class Hann : public WindowFunction {
136141
public:
137142
Hann() = default;
138143
const char* name() { return "Hann"; }
139-
144+
140145
float factor_internal(int idx) {
141146
return 0.54f * (1.0f - cos(twoPi * ratio(idx)));
142147
}
@@ -153,8 +158,8 @@ class Triangle : public WindowFunction {
153158
const char* name() { return "Triangle"; }
154159
float factor_internal(int idx) {
155160
return 1.0f - ((2.0f * fabs((idx - 1) -
156-
(static_cast<float>(i_samples - 1) / 2.0f))) /
157-
samples_minus_1);
161+
(static_cast<float>(i_samples - 1) / 2.0f))) /
162+
samples_minus_1);
158163
}
159164
};
160165

@@ -185,7 +190,7 @@ class Blackman : public WindowFunction {
185190
public:
186191
Blackman() = default;
187192
const char* name() { return "Blackman"; }
188-
float factor_internal(int idx)override {
193+
float factor_internal(int idx) override {
189194
float r = ratio(idx);
190195
return 0.42323f - (0.49755f * (cos(twoPi * r))) +
191196
(0.07922f * (cos(fourPi * r)));
@@ -201,7 +206,7 @@ class BlackmanNuttall : public WindowFunction {
201206
public:
202207
BlackmanNuttall() = default;
203208
const char* name() { return "BlackmanNuttall"; }
204-
float factor_internal(int idx) override{
209+
float factor_internal(int idx) override {
205210
float r = ratio(idx);
206211
return 0.3635819f - (0.4891775f * (cos(twoPi * r))) +
207212
(0.1365995f * (cos(fourPi * r))) - (0.0106411f * (cos(sixPi * r)));
@@ -217,7 +222,7 @@ class BlackmanHarris : public WindowFunction {
217222
public:
218223
BlackmanHarris() = default;
219224
const char* name() { return "BlackmanHarris"; }
220-
float factor_internal(int idx) override{
225+
float factor_internal(int idx) override {
221226
float r = ratio(idx);
222227
return 0.35875f - (0.48829f * (cos(twoPi * r))) +
223228
(0.14128f * (cos(fourPi * r))) - (0.01168f * (cos(sixPi * r)));
@@ -233,7 +238,7 @@ class FlatTop : public WindowFunction {
233238
public:
234239
FlatTop() = default;
235240
const char* name() { return "FlatTop"; }
236-
float factor_internal(int idx) override{
241+
float factor_internal(int idx) override {
237242
float r = ratio(idx);
238243
return 0.2810639f - (0.5208972f * cos(twoPi * r)) +
239244
(0.1980399f * cos(fourPi * r));
@@ -249,9 +254,10 @@ class Welch : public WindowFunction {
249254
public:
250255
Welch() = default;
251256
const char* name() { return "Welch"; }
252-
float factor_internal(int idx) override{
253-
float tmp = (((idx - 1) - samples_minus_1 / 2.0f) / (samples_minus_1 / 2.0f));
254-
return 1.0f - (tmp*tmp);
257+
float factor_internal(int idx) override {
258+
float tmp =
259+
(((idx - 1) - samples_minus_1 / 2.0f) / (samples_minus_1 / 2.0f));
260+
return 1.0f - (tmp * tmp);
255261
}
256262
};
257263

src/AudioTools/CoreAudio/AudioEffects/FFTEffects.h

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
namespace audio_tools {
88

9+
static Hann fft_effects_hann;
10+
static BufferedWindow fft_effects_buffered_window{&fft_effects_hann};
11+
912
/**
1013
* @brief Common configuration for FFT effects
1114
* @ingroup transform
@@ -14,11 +17,7 @@ namespace audio_tools {
1417
struct FFTEffectConfig : public AudioInfo {
1518
int length = 1024;
1619
int stride = 512;
17-
WindowFunction *window_function = &buffered_window;
18-
19-
protected:
20-
Hann hann;
21-
BufferedWindow buffered_window{&hann};
20+
WindowFunction *window_function = &fft_effects_buffered_window;
2221
};
2322

2423
/***
@@ -59,7 +58,9 @@ class FFTEffect : public AudioOutput {
5958
fft_cfg.callback = effect_callback;
6059
LOGI("length: %d", fft_cfg.length);
6160
LOGI("stride: %d", fft_cfg.stride);
62-
LOGI("window_function: %s", (fft_cfg.window_function!=nullptr) ? fft_cfg.window_function->name() : "-");
61+
LOGI("window_function: %s", (fft_cfg.window_function != nullptr)
62+
? fft_cfg.window_function->name()
63+
: "-");
6364
return fft.begin(fft_cfg);
6465
}
6566

@@ -111,12 +112,17 @@ class FFTRobotize : public FFTEffect {
111112
/// Robotise the output
112113
void effect(AudioFFTBase &fft) {
113114
TRACED();
115+
AudioFFTResult best = fft.result();
116+
114117
FFTBin bin;
115118
for (int n = 0; n < fft.size(); n++) {
116119
float amplitude = fft.magnitude(n);
120+
117121
// update new bin value
118-
bin.real = amplitude;
119-
bin.img = 0;
122+
bin.real = amplitude / best.magnitude;
123+
bin.img = 0.0;
124+
Serial.println(bin.real);
125+
120126
fft.setBin(n, bin);
121127
}
122128
}
@@ -153,6 +159,25 @@ class FFTWhisper : public FFTEffect {
153159
}
154160
};
155161

162+
/**
163+
* @brief Apply FFT and IFFT w/o any changes to the frequency domain
164+
* @ingroup transform
165+
* @author phil schatzmann
166+
*/
167+
168+
class FFTNop : public FFTEffect {
169+
friend FFTEffect;
170+
171+
public:
172+
FFTNop(AudioStream &out) : FFTEffect(out) { addNotifyAudioChange(out); };
173+
FFTNop(AudioOutput &out) : FFTEffect(out) { addNotifyAudioChange(out); };
174+
FFTNop(Print &out) : FFTEffect(out) {};
175+
176+
protected:
177+
/// Do nothing
178+
void effect(AudioFFTBase &fft) {}
179+
};
180+
156181
/**
157182
* @brief Pitch Shift FFT Effect Configuration
158183
* @ingroup transform
@@ -189,7 +214,15 @@ class FFTPitchShift : public FFTEffect {
189214

190215
bool begin(FFTPitchShiftConfig psConfig) {
191216
setShift(psConfig.shift);
192-
return FFTEffect::begin(psConfig);
217+
FFTEffect::begin(psConfig);
218+
return begin();
219+
}
220+
221+
bool begin() override {
222+
bool rc = FFTEffect::begin();
223+
// you can not shift more then you have bins
224+
assert(abs(shift) < fft.size());
225+
return rc;
193226
}
194227

195228
/// defines how many bins should be shifted up (>0) or down (<0);
@@ -220,7 +253,7 @@ class FFTPitchShift : public FFTEffect {
220253
}
221254
} else if (shift > 0) {
222255
// copy bins: right shift
223-
for (int n = max - shift; n <= 0; n--) {
256+
for (int n = max - shift - 1; n >= 0; n--) {
224257
int to_bin = n + shift;
225258
assert(to_bin >= 0);
226259
assert(to_bin < max);

tests-cmake/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/generator ${CMAKE_CURRENT_BINARY_DI
2525
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/effects ${CMAKE_CURRENT_BINARY_DIR}/effects)
2626
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/fft ${CMAKE_CURRENT_BINARY_DIR}/fft)
2727
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/ifft ${CMAKE_CURRENT_BINARY_DIR}/ifft)
28+
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/fft-effect ${CMAKE_CURRENT_BINARY_DIR}/fft-effect)
2829
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/filter ${CMAKE_CURRENT_BINARY_DIR}/filter)
2930
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/filter-wav ${CMAKE_CURRENT_BINARY_DIR}/filter-wav)
3031
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/url-test ${CMAKE_CURRENT_BINARY_DIR}/url-test)

tests-cmake/fft-effect/CMakeLists.txt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
cmake_minimum_required(VERSION 3.20)
2+
3+
4+
# set the project name
5+
project(fft-effect)
6+
set (CMAKE_CXX_STANDARD 11)
7+
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -ldl -lpthread -lm -fno-omit-frame-pointer -fsanitize=address")
8+
set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0 -ldl -lpthread -lm -fno-omit-frame-pointer -fsanitize=address")
9+
set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
10+
11+
# Emulator is not necessary for -DIS_MIN_DESKTOP
12+
set(ADD_ARDUINO_EMULATOR OFF CACHE BOOL "Add Arduino Emulator Library")
13+
set(ADD_PORTAUDIO OFF CACHE BOOL "No Portaudio")
14+
15+
# Build with arduino-audio-tools
16+
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
17+
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../.. ${CMAKE_CURRENT_BINARY_DIR}/arduino-audio-tools )
18+
endif()
19+
20+
# Download miniaudio.h
21+
file(DOWNLOAD https://raw.githubusercontent.com/mackron/miniaudio/master/miniaudio.h
22+
${CMAKE_CURRENT_SOURCE_DIR}/miniaudio.h)
23+
24+
25+
# build sketch as executable
26+
add_executable (fft-effect fft-effect.cpp)
27+
28+
# set preprocessor defines
29+
target_compile_definitions(fft-effect PUBLIC -DIS_MIN_DESKTOP)
30+
31+
# specify libraries
32+
target_link_libraries(fft-effect arduino-audio-tools)
33+
34+
# access to miniaudio in sketch directory
35+
target_include_directories(fft-effect PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

0 commit comments

Comments
 (0)