diff --git a/components/badge23/CMakeLists.txt b/components/badge23/CMakeLists.txt index 4c394395e8db8e5069bb810c3e3a7325e826d30c..23ad0f3837614658c140b5f3e78d7d0b7c312884 100644 --- a/components/badge23/CMakeLists.txt +++ b/components/badge23/CMakeLists.txt @@ -5,7 +5,6 @@ idf_component_register( captouch.c espan.c leds.c - scope.c synth.c spio.c INCLUDE_DIRS diff --git a/components/badge23/audio.c b/components/badge23/audio.c index c721c8756152e8d87cf4131b3b1acbce85b26bc5..9e0852547da218cc389fc746207d4c07b30b5e1c 100644 --- a/components/badge23/audio.c +++ b/components/badge23/audio.c @@ -1,8 +1,9 @@ #include "badge23/audio.h" #include "badge23/synth.h" -#include "badge23/scope.h" #include "badge23/lock.h" +#include "st3m_scope.h" + #include "driver/i2s.h" #include "driver/i2c.h" @@ -584,8 +585,9 @@ uint16_t count_audio_sources(){ } static void _audio_init(void) { + st3m_scope_init(); + // TODO: this assumes I2C is already initialized - init_scope(241); i2s_init(); audio_update_jacksense(); //ESP_ERROR_CHECK(i2s_channel_enable(tx_chan)); @@ -606,7 +608,7 @@ static void audio_player_task(void* arg) { sample += (*(audio_source->render_function))(audio_source->render_data); audio_source = audio_source->next; } - write_to_scope((int16_t) (1600. * sample)); + st3m_scope_write((int16_t) (1600. * sample)); sample = software_volume * (sample/10); if(sample > 32767) sample = 32767; if(sample < -32767) sample = -32767; diff --git a/components/badge23/include/badge23/scope.h b/components/badge23/include/badge23/scope.h deleted file mode 100644 index fe21205194e6939af6d740bea809d0e7c05d3799..0000000000000000000000000000000000000000 --- a/components/badge23/include/badge23/scope.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once -#include <stdint.h> - -typedef struct { - int16_t * buffer; - int16_t buffer_size; - int16_t write_head_position; // last written value - // atomic value used to prevent simultaneous reads/writes (not a mutex!). - volatile uint32_t is_being_read; -} scope_t; - -void init_scope(uint16_t size); -void write_to_scope(int16_t value); -void begin_scope_read(); -void end_scope_read(); -void read_line_from_scope(uint16_t * line, int16_t point); diff --git a/components/badge23/scope.c b/components/badge23/scope.c deleted file mode 100644 index ac78a0f6aa79787c78d3b8d8f21e99bc6370e65b..0000000000000000000000000000000000000000 --- a/components/badge23/scope.c +++ /dev/null @@ -1,88 +0,0 @@ -#include "badge23/scope.h" -#include "esp_log.h" -#include <string.h> -#include <freertos/FreeRTOS.h> -#include <freertos/atomic.h> - -scope_t * scope = NULL; -static const char* TAG = "scope"; - -void init_scope(uint16_t size){ - if (scope != NULL) { - return; - } - - scope_t * scp = malloc(sizeof(scope_t)); - if(scp == NULL) { - ESP_LOGE(TAG, "scope allocation failed, out of memory"); - return; - } - scp->buffer_size = size; - scp->buffer = malloc(sizeof(int16_t) * scp->buffer_size); - if(scp->buffer == NULL){ - free(scp); - ESP_LOGE(TAG, "scope buffer allocation failed, out of memory"); - return; - } - memset(scp->buffer, 0, sizeof(int16_t) * scp->buffer_size); - scope = scp; - ESP_LOGI(TAG, "initialized"); -} - -static inline bool scope_being_read(void) { - return Atomic_CompareAndSwap_u32(&scope->is_being_read, 0, 0) == ATOMIC_COMPARE_AND_SWAP_FAILURE; -} - -void write_to_scope(int16_t value){ - if(scope == NULL || scope_being_read()) { - return; - } - - scope->write_head_position++; - if(scope->write_head_position >= scope->buffer_size) scope->write_head_position = 0; - scope->buffer[scope->write_head_position] = value; -} - -void begin_scope_read(void) { - if (scope == NULL) { - return; - } - Atomic_Increment_u32(&scope->is_being_read); -} - -void read_line_from_scope(uint16_t * line, int16_t point){ - if (scope == NULL) { - return; - } - memset(line, 0, 480); - int16_t startpoint = scope->write_head_position - point; - while(startpoint < 0){ - startpoint += scope->buffer_size; - } - int16_t stoppoint = startpoint - 1; - if(stoppoint < 0){ - stoppoint += scope->buffer_size; - } - int16_t start = (scope->buffer[point]/32 + 120); - int16_t stop = (scope->buffer[point+1]/32 + 120); - if(start>240) start = 240; - if(start<0) start = 0; - if(stop>240) stop = 240; - if(stop<0) stop = 0; - - if(start > stop){ - int16_t inter = start; - start = stop; - stop = inter; - } - for(int16_t i = start; i <= stop; i++){ - line[i] = 255; - } -} - -void end_scope_read(void) { - if (scope == NULL) { - return; - } - Atomic_Decrement_u32(&scope->is_being_read); -} diff --git a/components/st3m/CMakeLists.txt b/components/st3m/CMakeLists.txt index 2504ce806a0d2273bc21defa403e4debcdafd25b..ec395c2c44e78dcbe08be49fc97eab37061bc60f 100644 --- a/components/st3m/CMakeLists.txt +++ b/components/st3m/CMakeLists.txt @@ -3,6 +3,7 @@ idf_component_register( st3m_gfx.c st3m_counter.c st3m_fs.c + st3m_scope.c INCLUDE_DIRS . REQUIRES diff --git a/components/st3m/st3m_scope.c b/components/st3m/st3m_scope.c new file mode 100644 index 0000000000000000000000000000000000000000..b9a21284c178370b7d44c69eb261b66eeac24044 --- /dev/null +++ b/components/st3m/st3m_scope.c @@ -0,0 +1,119 @@ +#include "st3m_scope.h" + +#include <string.h> + +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/atomic.h" + +#include "ctx_config.h" +#include "ctx.h" + +st3m_scope_t scope = { 0, }; +static const char* TAG = "st3m-scope"; + +void st3m_scope_init(void){ + if (scope.write_buffer != NULL) { + return; + } + + scope.buffer_size = 240; + scope.write_buffer = malloc(sizeof(int16_t) * scope.buffer_size); + scope.exchange_buffer = malloc(sizeof(int16_t) * scope.buffer_size); + scope.read_buffer = malloc(sizeof(int16_t) * scope.buffer_size); + + if (scope.write_buffer == NULL || scope.exchange_buffer == NULL || scope.read_bufer == NULL) { + if (scope.write_buffer != NULL) { + free(scope.write_buffer); + scope.write_buffer = NULL; + } + if (scope.exchange_buffer != NULL) { + free(scope.exchange_buffer); + scope.exchange_buffer = NULL; + } + if (scope.read_buffer != NULL) { + free(scope.read_buffer); + scope.read_buffer = NULL; + } + ESP_LOGE(TAG, "out of memory"); + return; + } + + memset(scope.write_buffer, 0, sizeof(int16_t) * scope.buffer_size); + memset(scope.exchange_buffer, 0, sizeof(int16_t) * scope.buffer_size); + memset(scope.read_buffer, 0, sizeof(int16_t) * scope.buffer_size); + + + scope.write_head_position = 0; + scope.prev_write_attempt = 0; + ESP_LOGI(TAG, "initialized"); +} + +void st3m_scope_write(int16_t value){ + if(scope.write_buffer == NULL) { + return; + } + + int16_t prev_write_attempt = scope.prev_write_attempt; + scope.prev_write_attempt = value; + + // If we're about to write the first sample, make sure we do so at a + // positive zero crossing. + if (scope.write_head_position == 0) { + // Calculate 'positivity' sign of this value and previous value. + int16_t this = value > 0; + int16_t prev = prev_write_attempt > 0; + + if (this != 1 || prev != 0) { + return; + } + } + + if (scope.write_head_position >= scope.buffer_size) { + scope.write_buffer = Atomic_SwapPointers_p32(&scope.exchange_buffer, scope.write_buffer); + scope.write_head_position = 0; + } else { + scope.write_buffer[scope.write_head_position] = value; + scope.write_head_position++; + } +} + +void st3m_scope_draw(Ctx *ctx){ + if (scope.write_buffer == NULL) { + return; + } + + scope.read_buffer = Atomic_SwapPointers_p32(&scope.exchange_buffer, scope.read_buffer); + + + // How much to divide the values persisted in the buffer to scale to + // -120+120. + // + // shift == 5 -> division by 2^5 -> division by 32. This seems to work for + // the value currently emitted by the audio stack. + int16_t shift = 5; + + // How many samples to skip for each drawn line segment. + // + // decimate == 1 works, but is a waste of CPU cycles both at draw and + // rasterization time. + // + // decimate == 2 -> every second sample is drawn (240/2 == 120 line segments + // are drawn). Looks good enough. + size_t decimate = 2; + + int x = -120; + int y = scope.read_buffer[0] >> shift; + ctx_move_to(ctx, x, y); + for (size_t i = 1; i < scope.buffer_size; i += decimate) { + x += decimate; + int y = scope.read_buffer[i] >> shift; + ctx_line_to(ctx, x, y); + } + + ctx_line_to(ctx, 130, 0); + ctx_line_to(ctx, 130, -130); + ctx_line_to(ctx, -130, -130); + ctx_line_to(ctx, -130, 0); + +} diff --git a/components/st3m/st3m_scope.h b/components/st3m/st3m_scope.h new file mode 100644 index 0000000000000000000000000000000000000000..fc8d4074e6d773b59d76676cf1288c43eb7e1923 --- /dev/null +++ b/components/st3m/st3m_scope.h @@ -0,0 +1,51 @@ +#pragma once + +// st3m_scope implements an oscilloscope-style music visualizer. +// +// The audio subsystem will continuously send the global mixing output result +// into the oscilloscope. User code can decide when to draw said scope. + +#include <stdint.h> + +#include "flow3r_bsp.h" +#include "st3m_gfx.h" + +typedef struct { + // Scope buffer size, in samples. Currently always 240 (same as screen + // width). + size_t buffer_size; + + // Triple-buffering for lockless exhange between free-running writer and + // reader. The exchange buffer is swapped to/from by the reader/writer + // whenever they're done with a whole sample buffer. + int16_t *write_buffer; + int16_t *exchange_buffer; + int16_t *read_buffer; + + // Offset where the write handler should write the next sample. + uint32_t write_head_position; + // Previous sample that was attempted to be written. Used for + // zero-detection. + int16_t prev_write_attempt; +} st3m_scope_t; + +// Initialize global scope. Must be performed before any other access to scope +// is attempted. +// +// If initialization failes (eg. due to lack of memory) an error will be +// printed. +void st3m_scope_init(void); + +// Write a sound sample to the scope. +void st3m_scope_write(int16_t value); + +// Draw the scope at bounding box -120/-120 +120/+120. +// +// The scope will be drawn as a closable line segment starting at x:-120 +// y:sample[0], through x:120 y:sample[239], then going through x:130 y:130, +// x:-130 y:130. +// +// The user is responsible for setting a color and running a fill/stroke +// afterwards. +void st3m_scope_draw(Ctx *ctx); + diff --git a/usermodule/mp_hardware.c b/usermodule/mp_hardware.c index b7b62c4dffbf7dc45da65376ef91210ab40cebca..7f40a738bb02129bb60a11d1cdfc5b6c16619a93 100644 --- a/usermodule/mp_hardware.c +++ b/usermodule/mp_hardware.c @@ -18,6 +18,7 @@ #include "flow3r_bsp.h" #include "st3m_gfx.h" +#include "st3m_scope.h" #include "ctx_config.h" #include "ctx.h" @@ -217,6 +218,16 @@ STATIC mp_obj_t mp_display_pipe_flush(void) { } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_display_pipe_flush_obj, mp_display_pipe_flush); +STATIC mp_obj_t mp_scope_draw(mp_obj_t ctx_in) { + // TODO(q3k): check in_ctx? Or just drop from API? + + if (gfx_last_desc != NULL) { + st3m_scope_draw(gfx_last_desc->ctx); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_scope_draw_obj, mp_scope_draw); + STATIC const mp_rom_map_elem_t mp_module_hardware_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_badge_audio) }, { MP_ROM_QSTR(MP_QSTR_init_done), MP_ROM_PTR(&mp_init_done_obj) }, @@ -253,6 +264,8 @@ STATIC const mp_rom_map_elem_t mp_module_hardware_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_BUTTON_PRESSED_RIGHT), MP_ROM_INT(BUTTON_PRESSED_RIGHT) }, { MP_ROM_QSTR(MP_QSTR_BUTTON_PRESSED_DOWN), MP_ROM_INT(BUTTON_PRESSED_DOWN) }, { MP_ROM_QSTR(MP_QSTR_BUTTON_NOT_PRESSED), MP_ROM_INT(BUTTON_NOT_PRESSED) }, + + { MP_ROM_QSTR(MP_QSTR_scope_draw), MP_ROM_PTR(&mp_scope_draw_obj) }, }; STATIC MP_DEFINE_CONST_DICT(mp_module_hardware_globals, mp_module_hardware_globals_table);