Skip to content

Commit

Permalink
Merge pull request #11 from Gadgetoid/feature/display-plugins
Browse files Browse the repository at this point in the history
Significant refactor to support pluggable effects.
  • Loading branch information
Gadgetoid authored Feb 26, 2023
2 parents 640aa1a + f1ebcd0 commit c7ab8ae
Show file tree
Hide file tree
Showing 16 changed files with 479 additions and 210 deletions.
7 changes: 6 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()

include(bluetooth/bluetooth.cmake)
include(effect/rainbow_fft.cmake)
include(effect/classic_fft.cmake)

if(DISPLAY_PATH AND EXISTS ${CMAKE_CURRENT_LIST_DIR}/${DISPLAY_PATH})
include(${CMAKE_CURRENT_LIST_DIR}/${DISPLAY_PATH})
Expand All @@ -24,17 +26,20 @@ add_executable(${NAME}
${CMAKE_CURRENT_LIST_DIR}/src/main.cpp
${CMAKE_CURRENT_LIST_DIR}/src/a2dp_sink.cpp
${CMAKE_CURRENT_LIST_DIR}/src/btstack_audio_pico.cpp
${CMAKE_CURRENT_LIST_DIR}/src/fixed_fft.cpp
)

target_include_directories(${NAME} PRIVATE
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/effect
)

target_link_libraries(${NAME}
picow_bt_example_common
pico_audio_i2s
pico_multicore
display
rainbow_fft
classic_fft
)

message(WARNING "Display: ${DISPLAY_NAME}")
Expand Down
5 changes: 4 additions & 1 deletion display/cosmic/cosmic_unicorn.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ target_sources(display INTERFACE
${CMAKE_CURRENT_LIST_DIR}/cosmic_unicorn.cpp
)

target_include_directories(display INTERFACE ${CMAKE_CURRENT_LIST_DIR})
target_include_directories(display INTERFACE
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/../
)

# Pull in pico libraries that we need
target_link_libraries(display INTERFACE pico_stdlib hardware_adc hardware_pio hardware_dma)
Expand Down
13 changes: 3 additions & 10 deletions display/cosmic/display.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,26 +58,19 @@ class Display {
// must be aligned for 32bit dma transfer
alignas(4) uint8_t bitstream[BITSTREAM_LENGTH] = {0};
const uint32_t bitstream_addr = (uint32_t)bitstream;
void dma_safe_abort(uint channel);

public:
~Display();

void init();
static inline void pio_program_init(PIO pio, uint sm, uint offset);

void clear();

void update();
void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b);

void set_brightness(float value);
float get_brightness();
void adjust_brightness(float delta);

void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b);

uint16_t light();

bool is_pressed(uint8_t button);

private:
void dma_safe_abort(uint channel);
};
11 changes: 3 additions & 8 deletions display/galactic/display.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,24 +58,19 @@ class Display {
// must be aligned for 32bit dma transfer
alignas(4) uint8_t bitstream[BITSTREAM_LENGTH] = {0};
const uint32_t bitstream_addr = (uint32_t)bitstream;
void dma_safe_abort(uint channel);

public:
~Display();

void init();
static inline void pio_program_init(PIO pio, uint sm, uint offset);

void clear();

void update();
void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b);

void set_brightness(float value);
float get_brightness();
void adjust_brightness(float delta);

void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b);

uint16_t light();

private:
void dma_safe_abort(uint channel);
};
5 changes: 4 additions & 1 deletion display/galactic/galactic_unicorn.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ target_sources(display INTERFACE
${CMAKE_CURRENT_LIST_DIR}/galactic_unicorn.cpp
)

target_include_directories(display INTERFACE ${CMAKE_CURRENT_LIST_DIR})
target_include_directories(display INTERFACE
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/../
)

# Pull in pico libraries that we need
target_link_libraries(display INTERFACE pico_stdlib hardware_adc hardware_pio hardware_dma)
Expand Down
2 changes: 1 addition & 1 deletion display/galactic/galactic_unicorn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,6 @@ void Display::adjust_brightness(float delta) {
this->set_brightness(this->get_brightness() + delta);
}

