Skip to content

Commit c7ab8ae

Browse files
authored
Merge pull request #11 from Gadgetoid/feature/display-plugins
Significant refactor to support pluggable effects.
2 parents 640aa1a + f1ebcd0 commit c7ab8ae

16 files changed

+479
-210
lines changed

CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ set(CMAKE_CXX_STANDARD 17)
1212
pico_sdk_init()
1313

1414
include(bluetooth/bluetooth.cmake)
15+
include(effect/rainbow_fft.cmake)
16+
include(effect/classic_fft.cmake)
1517

1618
if(DISPLAY_PATH AND EXISTS ${CMAKE_CURRENT_LIST_DIR}/${DISPLAY_PATH})
1719
include(${CMAKE_CURRENT_LIST_DIR}/${DISPLAY_PATH})
@@ -24,17 +26,20 @@ add_executable(${NAME}
2426
${CMAKE_CURRENT_LIST_DIR}/src/main.cpp
2527
${CMAKE_CURRENT_LIST_DIR}/src/a2dp_sink.cpp
2628
${CMAKE_CURRENT_LIST_DIR}/src/btstack_audio_pico.cpp
27-
${CMAKE_CURRENT_LIST_DIR}/src/fixed_fft.cpp
2829
)
2930

3031
target_include_directories(${NAME} PRIVATE
3132
${CMAKE_CURRENT_LIST_DIR}
33+
${CMAKE_CURRENT_LIST_DIR}/effect
3234
)
3335

3436
target_link_libraries(${NAME}
3537
picow_bt_example_common
3638
pico_audio_i2s
39+
pico_multicore
3740
display
41+
rainbow_fft
42+
classic_fft
3843
)
3944

4045
message(WARNING "Display: ${DISPLAY_NAME}")

display/cosmic/cosmic_unicorn.cmake

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ target_sources(display INTERFACE
66
${CMAKE_CURRENT_LIST_DIR}/cosmic_unicorn.cpp
77
)
88

9-
target_include_directories(display INTERFACE ${CMAKE_CURRENT_LIST_DIR})
9+
target_include_directories(display INTERFACE
10+
${CMAKE_CURRENT_LIST_DIR}
11+
${CMAKE_CURRENT_LIST_DIR}/../
12+
)
1013

1114
# Pull in pico libraries that we need
1215
target_link_libraries(display INTERFACE pico_stdlib hardware_adc hardware_pio hardware_dma)

display/cosmic/display.hpp

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,26 +58,19 @@ class Display {
5858
// must be aligned for 32bit dma transfer
5959
alignas(4) uint8_t bitstream[BITSTREAM_LENGTH] = {0};
6060
const uint32_t bitstream_addr = (uint32_t)bitstream;
61+
void dma_safe_abort(uint channel);
62+
6163
public:
6264
~Display();
6365

6466
void init();
65-
static inline void pio_program_init(PIO pio, uint sm, uint offset);
66-
6767
void clear();
68-
6968
void update();
69+
void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b);
7070

7171
void set_brightness(float value);
7272
float get_brightness();
7373
void adjust_brightness(float delta);
7474

75-
void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b);
76-
7775
uint16_t light();
78-
79-
bool is_pressed(uint8_t button);
80-
81-
private:
82-
void dma_safe_abort(uint channel);
8376
};

display/galactic/display.hpp

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,24 +58,19 @@ class Display {
5858
// must be aligned for 32bit dma transfer
5959
alignas(4) uint8_t bitstream[BITSTREAM_LENGTH] = {0};
6060
const uint32_t bitstream_addr = (uint32_t)bitstream;
61+
void dma_safe_abort(uint channel);
62+
6163
public:
6264
~Display();
6365

6466
void init();
65-
static inline void pio_program_init(PIO pio, uint sm, uint offset);
66-
6767
void clear();
68-
6968
void update();
69+
void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b);
7070

7171
void set_brightness(float value);
7272
float get_brightness();
7373
void adjust_brightness(float delta);
7474

75-
void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b);
76-
7775
uint16_t light();
78-
79-
private:
80-
void dma_safe_abort(uint channel);
8176
};

