From a0b64348c21e5beea0c3673591f4e8e1c30b5f34 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Sat, 25 Feb 2023 22:36:14 +0000 Subject: [PATCH 01/12] Significant refactor to support pluggable effects. --- CMakeLists.txt | 4 +- display/cosmic/cosmic_unicorn.cmake | 5 +- display/cosmic/display.hpp | 23 ++- display/displaybase.hpp | 12 ++ display/galactic/display.hpp | 21 ++- display/galactic/galactic_unicorn.cmake | 5 +- effect/effect.hpp | 104 ++++++++++++++ {src => effect/lib}/fixed_fft.cpp | 0 {src => effect/lib}/fixed_fft.hpp | 0 effect/lib/rgb.hpp | 28 ++++ effect/rainbow_fft.cmake | 20 +++ effect/rainbow_fft.cpp | 85 +++++++++++ src/btstack_audio_pico.cpp | 183 ++---------------------- 13 files changed, 288 insertions(+), 202 deletions(-) create mode 100644 display/displaybase.hpp create mode 100644 effect/effect.hpp rename {src => effect/lib}/fixed_fft.cpp (100%) rename {src => effect/lib}/fixed_fft.hpp (100%) create mode 100644 effect/lib/rgb.hpp create mode 100644 effect/rainbow_fft.cmake create mode 100644 effect/rainbow_fft.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b638f66..fdb906c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ set(CMAKE_CXX_STANDARD 17) pico_sdk_init() include(bluetooth/bluetooth.cmake) +include(effect/rainbow_fft.cmake) if(DISPLAY_PATH AND EXISTS ${CMAKE_CURRENT_LIST_DIR}/${DISPLAY_PATH}) include(${CMAKE_CURRENT_LIST_DIR}/${DISPLAY_PATH}) @@ -24,17 +25,18 @@ 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 display + rainbow_fft ) message(WARNING "Display: ${DISPLAY_NAME}") diff --git a/display/cosmic/cosmic_unicorn.cmake b/display/cosmic/cosmic_unicorn.cmake index 69c94d0..f49adf2 100644 --- a/display/cosmic/cosmic_unicorn.cmake +++ b/display/cosmic/cosmic_unicorn.cmake @@ -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) diff --git a/display/cosmic/display.hpp b/display/cosmic/display.hpp index 901adda..31a0021 100644 --- a/display/cosmic/display.hpp +++ b/display/cosmic/display.hpp @@ -1,8 +1,9 @@ #pragma once #include "hardware/pio.h" +#include "displaybase.hpp" -class Display { +class Display : public DisplayBase { public: static const int WIDTH = 32; static const int HEIGHT = 32; @@ -58,26 +59,22 @@ 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 init() override; + void clear() override; + void update() override; + void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) override; - void clear(); - - void update(); + const int get_width() override {return WIDTH;}; + const int get_height() override {return HEIGHT;}; 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); }; \ No newline at end of file diff --git a/display/displaybase.hpp b/display/displaybase.hpp new file mode 100644 index 0000000..be28fb4 --- /dev/null +++ b/display/displaybase.hpp @@ -0,0 +1,12 @@ +#pragma once +#include + +class DisplayBase { + public: + virtual void init(); + virtual void clear(); + virtual void update(); + virtual void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b); + virtual const int get_width(); + virtual const int get_height(); +}; \ No newline at end of file diff --git a/display/galactic/display.hpp b/display/galactic/display.hpp index e512949..b7f66f8 100644 --- a/display/galactic/display.hpp +++ b/display/galactic/display.hpp @@ -1,8 +1,9 @@ #pragma once #include "hardware/pio.h" +#include "displaybase.hpp" -class Display { +class Display : public DisplayBase { public: static const int WIDTH = 53; static const int HEIGHT = 11; @@ -58,24 +59,22 @@ 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 init() override; + void clear() override; + void update() override; + void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) override; - void clear(); - - void update(); + const int get_width() override {return WIDTH;}; + const int get_height() override {return HEIGHT;}; 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); }; \ No newline at end of file diff --git a/display/galactic/galactic_unicorn.cmake b/display/galactic/galactic_unicorn.cmake index 36bcc5c..1aece13 100644 --- a/display/galactic/galactic_unicorn.cmake +++ b/display/galactic/galactic_unicorn.cmake @@ -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) diff --git a/effect/effect.hpp b/effect/effect.hpp new file mode 100644 index 0000000..8819768 --- /dev/null +++ b/effect/effect.hpp @@ -0,0 +1,104 @@ +#pragma once +#include +#include "displaybase.hpp" +#include "lib/fixed_fft.hpp" +#include "lib/rgb.hpp" + +class Effect { + public: + DisplayBase &display; + Effect(DisplayBase& display) : display(display) {}; + 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[32][HISTORY_LEN]; + fix15 loudness_adjust[32]; + FIX_FFT fft; + RGB palette_peak[32]; + RGB palette_main[32]; + + 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 + + struct LoudnessLookup { + int freq; + float multiplier; + }; + + // Amplitude to loudness lookup at 20 phons + static constexpr LoudnessLookup loudness_lookup[] = { + { 20, 0.2232641215f }, + { 25, 0.241984271f }, + { 31, 0.263227165f }, + { 40, 0.2872737719f }, + { 50, 0.3124023743f }, + { 63, 0.341588386f }, + { 80, 0.3760105283f }, + { 100, 0.4133939644f }, + { 125, 0.4551661356f }, + { 160, 0.508001016f }, + { 200, 0.5632216277f }, + { 250, 0.6251953736f }, + { 315, 0.6971070059f }, + { 400, 0.7791195949f }, + { 500, 0.8536064874f }, + { 630, 0.9310986965f }, + { 800, 0.9950248756f }, + { 1000, 0.9995002499f }, + { 1250, 0.9319664492f }, + { 1600, 0.9345794393f }, + { 2000, 1.101928375f }, + { 2500, 1.300390117f }, + { 3150, 1.402524544f }, + { 4000, 1.321003963f }, + { 5000, 1.073537305f }, + { 6300, 0.7993605116f }, + { 8000, 0.6345177665f }, + { 10000, 0.5808887598f }, + { 12500, 0.6053268765f }, + { 20000, 0 } + }; + + void init_loudness(uint32_t sample_frequency); + + public: + RainbowFFT(DisplayBase& display) : Effect(display) { + history_idx = 0; + + for(auto i = 0u; i < display.get_width(); i++) { + float h = float(i) / display.get_width(); + palette_peak[i] = RGB::from_hsv(h, 0.7f, 1.0f); + palette_main[i] = RGB::from_hsv(h, 1.0f, 0.7f); + } + + float max_sample_from_fft = 4000.f + 130.f * display.get_height(); + int lower_threshold = 270 - 2 * display.get_height(); +#ifdef SCALE_LOGARITHMIC + fix15 multiple = float_to_fix15(pow(max_sample_from_fft / lower_threshold, -1.f / (display.get_height() - 1))); +#elif defined(SCALE_SQRT) + fix15 subtract_step = float_to_fix15((max_sample_from_fft - lower_threshold) * 2.f / (display.get_height() * (display.get_height() - 1))); +#elif defined(SCALE_LINEAR) + fix15 subtract = float_to_fix15((max_sample_from_fft - lower_threshold) / (display.get_height() - 1)); +#endif + } + void update(int16_t *buffer16, size_t sample_count) override; + void init(uint32_t sample_frequency) override; +}; diff --git a/src/fixed_fft.cpp b/effect/lib/fixed_fft.cpp similarity index 100% rename from src/fixed_fft.cpp rename to effect/lib/fixed_fft.cpp diff --git a/src/fixed_fft.hpp b/effect/lib/fixed_fft.hpp similarity index 100% rename from src/fixed_fft.hpp rename to effect/lib/fixed_fft.hpp diff --git a/effect/lib/rgb.hpp b/effect/lib/rgb.hpp new file mode 100644 index 0000000..8fe8c66 --- /dev/null +++ b/effect/lib/rgb.hpp @@ -0,0 +1,28 @@ +#pragma once +#include + +struct RGB { + uint8_t r, g, b; + + constexpr RGB() : r(0), g(0), b(0) {} + constexpr RGB(uint8_t r, uint8_t g, uint8_t b) : r(r), g(g), b(b) {} + + static RGB from_hsv(float h, float s, float v) { + int i = h * 6.0f; + float f = (h * 6.0f) - i; + v *= 255.0f; + uint8_t p = v * (1.0f - s); + uint8_t q = v * (1.0f - (f * s)); + uint8_t t = v * (1.0f - ((1.0f - f) * s)); + + switch (i % 6) { + default: + case 0: return RGB(v, t, p); + case 1: return RGB(q, v, p); + case 2: return RGB(p, v, t); + case 3: return RGB(p, q, v); + case 4: return RGB(t, p, v); + case 5: return RGB(v, p, q); + }; + } +}; \ No newline at end of file diff --git a/effect/rainbow_fft.cmake b/effect/rainbow_fft.cmake new file mode 100644 index 0000000..9b08fc9 --- /dev/null +++ b/effect/rainbow_fft.cmake @@ -0,0 +1,20 @@ +add_library(rainbow_fft INTERFACE) + +target_sources(rainbow_fft INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/rainbow_fft.cpp + ${CMAKE_CURRENT_LIST_DIR}/lib/fixed_fft.cpp +) + +target_include_directories(rainbow_fft INTERFACE + ${CMAKE_CURRENT_LIST_DIR} +) + +# Choose one: +# SCALE_LOGARITHMIC +# SCALE_SQRT +# SCALE_LINEAR +target_compile_definitions(rainbow_fft INTERFACE + -DSCALE_SQRT +) + +set(EFFECT_NAME "Rainbow FFT") \ No newline at end of file diff --git a/effect/rainbow_fft.cpp b/effect/rainbow_fft.cpp new file mode 100644 index 0000000..b0c0265 --- /dev/null +++ b/effect/rainbow_fft.cpp @@ -0,0 +1,85 @@ +#include "lib/rgb.hpp" +#include "effect.hpp" + +void RainbowFFT::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 < sample_count; i++) { + fft_array[i] = buffer16[i]; + } + + fft.update(); + for (auto i = 0u; i < display.get_width(); i++) { + fix15 sample = std::min(float_to_fix15(max_sample_from_fft), fft.get_scaled_as_fix15(i + FFT_SKIP_BINS, loudness_adjust[i])); + 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.get_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_main[i].r); + g = (uint16_t)(palette_main[i].g); + b = (uint16_t)(palette_main[i].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_main[i].r), int_sample); + g = std::min((uint16_t)(palette_main[i].g), int_sample); + b = std::min((uint16_t)(palette_main[i].b), int_sample); + eq_history[i][history_idx] = y; + sample = 0; + if (maxy < y) { + maxy = y; + } + } else if (y < maxy) { + r = (uint16_t)(palette_main[i].r) >> 2; + g = (uint16_t)(palette_main[i].g) >> 2; + b = (uint16_t)(palette_main[i].b) >> 2; + } + display.set_pixel(i, display.get_height() - 1 - y, r, g, b); + } + if (maxy > 0) { + RGB c = palette_peak[i]; + display.set_pixel(i, display.get_height() - 1 - maxy, c.r, c.g, c.b); + } + } + history_idx = (history_idx + 1) % HISTORY_LEN; +} + +void RainbowFFT::init_loudness(uint32_t sample_frequency) { + float scale = float(display.get_height()) * .318f; + + for (int i = 0; i < display.get_width(); ++i) { + int freq = (sample_frequency * 2) * (i + FFT_SKIP_BINS) / 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)); + printf("%d %d %f\n", i, freq, fix15_to_float(loudness_adjust[i])); + } +} + +void RainbowFFT::init(uint32_t sample_frequency) { + init_loudness(sample_frequency); +} \ No newline at end of file diff --git a/src/btstack_audio_pico.cpp b/src/btstack_audio_pico.cpp index 6417f3e..36523fc 100644 --- a/src/btstack_audio_pico.cpp +++ b/src/btstack_audio_pico.cpp @@ -57,109 +57,21 @@ #include "pico/stdlib.h" #include "display.hpp" -#include "fixed_fft.hpp" +#include "effect.hpp" #define DRIVER_POLL_INTERVAL_MS 5 -#define FFT_SKIP_BINS 1 // Number of FFT bins to skip on the left, the low frequencies tend to be pretty boring visually - -constexpr unsigned int BUFFERS_PER_FFT_SAMPLE = 2; -constexpr unsigned int SAMPLES_PER_AUDIO_BUFFER = SAMPLE_COUNT / BUFFERS_PER_FFT_SAMPLE; - -struct LoudnessLookup { - int freq; - float multiplier; -}; - -// Amplitude to loudness lookup at 20 phons -constexpr LoudnessLookup loudness_lookup[] = { - { 20, 0.2232641215f }, - { 25, 0.241984271f }, - { 31, 0.263227165f }, - { 40, 0.2872737719f }, - { 50, 0.3124023743f }, - { 63, 0.341588386f }, - { 80, 0.3760105283f }, - { 100, 0.4133939644f }, - { 125, 0.4551661356f }, - { 160, 0.508001016f }, - { 200, 0.5632216277f }, - { 250, 0.6251953736f }, - { 315, 0.6971070059f }, - { 400, 0.7791195949f }, - { 500, 0.8536064874f }, - { 630, 0.9310986965f }, - { 800, 0.9950248756f }, - { 1000, 0.9995002499f }, - { 1250, 0.9319664492f }, - { 1600, 0.9345794393f }, - { 2000, 1.101928375f }, - { 2500, 1.300390117f }, - { 3150, 1.402524544f }, - { 4000, 1.321003963f }, - { 5000, 1.073537305f }, - { 6300, 0.7993605116f }, - { 8000, 0.6345177665f }, - { 10000, 0.5808887598f }, - { 12500, 0.6053268765f }, - { 20000, 0 } -}; - -struct RGB { - uint8_t r, g, b; - - constexpr RGB() : r(0), g(0), b(0) {} - constexpr RGB(uint c) : r((c >> 16) & 0xff), g((c >> 8) & 0xff), b(c & 0xff) {} - constexpr RGB(uint8_t r, uint8_t g, uint8_t b) : r(r), g(g), b(b) {} - - static RGB from_hsv(float h, float s, float v) { - int i = h * 6.0f; - float f = (h * 6.0f) - i; - v *= 255.0f; - uint8_t p = v * (1.0f - s); - uint8_t q = v * (1.0f - (f * s)); - uint8_t t = v * (1.0f - ((1.0f - f) * s)); - - switch (i % 6) { - default: - case 0: return RGB(v, t, p); - case 1: return RGB(q, v, p); - case 2: return RGB(p, v, t); - case 3: return RGB(p, q, v); - case 4: return RGB(t, p, v); - case 5: return RGB(v, p, q); - } - } -}; Display display; -constexpr int HISTORY_LEN = 21; // About 0.25s -static uint history_idx = 0; -static uint8_t eq_history[display.WIDTH][HISTORY_LEN] = {{0}}; -static fix15 loudness_adjust[display.WIDTH]; +RainbowFFT effect(display); -FIX_FFT fft; -RGB palette_peak[display.WIDTH]; -RGB palette_main[display.WIDTH]; +static constexpr unsigned int BUFFERS_PER_FFT_SAMPLE = 2; +static constexpr unsigned int SAMPLES_PER_AUDIO_BUFFER = SAMPLE_COUNT / BUFFERS_PER_FFT_SAMPLE; + // client static void (*playback_callback)(int16_t * buffer, uint16_t num_samples); -static void init_loudness(uint32_t sample_frequency) { - float scale = float(display.HEIGHT) * .318f; - - for (int i = 0; i < display.WIDTH; ++i) { - int freq = (sample_frequency * 2) * (i + FFT_SKIP_BINS) / 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)); - printf("%d %d %f\n", i, freq, fix15_to_float(loudness_adjust[i])); - } -} - // timer to fill output ring buffer static btstack_timer_source_t driver_timer_sink; @@ -190,7 +102,6 @@ static audio_buffer_pool_t *init_audio(uint32_t sample_frequency, uint8_t channe btstack_last_sample_idx = 0; btstack_volume = 127; - init_loudness(sample_frequency); audio_buffer_pool_t * producer_pool = audio_new_producer_pool(&btstack_audio_pico_producer_format, 3, SAMPLES_PER_AUDIO_BUFFER); // todo correct size @@ -211,17 +122,12 @@ static audio_buffer_pool_t *init_audio(uint32_t sample_frequency, uint8_t channe assert(ok); (void)ok; + effect.init(sample_frequency); display.init(); display.clear(); display.set_pixel(0, 0, 255, 0, 0); - for(auto i = 0u; i < display.WIDTH; i++) { - float h = float(i) / display.WIDTH; - palette_peak[i] = RGB::from_hsv(h, 0.7f, 1.0f); - palette_main[i] = RGB::from_hsv(h, 1.0f, 0.7f); - } - return producer_pool; } @@ -235,11 +141,9 @@ static void btstack_audio_pico_sink_fill_buffers(void){ int16_t * buffer16 = (int16_t *) audio_buffer->buffer->bytes; (*playback_callback)(buffer16, audio_buffer->max_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)); + effect.update(buffer16, SAMPLE_COUNT); + for (auto i = 0u; i < SAMPLE_COUNT; i++) { - fft_array[i] = buffer16[i]; - // Apply volume after copying to FFT buffer16[i] = (int32_t(buffer16[i]) * int32_t(btstack_volume)) >> 8; } @@ -252,77 +156,6 @@ static void btstack_audio_pico_sink_fill_buffers(void){ } } -// Choose one: -//#define SCALE_LOGARITHMIC -#define SCALE_SQRT -//#define SCALE_LINEAR - - constexpr float max_sample_from_fft = 4000.f + 130.f * display.HEIGHT; - constexpr int lower_threshold = 270 - 2 * display.HEIGHT; -#ifdef SCALE_LOGARITHMIC - constexpr fix15 multiple = float_to_fix15(pow(max_sample_from_fft / lower_threshold, -1.f / (display.HEIGHT - 1))); -#elif defined(SCALE_SQRT) - constexpr fix15 subtract_step = float_to_fix15((max_sample_from_fft - lower_threshold) * 2.f / (display.HEIGHT * (display.HEIGHT - 1))); -#elif defined(SCALE_LINEAR) - constexpr fix15 subtract = float_to_fix15((max_sample_from_fft - lower_threshold) / (display.HEIGHT - 1)); -#else - #error "Choose a scale mode" -#endif - 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, loudness_adjust[i])); - 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_main[i].r); - g = (uint16_t)(palette_main[i].g); - b = (uint16_t)(palette_main[i].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_main[i].r), int_sample); - g = std::min((uint16_t)(palette_main[i].g), int_sample); - b = std::min((uint16_t)(palette_main[i].b), int_sample); - eq_history[i][history_idx] = y; - sample = 0; - if (maxy < y) { - maxy = y; - } - } else if (y < maxy) { - r = (uint16_t)(palette_main[i].r) >> 2; - g = (uint16_t)(palette_main[i].g) >> 2; - b = (uint16_t)(palette_main[i].b) >> 2; - } - display.set_pixel(i, display.HEIGHT - 1 - y, r, g, b); - } - if (maxy > 0) { - RGB c = palette_peak[i]; - display.set_pixel(i, display.HEIGHT - 1 - maxy, c.r, c.g, c.b); - } - } - history_idx = (history_idx + 1) % HISTORY_LEN; - audio_buffer->sample_count = audio_buffer->max_sample_count; give_audio_buffer(btstack_audio_pico_audio_buffer_pool, audio_buffer); } From c51c8e490ab4633cb9781770664d205b078bdfe9 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Sat, 25 Feb 2023 22:44:25 +0000 Subject: [PATCH 02/12] Don't shadow variables. --- effect/effect.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/effect/effect.hpp b/effect/effect.hpp index 8819768..2334f28 100644 --- a/effect/effect.hpp +++ b/effect/effect.hpp @@ -89,14 +89,14 @@ class RainbowFFT : public Effect { palette_main[i] = RGB::from_hsv(h, 1.0f, 0.7f); } - float max_sample_from_fft = 4000.f + 130.f * display.get_height(); - int lower_threshold = 270 - 2 * display.get_height(); + max_sample_from_fft = 4000.f + 130.f * display.get_height(); + lower_threshold = 270 - 2 * display.get_height(); #ifdef SCALE_LOGARITHMIC - fix15 multiple = float_to_fix15(pow(max_sample_from_fft / lower_threshold, -1.f / (display.get_height() - 1))); + multiple = float_to_fix15(pow(max_sample_from_fft / lower_threshold, -1.f / (display.get_height() - 1))); #elif defined(SCALE_SQRT) - fix15 subtract_step = float_to_fix15((max_sample_from_fft - lower_threshold) * 2.f / (display.get_height() * (display.get_height() - 1))); + subtract_step = float_to_fix15((max_sample_from_fft - lower_threshold) * 2.f / (display.get_height() * (display.get_height() - 1))); #elif defined(SCALE_LINEAR) - fix15 subtract = float_to_fix15((max_sample_from_fft - lower_threshold) / (display.get_height() - 1)); + subtract = float_to_fix15((max_sample_from_fft - lower_threshold) / (display.get_height() - 1)); #endif } void update(int16_t *buffer16, size_t sample_count) override; From eed8bd92bfef21c8890d9cda8d4396020799490c Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Sat, 25 Feb 2023 23:08:27 +0000 Subject: [PATCH 03/12] Something something buffer overflow. --- effect/effect.hpp | 23 +++-------------------- effect/rainbow_fft.cpp | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/effect/effect.hpp b/effect/effect.hpp index 2334f28..3b13ef1 100644 --- a/effect/effect.hpp +++ b/effect/effect.hpp @@ -22,7 +22,6 @@ class RainbowFFT : public Effect { uint history_idx; uint8_t eq_history[32][HISTORY_LEN]; fix15 loudness_adjust[32]; - FIX_FFT fft; RGB palette_peak[32]; RGB palette_main[32]; @@ -77,28 +76,12 @@ class RainbowFFT : public Effect { { 20000, 0 } }; + FIX_FFT fft; + void init_loudness(uint32_t sample_frequency); public: - RainbowFFT(DisplayBase& display) : Effect(display) { - history_idx = 0; - - for(auto i = 0u; i < display.get_width(); i++) { - float h = float(i) / display.get_width(); - palette_peak[i] = RGB::from_hsv(h, 0.7f, 1.0f); - palette_main[i] = RGB::from_hsv(h, 1.0f, 0.7f); - } - - max_sample_from_fft = 4000.f + 130.f * display.get_height(); - lower_threshold = 270 - 2 * display.get_height(); -#ifdef SCALE_LOGARITHMIC - multiple = float_to_fix15(pow(max_sample_from_fft / lower_threshold, -1.f / (display.get_height() - 1))); -#elif defined(SCALE_SQRT) - subtract_step = float_to_fix15((max_sample_from_fft - lower_threshold) * 2.f / (display.get_height() * (display.get_height() - 1))); -#elif defined(SCALE_LINEAR) - subtract = float_to_fix15((max_sample_from_fft - lower_threshold) / (display.get_height() - 1)); -#endif - } + RainbowFFT(DisplayBase& display) : Effect(display) {} void update(int16_t *buffer16, size_t sample_count) override; void init(uint32_t sample_frequency) override; }; diff --git a/effect/rainbow_fft.cpp b/effect/rainbow_fft.cpp index b0c0265..54bced7 100644 --- a/effect/rainbow_fft.cpp +++ b/effect/rainbow_fft.cpp @@ -10,6 +10,7 @@ void RainbowFFT::update(int16_t *buffer16, size_t sample_count) { } fft.update(); + for (auto i = 0u; i < display.get_width(); i++) { fix15 sample = std::min(float_to_fix15(max_sample_from_fft), fft.get_scaled_as_fix15(i + FFT_SKIP_BINS, loudness_adjust[i])); uint8_t maxy = 0; @@ -81,5 +82,25 @@ void RainbowFFT::init_loudness(uint32_t sample_frequency) { } void RainbowFFT::init(uint32_t sample_frequency) { + printf("RainbowFFT: %ix%i\n", display.get_width(), display.get_height()); + + history_idx = 0; + + for(auto i = 0u; i < display.get_width(); i++) { + float h = float(i) / display.get_width(); + palette_peak[i] = RGB::from_hsv(h, 0.7f, 1.0f); + palette_main[i] = RGB::from_hsv(h, 1.0f, 0.7f); + } + + max_sample_from_fft = 4000.f + 130.f * display.get_height(); + lower_threshold = 270 - 2 * display.get_height(); +#ifdef SCALE_LOGARITHMIC + multiple = float_to_fix15(pow(max_sample_from_fft / lower_threshold, -1.f / (display.get_height() - 1))); +#elif defined(SCALE_SQRT) + subtract_step = float_to_fix15((max_sample_from_fft - lower_threshold) * 2.f / (display.get_height() * (display.get_height() - 1))); +#elif defined(SCALE_LINEAR) + subtract = float_to_fix15((max_sample_from_fft - lower_threshold) / (display.get_height() - 1)); +#endif + init_loudness(sample_frequency); } \ No newline at end of file From eab6bb12bfb26dbac927951ae810b8446182cf4a Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Sat, 25 Feb 2023 23:18:43 +0000 Subject: [PATCH 04/12] Don't overflow FFT buffer. --- display/galactic/galactic_unicorn.cpp | 2 +- effect/effect.hpp | 5 +++-- effect/rainbow_fft.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/display/galactic/galactic_unicorn.cpp b/display/galactic/galactic_unicorn.cpp index 1b1291d..af1fe86 100644 --- a/display/galactic/galactic_unicorn.cpp +++ b/display/galactic/galactic_unicorn.cpp @@ -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? } \ No newline at end of file diff --git a/effect/effect.hpp b/effect/effect.hpp index 3b13ef1..c9b4ab3 100644 --- a/effect/effect.hpp +++ b/effect/effect.hpp @@ -22,6 +22,9 @@ class RainbowFFT : public Effect { uint history_idx; uint8_t eq_history[32][HISTORY_LEN]; fix15 loudness_adjust[32]; + + + FIX_FFT fft; RGB palette_peak[32]; RGB palette_main[32]; @@ -76,8 +79,6 @@ class RainbowFFT : public Effect { { 20000, 0 } }; - FIX_FFT fft; - void init_loudness(uint32_t sample_frequency); public: diff --git a/effect/rainbow_fft.cpp b/effect/rainbow_fft.cpp index 54bced7..e6ab31a 100644 --- a/effect/rainbow_fft.cpp +++ b/effect/rainbow_fft.cpp @@ -5,7 +5,7 @@ void RainbowFFT::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 < sample_count; i++) { + for (auto i = 0u; i < SAMPLES_PER_AUDIO_BUFFER; i++) { fft_array[i] = buffer16[i]; } From 8efd16ac7ceaef65a93f3dde074377a340916424 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Sat, 25 Feb 2023 23:40:14 +0000 Subject: [PATCH 05/12] Add classic FFT effect. --- CMakeLists.txt | 2 + effect/classic_fft.cmake | 18 +++++++ effect/classic_fft.cpp | 107 +++++++++++++++++++++++++++++++++++++++ effect/effect.hpp | 74 +++++++++++++++++++++++++++ effect/rainbow_fft.cmake | 4 +- 5 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 effect/classic_fft.cmake create mode 100644 effect/classic_fft.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fdb906c..e14fed5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ 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}) @@ -37,6 +38,7 @@ target_link_libraries(${NAME} pico_audio_i2s display rainbow_fft + classic_fft ) message(WARNING "Display: ${DISPLAY_NAME}") diff --git a/effect/classic_fft.cmake b/effect/classic_fft.cmake new file mode 100644 index 0000000..2c67d7c --- /dev/null +++ b/effect/classic_fft.cmake @@ -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 +) \ No newline at end of file diff --git a/effect/classic_fft.cpp b/effect/classic_fft.cpp new file mode 100644 index 0000000..fe16360 --- /dev/null +++ b/effect/classic_fft.cpp @@ -0,0 +1,107 @@ +#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.get_width(); i++) { + fix15 sample = std::min(float_to_fix15(max_sample_from_fft), fft.get_scaled_as_fix15(i + FFT_SKIP_BINS, loudness_adjust[i])); + 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.get_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) >> 2; + g = (uint16_t)(palette[y].g) >> 2; + b = (uint16_t)(palette[y].b) >> 2; + } + display.set_pixel(i, display.get_height() - 1 - y, r, g, b); + } + if (maxy > 0) { + RGB c = palette[display.get_height() - 1]; + display.set_pixel(i, display.get_height() - 1 - maxy, c.r, c.g, c.b); + } + } + history_idx = (history_idx + 1) % HISTORY_LEN; +} + +void ClassicFFT::init_loudness(uint32_t sample_frequency) { + float scale = float(display.get_height()) * .318f; + + for (int i = 0; i < display.get_width(); ++i) { + int freq = (sample_frequency * 2) * (i + FFT_SKIP_BINS) / 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)); + printf("%d %d %f\n", i, freq, fix15_to_float(loudness_adjust[i])); + } +} + +void ClassicFFT::init(uint32_t sample_frequency) { + printf("ClassicFFT: %ix%i\n", display.get_width(), display.get_height()); + + history_idx = 0; + + for(auto i = 0u; i < display.get_height(); i++) { + int n = floor(i / 4) * 4; + float h = 0.4 * float(n) / display.get_height(); + h = 0.333 - h; + palette[i] = RGB::from_hsv(h, 1.0f, 1.0f); + } + + max_sample_from_fft = 4000.f + 130.f * display.get_height(); + lower_threshold = 270 - 2 * display.get_height(); +#ifdef SCALE_LOGARITHMIC + multiple = float_to_fix15(pow(max_sample_from_fft / lower_threshold, -1.f / (display.get_height() - 1))); +#elif defined(SCALE_SQRT) + subtract_step = float_to_fix15((max_sample_from_fft - lower_threshold) * 2.f / (display.get_height() * (display.get_height() - 1))); +#elif defined(SCALE_LINEAR) + subtract = float_to_fix15((max_sample_from_fft - lower_threshold) / (display.get_height() - 1)); +#endif + + init_loudness(sample_frequency); +} \ No newline at end of file diff --git a/effect/effect.hpp b/effect/effect.hpp index c9b4ab3..04f6ab6 100644 --- a/effect/effect.hpp +++ b/effect/effect.hpp @@ -86,3 +86,77 @@ class RainbowFFT : public Effect { 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[32][HISTORY_LEN]; + fix15 loudness_adjust[32]; + + + FIX_FFT fft; + RGB palette[32]; + + 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 + + struct LoudnessLookup { + int freq; + float multiplier; + }; + + // Amplitude to loudness lookup at 20 phons + static constexpr LoudnessLookup loudness_lookup[] = { + { 20, 0.2232641215f }, + { 25, 0.241984271f }, + { 31, 0.263227165f }, + { 40, 0.2872737719f }, + { 50, 0.3124023743f }, + { 63, 0.341588386f }, + { 80, 0.3760105283f }, + { 100, 0.4133939644f }, + { 125, 0.4551661356f }, + { 160, 0.508001016f }, + { 200, 0.5632216277f }, + { 250, 0.6251953736f }, + { 315, 0.6971070059f }, + { 400, 0.7791195949f }, + { 500, 0.8536064874f }, + { 630, 0.9310986965f }, + { 800, 0.9950248756f }, + { 1000, 0.9995002499f }, + { 1250, 0.9319664492f }, + { 1600, 0.9345794393f }, + { 2000, 1.101928375f }, + { 2500, 1.300390117f }, + { 3150, 1.402524544f }, + { 4000, 1.321003963f }, + { 5000, 1.073537305f }, + { 6300, 0.7993605116f }, + { 8000, 0.6345177665f }, + { 10000, 0.5808887598f }, + { 12500, 0.6053268765f }, + { 20000, 0 } + }; + + void init_loudness(uint32_t sample_frequency); + + public: + ClassicFFT(DisplayBase& display) : Effect(display) {} + void update(int16_t *buffer16, size_t sample_count) override; + void init(uint32_t sample_frequency) override; +}; diff --git a/effect/rainbow_fft.cmake b/effect/rainbow_fft.cmake index 9b08fc9..b6f72ba 100644 --- a/effect/rainbow_fft.cmake +++ b/effect/rainbow_fft.cmake @@ -15,6 +15,4 @@ target_include_directories(rainbow_fft INTERFACE # SCALE_LINEAR target_compile_definitions(rainbow_fft INTERFACE -DSCALE_SQRT -) - -set(EFFECT_NAME "Rainbow FFT") \ No newline at end of file +) \ No newline at end of file From a4447e8d5eb573af33b35209f26f6816d6711512 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Sun, 26 Feb 2023 00:29:42 +0000 Subject: [PATCH 06/12] Works, but is a stuttery mess. --- display/displaybase.hpp | 4 +- effect/classic_fft.cpp | 47 ++++++----------- effect/effect.hpp | 102 ++++--------------------------------- effect/lib/fixed_fft.cpp | 25 ++++++--- effect/lib/fixed_fft.hpp | 52 +++++++++++++++++-- effect/rainbow_fft.cpp | 45 ++++++---------- src/btstack_audio_pico.cpp | 4 +- 7 files changed, 114 insertions(+), 165 deletions(-) diff --git a/display/displaybase.hpp b/display/displaybase.hpp index be28fb4..170a8e9 100644 --- a/display/displaybase.hpp +++ b/display/displaybase.hpp @@ -7,6 +7,6 @@ class DisplayBase { virtual void clear(); virtual void update(); virtual void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b); - virtual const int get_width(); - virtual const int get_height(); + virtual const int get_width() = 0; + virtual const int get_height() = 0; }; \ No newline at end of file diff --git a/effect/classic_fft.cpp b/effect/classic_fft.cpp index fe16360..f190f49 100644 --- a/effect/classic_fft.cpp +++ b/effect/classic_fft.cpp @@ -11,8 +11,8 @@ void ClassicFFT::update(int16_t *buffer16, size_t sample_count) { fft.update(); - for (auto i = 0u; i < display.get_width(); i++) { - fix15 sample = std::min(float_to_fix15(max_sample_from_fft), fft.get_scaled_as_fix15(i + FFT_SKIP_BINS, loudness_adjust[i])); + for (auto i = 0u; i < 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) { @@ -24,7 +24,7 @@ void ClassicFFT::update(int16_t *buffer16, size_t sample_count) { #ifdef SCALE_SQRT fix15 subtract = subtract_step; #endif - for (auto y = 0; y < display.get_height(); y++) { + for (auto y = 0; y < height; y++) { uint8_t r = 0; uint8_t g = 0; uint8_t b = 0; @@ -56,52 +56,37 @@ void ClassicFFT::update(int16_t *buffer16, size_t sample_count) { g = (uint16_t)(palette[y].g) >> 2; b = (uint16_t)(palette[y].b) >> 2; } - display.set_pixel(i, display.get_height() - 1 - y, r, g, b); + display.set_pixel(i, height - 1 - y, r, g, b); } if (maxy > 0) { - RGB c = palette[display.get_height() - 1]; - display.set_pixel(i, display.get_height() - 1 - maxy, c.r, c.g, c.b); + RGB c = palette[height - 1]; + display.set_pixel(i, height - 1 - maxy, c.r, c.g, c.b); } } history_idx = (history_idx + 1) % HISTORY_LEN; } -void ClassicFFT::init_loudness(uint32_t sample_frequency) { - float scale = float(display.get_height()) * .318f; - - for (int i = 0; i < display.get_width(); ++i) { - int freq = (sample_frequency * 2) * (i + FFT_SKIP_BINS) / 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)); - printf("%d %d %f\n", i, freq, fix15_to_float(loudness_adjust[i])); - } -} - void ClassicFFT::init(uint32_t sample_frequency) { - printf("ClassicFFT: %ix%i\n", display.get_width(), display.get_height()); + printf("ClassicFFT: %ix%i\n", width, height); history_idx = 0; - for(auto i = 0u; i < display.get_height(); i++) { + fft.set_scale(height * .318f); + + for(auto i = 0u; i < height; i++) { int n = floor(i / 4) * 4; - float h = 0.4 * float(n) / display.get_height(); + float h = 0.4 * float(n) / height; h = 0.333 - h; palette[i] = RGB::from_hsv(h, 1.0f, 1.0f); } - max_sample_from_fft = 4000.f + 130.f * display.get_height(); - lower_threshold = 270 - 2 * display.get_height(); + max_sample_from_fft = 4000.f + 130.f * height; + lower_threshold = 270 - 2 * height; #ifdef SCALE_LOGARITHMIC - multiple = float_to_fix15(pow(max_sample_from_fft / lower_threshold, -1.f / (display.get_height() - 1))); + multiple = float_to_fix15(pow(max_sample_from_fft / lower_threshold, -1.f / (height - 1))); #elif defined(SCALE_SQRT) - subtract_step = float_to_fix15((max_sample_from_fft - lower_threshold) * 2.f / (display.get_height() * (display.get_height() - 1))); + subtract_step = float_to_fix15((max_sample_from_fft - lower_threshold) * 2.f / (height * (height - 1))); #elif defined(SCALE_LINEAR) - subtract = float_to_fix15((max_sample_from_fft - lower_threshold) / (display.get_height() - 1)); + subtract = float_to_fix15((max_sample_from_fft - lower_threshold) / (height - 1)); #endif - - init_loudness(sample_frequency); } \ No newline at end of file diff --git a/effect/effect.hpp b/effect/effect.hpp index 04f6ab6..cff4c26 100644 --- a/effect/effect.hpp +++ b/effect/effect.hpp @@ -7,7 +7,15 @@ class Effect { public: DisplayBase &display; - Effect(DisplayBase& display) : display(display) {}; + FIX_FFT &fft; + int width; + int height; + Effect(DisplayBase& display, FIX_FFT& fft) : + display(display), + fft(fft) { + width = display.get_width(); + height = display.get_height(); + }; virtual void init(uint32_t sample_frequency); virtual void update(int16_t *buffer16, size_t sample_count); }; @@ -21,10 +29,7 @@ class RainbowFFT : public Effect { static constexpr int HISTORY_LEN = 21; // About 0.25s uint history_idx; uint8_t eq_history[32][HISTORY_LEN]; - fix15 loudness_adjust[32]; - - FIX_FFT fft; RGB palette_peak[32]; RGB palette_main[32]; @@ -40,49 +45,8 @@ class RainbowFFT : public Effect { #error "Choose a scale mode" #endif - struct LoudnessLookup { - int freq; - float multiplier; - }; - - // Amplitude to loudness lookup at 20 phons - static constexpr LoudnessLookup loudness_lookup[] = { - { 20, 0.2232641215f }, - { 25, 0.241984271f }, - { 31, 0.263227165f }, - { 40, 0.2872737719f }, - { 50, 0.3124023743f }, - { 63, 0.341588386f }, - { 80, 0.3760105283f }, - { 100, 0.4133939644f }, - { 125, 0.4551661356f }, - { 160, 0.508001016f }, - { 200, 0.5632216277f }, - { 250, 0.6251953736f }, - { 315, 0.6971070059f }, - { 400, 0.7791195949f }, - { 500, 0.8536064874f }, - { 630, 0.9310986965f }, - { 800, 0.9950248756f }, - { 1000, 0.9995002499f }, - { 1250, 0.9319664492f }, - { 1600, 0.9345794393f }, - { 2000, 1.101928375f }, - { 2500, 1.300390117f }, - { 3150, 1.402524544f }, - { 4000, 1.321003963f }, - { 5000, 1.073537305f }, - { 6300, 0.7993605116f }, - { 8000, 0.6345177665f }, - { 10000, 0.5808887598f }, - { 12500, 0.6053268765f }, - { 20000, 0 } - }; - - void init_loudness(uint32_t sample_frequency); - public: - RainbowFFT(DisplayBase& display) : Effect(display) {} + RainbowFFT(DisplayBase& display, FIX_FFT& fft) : Effect(display, fft) {} void update(int16_t *buffer16, size_t sample_count) override; void init(uint32_t sample_frequency) override; }; @@ -96,10 +60,7 @@ class ClassicFFT : public Effect { static constexpr int HISTORY_LEN = 21; // About 0.25s uint history_idx; uint8_t eq_history[32][HISTORY_LEN]; - fix15 loudness_adjust[32]; - - FIX_FFT fft; RGB palette[32]; float max_sample_from_fft; @@ -114,49 +75,8 @@ class ClassicFFT : public Effect { #error "Choose a scale mode" #endif - struct LoudnessLookup { - int freq; - float multiplier; - }; - - // Amplitude to loudness lookup at 20 phons - static constexpr LoudnessLookup loudness_lookup[] = { - { 20, 0.2232641215f }, - { 25, 0.241984271f }, - { 31, 0.263227165f }, - { 40, 0.2872737719f }, - { 50, 0.3124023743f }, - { 63, 0.341588386f }, - { 80, 0.3760105283f }, - { 100, 0.4133939644f }, - { 125, 0.4551661356f }, - { 160, 0.508001016f }, - { 200, 0.5632216277f }, - { 250, 0.6251953736f }, - { 315, 0.6971070059f }, - { 400, 0.7791195949f }, - { 500, 0.8536064874f }, - { 630, 0.9310986965f }, - { 800, 0.9950248756f }, - { 1000, 0.9995002499f }, - { 1250, 0.9319664492f }, - { 1600, 0.9345794393f }, - { 2000, 1.101928375f }, - { 2500, 1.300390117f }, - { 3150, 1.402524544f }, - { 4000, 1.321003963f }, - { 5000, 1.073537305f }, - { 6300, 0.7993605116f }, - { 8000, 0.6345177665f }, - { 10000, 0.5808887598f }, - { 12500, 0.6053268765f }, - { 20000, 0 } - }; - - void init_loudness(uint32_t sample_frequency); - public: - ClassicFFT(DisplayBase& display) : Effect(display) {} + ClassicFFT(DisplayBase& display, FIX_FFT &fft) : Effect(display, fft) {} void update(int16_t *buffer16, size_t sample_count) override; void init(uint32_t sample_frequency) override; }; diff --git a/effect/lib/fixed_fft.cpp b/effect/lib/fixed_fft.cpp index 744c326..feb8404 100644 --- a/effect/lib/fixed_fft.cpp +++ b/effect/lib/fixed_fft.cpp @@ -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 @@ -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; diff --git a/effect/lib/fixed_fft.hpp b/effect/lib/fixed_fft.hpp index 5b31368..2862bd3 100644 --- a/effect/lib/fixed_fft.hpp +++ b/effect/lib/fixed_fft.hpp @@ -1,3 +1,4 @@ +#pragma once #include #include #include @@ -30,6 +31,46 @@ class FIX_FFT { unsigned int log2_samples; unsigned int shift_amount; + // Loudness compensation lookup table + struct LoudnessLookup { + int freq; + float multiplier; + }; + + // Amplitude to loudness lookup at 20 phons + static constexpr LoudnessLookup loudness_lookup[] = { + { 20, 0.2232641215f }, + { 25, 0.241984271f }, + { 31, 0.263227165f }, + { 40, 0.2872737719f }, + { 50, 0.3124023743f }, + { 63, 0.341588386f }, + { 80, 0.3760105283f }, + { 100, 0.4133939644f }, + { 125, 0.4551661356f }, + { 160, 0.508001016f }, + { 200, 0.5632216277f }, + { 250, 0.6251953736f }, + { 315, 0.6971070059f }, + { 400, 0.7791195949f }, + { 500, 0.8536064874f }, + { 630, 0.9310986965f }, + { 800, 0.9950248756f }, + { 1000, 0.9995002499f }, + { 1250, 0.9319664492f }, + { 1600, 0.9345794393f }, + { 2000, 1.101928375f }, + { 2500, 1.300390117f }, + { 3150, 1.402524544f }, + { 4000, 1.321003963f }, + { 5000, 1.073537305f }, + { 6300, 0.7993605116f }, + { 8000, 0.6345177665f }, + { 10000, 0.5808887598f }, + { 12500, 0.6053268765f }, + { 20000, 0 } + }; + // Lookup tables fix15 sine_table[SAMPLE_COUNT]; // a table of sines for the FFT fix15 filter_window[SAMPLE_COUNT]; // a table of window values for the FFT @@ -38,6 +79,9 @@ class FIX_FFT { fix15 fr[SAMPLE_COUNT]; fix15 fi[SAMPLE_COUNT]; + // Storage for loudness compensation + fix15 loudness_adjust[SAMPLE_COUNT]; + int max_freq_dex = 0; void FFT(); @@ -56,12 +100,14 @@ class FIX_FFT { memset(fi, 0, SAMPLE_COUNT * sizeof(fix15)); init(); + set_scale(1.0f); }; ~FIX_FFT(); void update(); + void set_scale(float scale); float max_frequency(); - int get_scaled(unsigned int i, unsigned int scale); - int get_scaled_fix15(unsigned int i, fix15 scale); - fix15 get_scaled_as_fix15(unsigned int i, fix15 scale); + int get_scaled(unsigned int i); + int get_scaled_fix15(unsigned int i); + fix15 get_scaled_as_fix15(unsigned int i); }; \ No newline at end of file diff --git a/effect/rainbow_fft.cpp b/effect/rainbow_fft.cpp index e6ab31a..8cd4b01 100644 --- a/effect/rainbow_fft.cpp +++ b/effect/rainbow_fft.cpp @@ -11,8 +11,8 @@ void RainbowFFT::update(int16_t *buffer16, size_t sample_count) { fft.update(); - for (auto i = 0u; i < display.get_width(); i++) { - fix15 sample = std::min(float_to_fix15(max_sample_from_fft), fft.get_scaled_as_fix15(i + FFT_SKIP_BINS, loudness_adjust[i])); + for (auto i = 0u; i < 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) { @@ -24,7 +24,7 @@ void RainbowFFT::update(int16_t *buffer16, size_t sample_count) { #ifdef SCALE_SQRT fix15 subtract = subtract_step; #endif - for (auto y = 0; y < display.get_height(); y++) { + for (auto y = 0; y < height; y++) { uint8_t r = 0; uint8_t g = 0; uint8_t b = 0; @@ -56,51 +56,36 @@ void RainbowFFT::update(int16_t *buffer16, size_t sample_count) { g = (uint16_t)(palette_main[i].g) >> 2; b = (uint16_t)(palette_main[i].b) >> 2; } - display.set_pixel(i, display.get_height() - 1 - y, r, g, b); + display.set_pixel(i, height - 1 - y, r, g, b); } if (maxy > 0) { RGB c = palette_peak[i]; - display.set_pixel(i, display.get_height() - 1 - maxy, c.r, c.g, c.b); + display.set_pixel(i, height - 1 - maxy, c.r, c.g, c.b); } } history_idx = (history_idx + 1) % HISTORY_LEN; } -void RainbowFFT::init_loudness(uint32_t sample_frequency) { - float scale = float(display.get_height()) * .318f; - - for (int i = 0; i < display.get_width(); ++i) { - int freq = (sample_frequency * 2) * (i + FFT_SKIP_BINS) / 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)); - printf("%d %d %f\n", i, freq, fix15_to_float(loudness_adjust[i])); - } -} - void RainbowFFT::init(uint32_t sample_frequency) { - printf("RainbowFFT: %ix%i\n", display.get_width(), display.get_height()); + printf("RainbowFFT: %ix%i\n", width, height); history_idx = 0; - for(auto i = 0u; i < display.get_width(); i++) { - float h = float(i) / display.get_width(); + fft.set_scale(height * .318f); + + for(auto i = 0u; i < width; i++) { + float h = float(i) / width; palette_peak[i] = RGB::from_hsv(h, 0.7f, 1.0f); palette_main[i] = RGB::from_hsv(h, 1.0f, 0.7f); } - max_sample_from_fft = 4000.f + 130.f * display.get_height(); - lower_threshold = 270 - 2 * display.get_height(); + max_sample_from_fft = 4000.f + 130.f * height; + lower_threshold = 270 - 2 * height; #ifdef SCALE_LOGARITHMIC - multiple = float_to_fix15(pow(max_sample_from_fft / lower_threshold, -1.f / (display.get_height() - 1))); + multiple = float_to_fix15(pow(max_sample_from_fft / lower_threshold, -1.f / (height - 1))); #elif defined(SCALE_SQRT) - subtract_step = float_to_fix15((max_sample_from_fft - lower_threshold) * 2.f / (display.get_height() * (display.get_height() - 1))); + subtract_step = float_to_fix15((max_sample_from_fft - lower_threshold) * 2.f / (height * (height - 1))); #elif defined(SCALE_LINEAR) - subtract = float_to_fix15((max_sample_from_fft - lower_threshold) / (display.get_height() - 1)); + subtract = float_to_fix15((max_sample_from_fft - lower_threshold) / (height - 1)); #endif - - init_loudness(sample_frequency); } \ No newline at end of file diff --git a/src/btstack_audio_pico.cpp b/src/btstack_audio_pico.cpp index 36523fc..0d2a2df 100644 --- a/src/btstack_audio_pico.cpp +++ b/src/btstack_audio_pico.cpp @@ -58,11 +58,13 @@ #include "display.hpp" #include "effect.hpp" +#include "lib/fixed_fft.hpp" #define DRIVER_POLL_INTERVAL_MS 5 Display display; -RainbowFFT effect(display); +FIX_FFT fft; +RainbowFFT effect(display, fft); static constexpr unsigned int BUFFERS_PER_FFT_SAMPLE = 2; From f1b7d2708b652e4ce1f48b0de5c1e737b7f1e650 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Sun, 26 Feb 2023 01:16:45 +0000 Subject: [PATCH 07/12] Run effects on core1, yolo --- CMakeLists.txt | 1 + effect/classic_fft.cpp | 6 +++--- effect/rainbow_fft.cpp | 6 +++--- src/btstack_audio_pico.cpp | 23 +++++++++++++++++++++-- src/main.cpp | 6 ++++++ 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e14fed5..4381971 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ target_include_directories(${NAME} PRIVATE target_link_libraries(${NAME} picow_bt_example_common pico_audio_i2s + pico_multicore display rainbow_fft classic_fft diff --git a/effect/classic_fft.cpp b/effect/classic_fft.cpp index f190f49..3d9b6ec 100644 --- a/effect/classic_fft.cpp +++ b/effect/classic_fft.cpp @@ -52,9 +52,9 @@ void ClassicFFT::update(int16_t *buffer16, size_t sample_count) { maxy = y; } } else if (y < maxy) { - r = (uint16_t)(palette[y].r) >> 2; - g = (uint16_t)(palette[y].g) >> 2; - b = (uint16_t)(palette[y].b) >> 2; + 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, height - 1 - y, r, g, b); } diff --git a/effect/rainbow_fft.cpp b/effect/rainbow_fft.cpp index 8cd4b01..ac847e4 100644 --- a/effect/rainbow_fft.cpp +++ b/effect/rainbow_fft.cpp @@ -52,9 +52,9 @@ void RainbowFFT::update(int16_t *buffer16, size_t sample_count) { maxy = y; } } else if (y < maxy) { - r = (uint16_t)(palette_main[i].r) >> 2; - g = (uint16_t)(palette_main[i].g) >> 2; - b = (uint16_t)(palette_main[i].b) >> 2; + r = (uint16_t)(palette_main[i].r) >> 3; + g = (uint16_t)(palette_main[i].g) >> 3; + b = (uint16_t)(palette_main[i].b) >> 3; } display.set_pixel(i, height - 1 - y, r, g, b); } diff --git a/src/btstack_audio_pico.cpp b/src/btstack_audio_pico.cpp index 0d2a2df..b674584 100644 --- a/src/btstack_audio_pico.cpp +++ b/src/btstack_audio_pico.cpp @@ -55,6 +55,8 @@ #include "pico/audio_i2s.h" #include "pico/stdlib.h" +#include "pico/multicore.h" +#include "pico/sync.h" #include "display.hpp" #include "effect.hpp" @@ -66,6 +68,8 @@ Display display; FIX_FFT fft; RainbowFFT effect(display, fft); +constexpr int core1_stack_len = 512; +uint32_t core1_stack[512]; static constexpr unsigned int BUFFERS_PER_FFT_SAMPLE = 2; static constexpr unsigned int SAMPLES_PER_AUDIO_BUFFER = SAMPLE_COUNT / BUFFERS_PER_FFT_SAMPLE; @@ -88,6 +92,18 @@ static uint8_t btstack_audio_pico_channel_count; static uint8_t btstack_volume; static uint8_t btstack_last_sample_idx; +auto_init_mutex(core1_effect_update); + +int16_t effect_buf[SAMPLE_COUNT] = {0}; + +void core1_entry() { + while(1) { + mutex_enter_blocking(&core1_effect_update); + effect.update(effect_buf, SAMPLE_COUNT); + mutex_exit(&core1_effect_update); + } +} + static audio_buffer_pool_t *init_audio(uint32_t sample_frequency, uint8_t channel_count) { // num channels requested by application @@ -130,6 +146,8 @@ static audio_buffer_pool_t *init_audio(uint32_t sample_frequency, uint8_t channe display.clear(); display.set_pixel(0, 0, 255, 0, 0); + multicore_launch_core1_with_stack(core1_entry, core1_stack, core1_stack_len); + return producer_pool; } @@ -143,11 +161,12 @@ static void btstack_audio_pico_sink_fill_buffers(void){ int16_t * buffer16 = (int16_t *) audio_buffer->buffer->bytes; (*playback_callback)(buffer16, audio_buffer->max_sample_count); - effect.update(buffer16, SAMPLE_COUNT); - + mutex_enter_blocking(&core1_effect_update); for (auto i = 0u; i < SAMPLE_COUNT; i++) { + effect_buf[i] = buffer16[i]; buffer16[i] = (int32_t(buffer16[i]) * int32_t(btstack_volume)) >> 8; } + mutex_exit(&core1_effect_update); // duplicate samples for mono if (btstack_audio_pico_channel_count == 1){ diff --git a/src/main.cpp b/src/main.cpp index dc8e887..faedfd7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,8 +7,14 @@ #include "btstack_run_loop.h" #include "pico/stdlib.h" #include "bluetooth/common.h" +#include "hardware/vreg.h" + int main() { + //vreg_set_voltage(VREG_VOLTAGE_1_20); + //sleep_ms(10); + //set_sys_clock_khz(200000, true); + stdio_init_all(); int res = picow_bt_example_init(); From ebbd0318ecc32548a95dbac70de41ee3114c9adb Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Sun, 26 Feb 2023 01:31:01 +0000 Subject: [PATCH 08/12] Remove class inheritance nonsense from display, get our precious performance back. --- display/cosmic/display.hpp | 14 +++++--------- display/displaybase.hpp | 12 ------------ display/galactic/display.hpp | 14 +++++--------- effect/classic_fft.cpp | 28 ++++++++++++++-------------- effect/effect.hpp | 27 +++++++++++---------------- effect/rainbow_fft.cpp | 26 +++++++++++++------------- src/btstack_audio_pico.cpp | 10 ++++++---- 7 files changed, 54 insertions(+), 77 deletions(-) delete mode 100644 display/displaybase.hpp diff --git a/display/cosmic/display.hpp b/display/cosmic/display.hpp index 31a0021..5d7f655 100644 --- a/display/cosmic/display.hpp +++ b/display/cosmic/display.hpp @@ -1,9 +1,8 @@ #pragma once #include "hardware/pio.h" -#include "displaybase.hpp" -class Display : public DisplayBase { +class Display { public: static const int WIDTH = 32; static const int HEIGHT = 32; @@ -64,13 +63,10 @@ class Display : public DisplayBase { public: ~Display(); - void init() override; - void clear() override; - void update() override; - void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) override; - - const int get_width() override {return WIDTH;}; - const int get_height() override {return HEIGHT;}; + void init(); + 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(); diff --git a/display/displaybase.hpp b/display/displaybase.hpp deleted file mode 100644 index 170a8e9..0000000 --- a/display/displaybase.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include - -class DisplayBase { - public: - virtual void init(); - virtual void clear(); - virtual void update(); - virtual void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b); - virtual const int get_width() = 0; - virtual const int get_height() = 0; -}; \ No newline at end of file diff --git a/display/galactic/display.hpp b/display/galactic/display.hpp index b7f66f8..41ce834 100644 --- a/display/galactic/display.hpp +++ b/display/galactic/display.hpp @@ -1,9 +1,8 @@ #pragma once #include "hardware/pio.h" -#include "displaybase.hpp" -class Display : public DisplayBase { +class Display { public: static const int WIDTH = 53; static const int HEIGHT = 11; @@ -64,13 +63,10 @@ class Display : public DisplayBase { public: ~Display(); - void init() override; - void clear() override; - void update() override; - void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) override; - - const int get_width() override {return WIDTH;}; - const int get_height() override {return HEIGHT;}; + void init(); + 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(); diff --git a/effect/classic_fft.cpp b/effect/classic_fft.cpp index 3d9b6ec..c4254a6 100644 --- a/effect/classic_fft.cpp +++ b/effect/classic_fft.cpp @@ -11,7 +11,7 @@ void ClassicFFT::update(int16_t *buffer16, size_t sample_count) { fft.update(); - for (auto i = 0u; i < width; i++) { + 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; @@ -24,7 +24,7 @@ void ClassicFFT::update(int16_t *buffer16, size_t sample_count) { #ifdef SCALE_SQRT fix15 subtract = subtract_step; #endif - for (auto y = 0; y < height; y++) { + for (auto y = 0; y < display.HEIGHT; y++) { uint8_t r = 0; uint8_t g = 0; uint8_t b = 0; @@ -56,37 +56,37 @@ void ClassicFFT::update(int16_t *buffer16, size_t sample_count) { g = (uint16_t)(palette[y].g) >> 3; b = (uint16_t)(palette[y].b) >> 3; } - display.set_pixel(i, height - 1 - y, r, g, b); + display.set_pixel(i, display.HEIGHT - 1 - y, r, g, b); } if (maxy > 0) { - RGB c = palette[height - 1]; - display.set_pixel(i, height - 1 - maxy, c.r, c.g, c.b); + 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", width, height); + printf("ClassicFFT: %ix%i\n", display.WIDTH, display.HEIGHT); history_idx = 0; - fft.set_scale(height * .318f); + fft.set_scale(display.HEIGHT * .318f); - for(auto i = 0u; i < height; i++) { + for(auto i = 0u; i < display.HEIGHT; i++) { int n = floor(i / 4) * 4; - float h = 0.4 * float(n) / height; + 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 * height; - lower_threshold = 270 - 2 * height; + 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 / (height - 1))); + 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 / (height * (height - 1))); + 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) / (height - 1)); + subtract = float_to_fix15((max_sample_from_fft - lower_threshold) / (display.HEIGHT - 1)); #endif } \ No newline at end of file diff --git a/effect/effect.hpp b/effect/effect.hpp index cff4c26..a1b0dcd 100644 --- a/effect/effect.hpp +++ b/effect/effect.hpp @@ -1,21 +1,16 @@ #pragma once #include -#include "displaybase.hpp" +#include "display.hpp" #include "lib/fixed_fft.hpp" #include "lib/rgb.hpp" class Effect { public: - DisplayBase &display; + Display &display; FIX_FFT &fft; - int width; - int height; - Effect(DisplayBase& display, FIX_FFT& fft) : + Effect(Display& display, FIX_FFT& fft) : display(display), - fft(fft) { - width = display.get_width(); - height = display.get_height(); - }; + fft(fft) {}; virtual void init(uint32_t sample_frequency); virtual void update(int16_t *buffer16, size_t sample_count); }; @@ -28,10 +23,10 @@ class RainbowFFT : public Effect { 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[32][HISTORY_LEN]; + uint8_t eq_history[Display::WIDTH][HISTORY_LEN]; - RGB palette_peak[32]; - RGB palette_main[32]; + RGB palette_peak[Display::WIDTH]; + RGB palette_main[Display::WIDTH]; float max_sample_from_fft; int lower_threshold; @@ -46,7 +41,7 @@ class RainbowFFT : public Effect { #endif public: - RainbowFFT(DisplayBase& display, FIX_FFT& fft) : Effect(display, fft) {} + 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; }; @@ -59,9 +54,9 @@ class ClassicFFT : public Effect { 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[32][HISTORY_LEN]; + uint8_t eq_history[Display::WIDTH][HISTORY_LEN]; - RGB palette[32]; + RGB palette[Display::WIDTH]; float max_sample_from_fft; int lower_threshold; @@ -76,7 +71,7 @@ class ClassicFFT : public Effect { #endif public: - ClassicFFT(DisplayBase& display, FIX_FFT &fft) : Effect(display, fft) {} + 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; }; diff --git a/effect/rainbow_fft.cpp b/effect/rainbow_fft.cpp index ac847e4..25c4d66 100644 --- a/effect/rainbow_fft.cpp +++ b/effect/rainbow_fft.cpp @@ -11,7 +11,7 @@ void RainbowFFT::update(int16_t *buffer16, size_t sample_count) { fft.update(); - for (auto i = 0u; i < width; i++) { + 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; @@ -24,7 +24,7 @@ void RainbowFFT::update(int16_t *buffer16, size_t sample_count) { #ifdef SCALE_SQRT fix15 subtract = subtract_step; #endif - for (auto y = 0; y < height; y++) { + for (auto y = 0; y < display.HEIGHT; y++) { uint8_t r = 0; uint8_t g = 0; uint8_t b = 0; @@ -56,36 +56,36 @@ void RainbowFFT::update(int16_t *buffer16, size_t sample_count) { g = (uint16_t)(palette_main[i].g) >> 3; b = (uint16_t)(palette_main[i].b) >> 3; } - display.set_pixel(i, height - 1 - y, r, g, b); + display.set_pixel(i, display.HEIGHT - 1 - y, r, g, b); } if (maxy > 0) { RGB c = palette_peak[i]; - display.set_pixel(i, height - 1 - maxy, c.r, c.g, c.b); + display.set_pixel(i, display.HEIGHT - 1 - maxy, c.r, c.g, c.b); } } history_idx = (history_idx + 1) % HISTORY_LEN; } void RainbowFFT::init(uint32_t sample_frequency) { - printf("RainbowFFT: %ix%i\n", width, height); + printf("RainbowFFT: %ix%i\n", display.WIDTH, display.HEIGHT); history_idx = 0; - fft.set_scale(height * .318f); + fft.set_scale(display.HEIGHT * .318f); - for(auto i = 0u; i < width; i++) { - float h = float(i) / width; + for(auto i = 0u; i < display.WIDTH; i++) { + float h = float(i) / display.WIDTH; palette_peak[i] = RGB::from_hsv(h, 0.7f, 1.0f); palette_main[i] = RGB::from_hsv(h, 1.0f, 0.7f); } - max_sample_from_fft = 4000.f + 130.f * height; - lower_threshold = 270 - 2 * height; + 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 / (height - 1))); + 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 / (height * (height - 1))); + 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) / (height - 1)); + subtract = float_to_fix15((max_sample_from_fft - lower_threshold) / (display.HEIGHT - 1)); #endif } \ No newline at end of file diff --git a/src/btstack_audio_pico.cpp b/src/btstack_audio_pico.cpp index b674584..ff3113f 100644 --- a/src/btstack_audio_pico.cpp +++ b/src/btstack_audio_pico.cpp @@ -146,7 +146,7 @@ static audio_buffer_pool_t *init_audio(uint32_t sample_frequency, uint8_t channe display.clear(); display.set_pixel(0, 0, 255, 0, 0); - multicore_launch_core1_with_stack(core1_entry, core1_stack, core1_stack_len); + //multicore_launch_core1_with_stack(core1_entry, core1_stack, core1_stack_len); return producer_pool; } @@ -161,12 +161,14 @@ static void btstack_audio_pico_sink_fill_buffers(void){ int16_t * buffer16 = (int16_t *) audio_buffer->buffer->bytes; (*playback_callback)(buffer16, audio_buffer->max_sample_count); - mutex_enter_blocking(&core1_effect_update); + effect.update(buffer16, SAMPLE_COUNT); + + //mutex_enter_blocking(&core1_effect_update); for (auto i = 0u; i < SAMPLE_COUNT; i++) { - effect_buf[i] = buffer16[i]; + //effect_buf[i] = buffer16[i]; buffer16[i] = (int32_t(buffer16[i]) * int32_t(btstack_volume)) >> 8; } - mutex_exit(&core1_effect_update); + //mutex_exit(&core1_effect_update); // duplicate samples for mono if (btstack_audio_pico_channel_count == 1){ From 077d63584d0bf3cfe5be5f0fa12a1e83e7ce06ee Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Sun, 26 Feb 2023 21:41:23 +0000 Subject: [PATCH 09/12] Clear on pause. --- src/btstack_audio_pico.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/btstack_audio_pico.cpp b/src/btstack_audio_pico.cpp index ff3113f..02f7629 100644 --- a/src/btstack_audio_pico.cpp +++ b/src/btstack_audio_pico.cpp @@ -144,7 +144,6 @@ static audio_buffer_pool_t *init_audio(uint32_t sample_frequency, uint8_t channe display.init(); display.clear(); - display.set_pixel(0, 0, 255, 0, 0); //multicore_launch_core1_with_stack(core1_entry, core1_stack, core1_stack_len); @@ -216,8 +215,6 @@ static void btstack_audio_pico_sink_set_volume(uint8_t volume){ } static void btstack_audio_pico_sink_start_stream(void){ - display.set_pixel(0, 2, 0, 255, 0); - // pre-fill HAL buffers btstack_audio_pico_sink_fill_buffers(); @@ -233,14 +230,14 @@ static void btstack_audio_pico_sink_start_stream(void){ } static void btstack_audio_pico_sink_stop_stream(void){ - display.set_pixel(0, 2, 0, 0, 0); - audio_i2s_set_enabled(false); // stop timer btstack_run_loop_remove_timer(&driver_timer_sink); // state btstack_audio_pico_sink_active = false; + + display.clear(); } static void btstack_audio_pico_sink_close(void){ From d2cd1a3addd9be84f7c83f7b25de4e9c7ab29a7f Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Sun, 26 Feb 2023 21:50:14 +0000 Subject: [PATCH 10/12] Fix palette size in ClassicFFT. --- effect/effect.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/effect/effect.hpp b/effect/effect.hpp index a1b0dcd..da2be51 100644 --- a/effect/effect.hpp +++ b/effect/effect.hpp @@ -56,7 +56,7 @@ class ClassicFFT : public Effect { uint history_idx; uint8_t eq_history[Display::WIDTH][HISTORY_LEN]; - RGB palette[Display::WIDTH]; + RGB palette[Display::HEIGHT]; float max_sample_from_fft; int lower_threshold; From b4ac60681541b93caea5c6847206c3f6ea6ce897 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Sun, 26 Feb 2023 21:57:29 +0000 Subject: [PATCH 11/12] Effects stack. --- src/btstack_audio_pico.cpp | 39 ++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/btstack_audio_pico.cpp b/src/btstack_audio_pico.cpp index 02f7629..b6439bd 100644 --- a/src/btstack_audio_pico.cpp +++ b/src/btstack_audio_pico.cpp @@ -66,10 +66,14 @@ Display display; FIX_FFT fft; -RainbowFFT effect(display, fft); +std::vector effects; + +#ifdef EFFECTS_ON_CORE1 constexpr int core1_stack_len = 512; uint32_t core1_stack[512]; +int16_t effect_buf[SAMPLE_COUNT] = {0}; +#endif static constexpr unsigned int BUFFERS_PER_FFT_SAMPLE = 2; static constexpr unsigned int SAMPLES_PER_AUDIO_BUFFER = SAMPLE_COUNT / BUFFERS_PER_FFT_SAMPLE; @@ -94,15 +98,15 @@ static uint8_t btstack_last_sample_idx; auto_init_mutex(core1_effect_update); -int16_t effect_buf[SAMPLE_COUNT] = {0}; - +#ifdef EFFECTS_ON_CORE1 void core1_entry() { while(1) { mutex_enter_blocking(&core1_effect_update); - effect.update(effect_buf, SAMPLE_COUNT); + effects[0]->update(effect_buf, SAMPLE_COUNT); mutex_exit(&core1_effect_update); } } +#endif static audio_buffer_pool_t *init_audio(uint32_t sample_frequency, uint8_t channel_count) { @@ -140,12 +144,19 @@ static audio_buffer_pool_t *init_audio(uint32_t sample_frequency, uint8_t channe assert(ok); (void)ok; - effect.init(sample_frequency); + effects.push_back(new RainbowFFT(display, fft)); + effects.push_back(new ClassicFFT(display, fft)); + + for(auto &effect : effects) { + effect->init(sample_frequency); + } display.init(); display.clear(); - //multicore_launch_core1_with_stack(core1_entry, core1_stack, core1_stack_len); +#ifdef EFFECTS_ON_CORE1 + multicore_launch_core1_with_stack(core1_entry, core1_stack, core1_stack_len); +#endif return producer_pool; } @@ -160,14 +171,22 @@ static void btstack_audio_pico_sink_fill_buffers(void){ int16_t * buffer16 = (int16_t *) audio_buffer->buffer->bytes; (*playback_callback)(buffer16, audio_buffer->max_sample_count); - effect.update(buffer16, SAMPLE_COUNT); +#ifndef EFFECTS_ON_CORE1 + effects[0]->update(buffer16, SAMPLE_COUNT); +#endif - //mutex_enter_blocking(&core1_effect_update); +#ifdef EFFECTS_ON_CORE1 + mutex_enter_blocking(&core1_effect_update); +#endif for (auto i = 0u; i < SAMPLE_COUNT; i++) { - //effect_buf[i] = buffer16[i]; +#ifdef EFFECTS_ON_CORE1 + effect_buf[i] = buffer16[i]; +#endif buffer16[i] = (int32_t(buffer16[i]) * int32_t(btstack_volume)) >> 8; } - //mutex_exit(&core1_effect_update); +#ifdef EFFECTS_ON_CORE1 + mutex_exit(&core1_effect_update); +#endif // duplicate samples for mono if (btstack_audio_pico_channel_count == 1){ From f1ebcd0fe5c8d1fee05ce64e432bfc737d5dc068 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Sun, 26 Feb 2023 22:13:48 +0000 Subject: [PATCH 12/12] Switchable effects --- src/btstack_audio_pico.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/btstack_audio_pico.cpp b/src/btstack_audio_pico.cpp index b6439bd..ff78d55 100644 --- a/src/btstack_audio_pico.cpp +++ b/src/btstack_audio_pico.cpp @@ -66,8 +66,11 @@ Display display; FIX_FFT fft; +RainbowFFT rainbow_fft(display, fft); +ClassicFFT classic_fft(display, fft); std::vector effects; +unsigned int current_effect = 0; #ifdef EFFECTS_ON_CORE1 constexpr int core1_stack_len = 512; @@ -144,8 +147,8 @@ static audio_buffer_pool_t *init_audio(uint32_t sample_frequency, uint8_t channe assert(ok); (void)ok; - effects.push_back(new RainbowFFT(display, fft)); - effects.push_back(new ClassicFFT(display, fft)); + effects.push_back(&rainbow_fft); + effects.push_back(&classic_fft); for(auto &effect : effects) { effect->init(sample_frequency); @@ -168,11 +171,19 @@ static void btstack_audio_pico_sink_fill_buffers(void){ break; } + if (!gpio_get(Display::SWITCH_A)) { + current_effect = 0; + } + + if (!gpio_get(Display::SWITCH_B)) { + current_effect = 1; + } + int16_t * buffer16 = (int16_t *) audio_buffer->buffer->bytes; (*playback_callback)(buffer16, audio_buffer->max_sample_count); #ifndef EFFECTS_ON_CORE1 - effects[0]->update(buffer16, SAMPLE_COUNT); + effects[current_effect]->update(buffer16, SAMPLE_COUNT); #endif #ifdef EFFECTS_ON_CORE1