void update() {
void Display::update() {
// do something here, probably do the FFT and write the display back buffer?
}
18 changes: 18 additions & 0 deletions effect/classic_fft.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
add_library(classic_fft INTERFACE)

target_sources(classic_fft INTERFACE
${CMAKE_CURRENT_LIST_DIR}/classic_fft.cpp
${CMAKE_CURRENT_LIST_DIR}/lib/fixed_fft.cpp
)

target_include_directories(classic_fft INTERFACE
${CMAKE_CURRENT_LIST_DIR}
)

# Choose one:
# SCALE_LOGARITHMIC
# SCALE_SQRT
# SCALE_LINEAR
target_compile_definitions(classic_fft INTERFACE
-DSCALE_SQRT
)
92 changes: 92 additions & 0 deletions effect/classic_fft.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#include "lib/rgb.hpp"
#include "effect.hpp"

void ClassicFFT::update(int16_t *buffer16, size_t sample_count) {
int16_t* fft_array = &fft.sample_array[SAMPLES_PER_AUDIO_BUFFER * (BUFFERS_PER_FFT_SAMPLE - 1)];
memmove(fft.sample_array, &fft.sample_array[SAMPLES_PER_AUDIO_BUFFER], (BUFFERS_PER_FFT_SAMPLE - 1) * sizeof(uint16_t));

for (auto i = 0u; i < SAMPLES_PER_AUDIO_BUFFER; i++) {
fft_array[i] = buffer16[i];
}

fft.update();

for (auto i = 0u; i < display.WIDTH; i++) {
fix15 sample = std::min(float_to_fix15(max_sample_from_fft), fft.get_scaled_as_fix15(i + FFT_SKIP_BINS));
uint8_t maxy = 0;

for (int j = 0; j < HISTORY_LEN; ++j) {
if (eq_history[i][j] > maxy) {
maxy = eq_history[i][j];
}
}

#ifdef SCALE_SQRT
fix15 subtract = subtract_step;
#endif
for (auto y = 0; y < display.HEIGHT; y++) {
uint8_t r = 0;
uint8_t g = 0;
uint8_t b = 0;
if (sample > int_to_fix15(lower_threshold)) {
r = (uint16_t)(palette[y].r);
g = (uint16_t)(palette[y].g);
b = (uint16_t)(palette[y].b);
#ifdef SCALE_LOGARITHMIC
sample = multiply_fix15_unit(multiple, sample);
#else
sample = std::max(1, sample - subtract);
#ifdef SCALE_SQRT
subtract += subtract_step;
#endif
#endif
}
else if (sample > 0) {
uint16_t int_sample = (uint16_t)fix15_to_int(sample);
r = std::min((uint16_t)(palette[y].r), int_sample);
g = std::min((uint16_t)(palette[y].g), int_sample);
b = std::min((uint16_t)(palette[y].b), int_sample);
eq_history[i][history_idx] = y;
sample = 0;
if (maxy < y) {
maxy = y;
}
} else if (y < maxy) {
r = (uint16_t)(palette[y].r) >> 3;
g = (uint16_t)(palette[y].g) >> 3;
b = (uint16_t)(palette[y].b) >> 3;
}
display.set_pixel(i, display.HEIGHT - 1 - y, r, g, b);
}
if (maxy > 0) {
RGB c = palette[display.HEIGHT - 1];
display.set_pixel(i, display.HEIGHT - 1 - maxy, c.r, c.g, c.b);
}
}
history_idx = (history_idx + 1) % HISTORY_LEN;
}

void ClassicFFT::init(uint32_t sample_frequency) {
printf("ClassicFFT: %ix%i\n", display.WIDTH, display.HEIGHT);

history_idx = 0;

fft.set_scale(display.HEIGHT * .318f);

for(auto i = 0u; i < display.HEIGHT; i++) {
int n = floor(i / 4) * 4;
float h = 0.4 * float(n) / display.HEIGHT;
h = 0.333 - h;
palette[i] = RGB::from_hsv(h, 1.0f, 1.0f);
}

max_sample_from_fft = 4000.f + 130.f * display.HEIGHT;
lower_threshold = 270 - 2 * display.HEIGHT;
#ifdef SCALE_LOGARITHMIC
multiple = float_to_fix15(pow(max_sample_from_fft / lower_threshold, -1.f / (display.HEIGHT - 1)));
#elif defined(SCALE_SQRT)
subtract_step = float_to_fix15((max_sample_from_fft - lower_threshold) * 2.f / (display.HEIGHT * (display.HEIGHT - 1)));
#elif defined(SCALE_LINEAR)
subtract = float_to_fix15((max_sample_from_fft - lower_threshold) / (display.HEIGHT - 1));
#endif
}
77 changes: 77 additions & 0 deletions effect/effect.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#pragma once
#include <functional>
#include "display.hpp"
#include "lib/fixed_fft.hpp"
#include "lib/rgb.hpp"

class Effect {
public:
Display &display;
FIX_FFT &fft;
Effect(Display& display, FIX_FFT& fft) :
display(display),
fft(fft) {};
virtual void init(uint32_t sample_frequency);
virtual void update(int16_t *buffer16, size_t sample_count);
};

class RainbowFFT : public Effect {
private:
// Number of FFT bins to skip on the left, the low frequencies tend to be pretty boring visually
static constexpr unsigned int FFT_SKIP_BINS = 1;
static constexpr unsigned int BUFFERS_PER_FFT_SAMPLE = 2;
static constexpr unsigned int SAMPLES_PER_AUDIO_BUFFER = SAMPLE_COUNT / BUFFERS_PER_FFT_SAMPLE;
static constexpr int HISTORY_LEN = 21; // About 0.25s
uint history_idx;
uint8_t eq_history[Display::WIDTH][HISTORY_LEN];

RGB palette_peak[Display::WIDTH];
RGB palette_main[Display::WIDTH];

float max_sample_from_fft;
int lower_threshold;
#ifdef SCALE_LOGARITHMIC
fix15 multiple;
#elif defined(SCALE_SQRT)
fix15 subtract_step;
#elif defined(SCALE_LINEAR)
fix15 subtract;
#else
#error "Choose a scale mode"
#endif

public:
RainbowFFT(Display& display, FIX_FFT& fft) : Effect(display, fft) {}
void update(int16_t *buffer16, size_t sample_count) override;
void init(uint32_t sample_frequency) override;
};

class ClassicFFT : public Effect {
private:
// Number of FFT bins to skip on the left, the low frequencies tend to be pretty boring visually
static constexpr unsigned int FFT_SKIP_BINS = 1;
static constexpr unsigned int BUFFERS_PER_FFT_SAMPLE = 2;
static constexpr unsigned int SAMPLES_PER_AUDIO_BUFFER = SAMPLE_COUNT / BUFFERS_PER_FFT_SAMPLE;
static constexpr int HISTORY_LEN = 21; // About 0.25s
uint history_idx;
uint8_t eq_history[Display::WIDTH][HISTORY_LEN];

RGB palette[Display::HEIGHT];

float max_sample_from_fft;
int lower_threshold;
#ifdef SCALE_LOGARITHMIC
fix15 multiple;
#elif defined(SCALE_SQRT)
fix15 subtract_step;
#elif defined(SCALE_LINEAR)
fix15 subtract;
#else
#error "Choose a scale mode"
#endif

public:
ClassicFFT(Display& display, FIX_FFT &fft) : Effect(display, fft) {}
void update(int16_t *buffer16, size_t sample_count) override;
void init(uint32_t sample_frequency) override;
};
25 changes: 18 additions & 7 deletions src/fixed_fft.cpp → effect/lib/fixed_fft.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,19 @@ uint16_t __always_inline __revs(uint16_t v) {
FIX_FFT::~FIX_FFT() {
}

int FIX_FFT::get_scaled(unsigned int i, unsigned int scale) {
return fix15_to_int(multiply_fix15(fr[i], int_to_fix15(scale)));
int FIX_FFT::get_scaled(unsigned int i) {
return fix15_to_int(multiply_fix15(fr[i], loudness_adjust[i]));
}

int FIX_FFT::get_scaled_fix15(unsigned int i, fix15 scale) {
return fix15_to_int(multiply_fix15(fr[i], scale));
int FIX_FFT::get_scaled_fix15(unsigned int i) {
return fix15_to_int(multiply_fix15(fr[i], loudness_adjust[i]));
}

int FIX_FFT::get_scaled_as_fix15(unsigned int i, fix15 scale) {
return multiply_fix15(fr[i], scale);
int FIX_FFT::get_scaled_as_fix15(unsigned int i) {
return multiply_fix15(fr[i], loudness_adjust[i]);
}

void FIX_FFT::init() {

// Populate Filter and Sine tables
for (auto ii = 0u; ii < SAMPLE_COUNT; ii++) {
// Full sine wave with period NUM_SAMPLES
Expand All @@ -46,6 +45,18 @@ void FIX_FFT::init() {
}
}

void FIX_FFT::set_scale(float scale) {
for (int i = 0; i < SAMPLE_COUNT; ++i) {
int freq = (sample_rate * 2) * (i) / SAMPLE_COUNT;
int j = 0;
while (loudness_lookup[j+1].freq < freq) {
++j;
}
float t = float(freq - loudness_lookup[j].freq) / float(loudness_lookup[j+1].freq - loudness_lookup[j].freq);
loudness_adjust[i] = float_to_fix15(scale * (t * loudness_lookup[j+1].multiplier + (1.f - t) * loudness_lookup[j].multiplier));
}
}

void FIX_FFT::update() {
float max_freq = 0;

Expand Down
Loading

0 comments on commit c7ab8ae

Please sign in to comment.