display/galactic/galactic_unicorn.cmake

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ target_sources(display INTERFACE
66
${CMAKE_CURRENT_LIST_DIR}/galactic_unicorn.cpp
77
)
88

9-
target_include_directories(display INTERFACE ${CMAKE_CURRENT_LIST_DIR})
9+
target_include_directories(display INTERFACE
10+
${CMAKE_CURRENT_LIST_DIR}
11+
${CMAKE_CURRENT_LIST_DIR}/../
12+
)
1013

1114
# Pull in pico libraries that we need
1215
target_link_libraries(display INTERFACE pico_stdlib hardware_adc hardware_pio hardware_dma)

display/galactic/galactic_unicorn.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,6 @@ void Display::adjust_brightness(float delta) {
332332
this->set_brightness(this->get_brightness() + delta);
333333
}
334334

335-
void update() {
335+
void Display::update() {
336336
// do something here, probably do the FFT and write the display back buffer?
337337
}

effect/classic_fft.cmake

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
add_library(classic_fft INTERFACE)
2+
3+
target_sources(classic_fft INTERFACE
4+
${CMAKE_CURRENT_LIST_DIR}/classic_fft.cpp
5+
${CMAKE_CURRENT_LIST_DIR}/lib/fixed_fft.cpp
6+
)
7+
8+
target_include_directories(classic_fft INTERFACE
9+
${CMAKE_CURRENT_LIST_DIR}
10+
)
11+
12+
# Choose one:
13+
# SCALE_LOGARITHMIC
14+
# SCALE_SQRT
15+
# SCALE_LINEAR
16+
target_compile_definitions(classic_fft INTERFACE
17+
-DSCALE_SQRT
18+
)

effect/classic_fft.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#include "lib/rgb.hpp"
2+
#include "effect.hpp"
3+
4+
void ClassicFFT::update(int16_t *buffer16, size_t sample_count) {
5+
int16_t* fft_array = &fft.sample_array[SAMPLES_PER_AUDIO_BUFFER * (BUFFERS_PER_FFT_SAMPLE - 1)];
6+
memmove(fft.sample_array, &fft.sample_array[SAMPLES_PER_AUDIO_BUFFER], (BUFFERS_PER_FFT_SAMPLE - 1) * sizeof(uint16_t));
7+
8+
for (auto i = 0u; i < SAMPLES_PER_AUDIO_BUFFER; i++) {
9+
fft_array[i] = buffer16[i];
10+
}
11+
12+
fft.update();
13+
14+
for (auto i = 0u; i < display.WIDTH; i++) {
15+
fix15 sample = std::min(float_to_fix15(max_sample_from_fft), fft.get_scaled_as_fix15(i + FFT_SKIP_BINS));
16+
uint8_t maxy = 0;
17+
18+
for (int j = 0; j < HISTORY_LEN; ++j) {
19+
if (eq_history[i][j] > maxy) {
20+
maxy = eq_history[i][j];
21+
}
22+
}
23+
24+
#ifdef SCALE_SQRT
25+
fix15 subtract = subtract_step;
26+
#endif
27+
for (auto y = 0; y < display.HEIGHT; y++) {
28+
uint8_t r = 0;
29+
uint8_t g = 0;
30+
uint8_t b = 0;
31+
if (sample > int_to_fix15(lower_threshold)) {
32+
r = (uint16_t)(palette[y].r);
33+
g = (uint16_t)(palette[y].g);
34+
b = (uint16_t)(palette[y].b);
35+
#ifdef SCALE_LOGARITHMIC
36+
sample = multiply_fix15_unit(multiple, sample);
37+
#else
38+
sample = std::max(1, sample - subtract);
39+
#ifdef SCALE_SQRT
40+
subtract += subtract_step;
41+
#endif
42+
#endif
43+
}
44+
else if (sample > 0) {
45+
uint16_t int_sample = (uint16_t)fix15_to_int(sample);
46+
r = std::min((uint16_t)(palette[y].r), int_sample);
47+
g = std::min((uint16_t)(palette[y].g), int_sample);
48+
b = std::min((uint16_t)(palette[y].b), int_sample);
49+
eq_history[i][history_idx] = y;
50+
sample = 0;
51+
if (maxy < y) {
52+
maxy = y;
53+
}
54+
} else if (y < maxy) {
55+
r = (uint16_t)(palette[y].r) >> 3;
56+
g = (uint16_t)(palette[y].g) >> 3;
57+
b = (uint16_t)(palette[y].b) >> 3;
58+
}
59+
display.set_pixel(i, display.HEIGHT - 1 - y, r, g, b);
60+
}
61+
if (maxy > 0) {
62+
RGB c = palette[display.HEIGHT - 1];
63+
display.set_pixel(i, display.HEIGHT - 1 - maxy, c.r, c.g, c.b);
64+
}
65+
}
66+
history_idx = (history_idx + 1) % HISTORY_LEN;
67+
}
68+
69+
void ClassicFFT::init(uint32_t sample_frequency) {
70+
printf("ClassicFFT: %ix%i\n", display.WIDTH, display.HEIGHT);
71+
72+
history_idx = 0;
73+
74+
fft.set_scale(display.HEIGHT * .318f);
75+
76+
for(auto i = 0u; i < display.HEIGHT; i++) {
77+
int n = floor(i / 4) * 4;
78+
float h = 0.4 * float(n) / display.HEIGHT;
79+
h = 0.333 - h;
80+
palette[i] = RGB::from_hsv(h, 1.0f, 1.0f);
81+
}
82+
83+
max_sample_from_fft = 4000.f + 130.f * display.HEIGHT;
84+
lower_threshold = 270 - 2 * display.HEIGHT;
85+
#ifdef SCALE_LOGARITHMIC
86+
multiple = float_to_fix15(pow(max_sample_from_fft / lower_threshold, -1.f / (display.HEIGHT - 1)));
87+
#elif defined(SCALE_SQRT)
88+
subtract_step = float_to_fix15((max_sample_from_fft - lower_threshold) * 2.f / (display.HEIGHT * (display.HEIGHT - 1)));
89+
#elif defined(SCALE_LINEAR)
90+
subtract = float_to_fix15((max_sample_from_fft - lower_threshold) / (display.HEIGHT - 1));
91+
#endif
92+
}

effect/effect.hpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#pragma once
2+
#include <functional>
3+
#include "display.hpp"
4+
#include "lib/fixed_fft.hpp"
5+
#include "lib/rgb.hpp"
6+
7+
class Effect {
8+
public:
9+
Display &display;
10+
FIX_FFT &fft;
11+
Effect(Display& display, FIX_FFT& fft) :
12+
display(display),
13+
fft(fft) {};
14+
virtual void init(uint32_t sample_frequency);
15+
virtual void update(int16_t *buffer16, size_t sample_count);
16+
};
17+
18+
class RainbowFFT : public Effect {
19+
private:
20+
// Number of FFT bins to skip on the left, the low frequencies tend to be pretty boring visually
21+
static constexpr unsigned int FFT_SKIP_BINS = 1;
22+
static constexpr unsigned int BUFFERS_PER_FFT_SAMPLE = 2;
23+
static constexpr unsigned int SAMPLES_PER_AUDIO_BUFFER = SAMPLE_COUNT / BUFFERS_PER_FFT_SAMPLE;
24+
static constexpr int HISTORY_LEN = 21; // About 0.25s
25+
uint history_idx;
26+
uint8_t eq_history[Display::WIDTH][HISTORY_LEN];
27+
28+
RGB palette_peak[Display::WIDTH];
29+
RGB palette_main[Display::WIDTH];
30+
31+
float max_sample_from_fft;
32+
int lower_threshold;
33+
#ifdef SCALE_LOGARITHMIC
34+
fix15 multiple;
35+
#elif defined(SCALE_SQRT)
36+
fix15 subtract_step;
37+
#elif defined(SCALE_LINEAR)
38+
fix15 subtract;
39+
#else
40+
#error "Choose a scale mode"
41+
#endif
42+
43+
public:
44+
RainbowFFT(Display& display, FIX_FFT& fft) : Effect(display, fft) {}
45+
void update(int16_t *buffer16, size_t sample_count) override;
46+
void init(uint32_t sample_frequency) override;
47+
};
48+
49+
class ClassicFFT : public Effect {
50+
private:
51+
// Number of FFT bins to skip on the left, the low frequencies tend to be pretty boring visually
52+
static constexpr unsigned int FFT_SKIP_BINS = 1;
53+
static constexpr unsigned int BUFFERS_PER_FFT_SAMPLE = 2;
54+
static constexpr unsigned int SAMPLES_PER_AUDIO_BUFFER = SAMPLE_COUNT / BUFFERS_PER_FFT_SAMPLE;
55+
static constexpr int HISTORY_LEN = 21; // About 0.25s
56+
uint history_idx;
57+
uint8_t eq_history[Display::WIDTH][HISTORY_LEN];
58+
59+
RGB palette[Display::HEIGHT];
60+
61+
float max_sample_from_fft;
62+
int lower_threshold;
63+
#ifdef SCALE_LOGARITHMIC
64+
fix15 multiple;
65+
#elif defined(SCALE_SQRT)
66+
fix15 subtract_step;
67+
#elif defined(SCALE_LINEAR)
68+
fix15 subtract;
69+
#else
70+
#error "Choose a scale mode"
71+
#endif
72+
73+
public:
74+
ClassicFFT(Display& display, FIX_FFT &fft) : Effect(display, fft) {}
75+
void update(int16_t *buffer16, size_t sample_count) override;
76+
void init(uint32_t sample_frequency) override;
77+
};

src/fixed_fft.cpp renamed to effect/lib/fixed_fft.cpp

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,19 @@ uint16_t __always_inline __revs(uint16_t v) {
2020
FIX_FFT::~FIX_FFT() {
2121
}
2222

23-
int FIX_FFT::get_scaled(unsigned int i, unsigned int scale) {
24-
return fix15_to_int(multiply_fix15(fr[i], int_to_fix15(scale)));
23+
int FIX_FFT::get_scaled(unsigned int i) {
24+
return fix15_to_int(multiply_fix15(fr[i], loudness_adjust[i]));
2525
}
2626

27-
int FIX_FFT::get_scaled_fix15(unsigned int i, fix15 scale) {
28-
return fix15_to_int(multiply_fix15(fr[i], scale));
27+
int FIX_FFT::get_scaled_fix15(unsigned int i) {
28+
return fix15_to_int(multiply_fix15(fr[i], loudness_adjust[i]));
2929
}
3030

31-
int FIX_FFT::get_scaled_as_fix15(unsigned int i, fix15 scale) {
32-
return multiply_fix15(fr[i], scale);
31+
int FIX_FFT::get_scaled_as_fix15(unsigned int i) {
32+
return multiply_fix15(fr[i], loudness_adjust[i]);
3333
}
3434

3535
void FIX_FFT::init() {
36-
3736
// Populate Filter and Sine tables
3837
for (auto ii = 0u; ii < SAMPLE_COUNT; ii++) {
3938
// Full sine wave with period NUM_SAMPLES
@@ -46,6 +45,18 @@ void FIX_FFT::init() {
4645
}
4746
}
4847

48+
void FIX_FFT::set_scale(float scale) {
49+
for (int i = 0; i < SAMPLE_COUNT; ++i) {
50+
int freq = (sample_rate * 2) * (i) / SAMPLE_COUNT;
51+
int j = 0;
52+
while (loudness_lookup[j+1].freq < freq) {
53+
++j;
54+
}
55+
float t = float(freq - loudness_lookup[j].freq) / float(loudness_lookup[j+1].freq - loudness_lookup[j].freq);
56+
loudness_adjust[i] = float_to_fix15(scale * (t * loudness_lookup[j+1].multiplier + (1.f - t) * loudness_lookup[j].multiplier));
57+
}
58+
}
59+
4960
void FIX_FFT::update() {
5061
float max_freq = 0;
5162

0 commit comments

Comments
 (0)