Skip to content
Snippets Groups Projects
Commit 0b53d8a5 authored by q3k's avatar q3k
Browse files

components/st3m: implement scope

parent 9ad582e7
No related branches found
No related tags found
No related merge requests found
......@@ -5,7 +5,6 @@ idf_component_register(
captouch.c
espan.c
leds.c
scope.c
synth.c
spio.c
INCLUDE_DIRS
......
#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;
......
#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);
#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);
}
......@@ -3,6 +3,7 @@ idf_component_register(
st3m_gfx.c
st3m_counter.c
st3m_fs.c
st3m_scope.c
INCLUDE_DIRS
.
REQUIRES
......
#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);
}
#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);
......@@ -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);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment