Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • flow3r/flow3r-firmware
  • Vespasian/flow3r-firmware
  • alxndr42/flow3r-firmware
  • pl/flow3r-firmware
  • Kari/flow3r-firmware
  • raimue/flow3r-firmware
  • grandchild/flow3r-firmware
  • mu5tach3/flow3r-firmware
  • Nervengift/flow3r-firmware
  • arachnist/flow3r-firmware
  • TheNewCivilian/flow3r-firmware
  • alibi/flow3r-firmware
  • manuel_v/flow3r-firmware
  • xeniter/flow3r-firmware
  • maxbachmann/flow3r-firmware
  • yGifoom/flow3r-firmware
  • istobic/flow3r-firmware
  • EiNSTeiN_/flow3r-firmware
  • gnudalf/flow3r-firmware
  • 999eagle/flow3r-firmware
  • toerb/flow3r-firmware
  • pandark/flow3r-firmware
  • teal/flow3r-firmware
  • x42/flow3r-firmware
  • alufers/flow3r-firmware
  • dos/flow3r-firmware
  • yrlf/flow3r-firmware
  • LuKaRo/flow3r-firmware
  • ThomasElRubio/flow3r-firmware
  • ai/flow3r-firmware
  • T_X/flow3r-firmware
  • highTower/flow3r-firmware
  • beanieboi/flow3r-firmware
  • Woazboat/flow3r-firmware
  • gooniesbro/flow3r-firmware
  • marvino/flow3r-firmware
  • kressnerd/flow3r-firmware
  • quazgar/flow3r-firmware
  • aoid/flow3r-firmware
  • jkj/flow3r-firmware
  • naomi/flow3r-firmware
41 results
Select Git revision
Show changes
Showing
with 900 additions and 426 deletions
......@@ -35,7 +35,13 @@ void gc_init(void *start, void *end);
#if MICROPY_GC_SPLIT_HEAP
// Used to add additional memory areas to the heap.
void gc_add(void *start, void *end);
#endif
#if MICROPY_GC_SPLIT_HEAP_AUTO
// Port must implement this function to return the maximum available block of
// RAM to allocate a new heap area into using MP_PLAT_ALLOC_HEAP.
size_t gc_get_max_new_split(void);
#endif // MICROPY_GC_SPLIT_HEAP_AUTO
#endif // MICROPY_GC_SPLIT_HEAP
// These lock/unlock functions can be nested.
// They can be used to prevent the GC from allocating/freeing.
......@@ -69,6 +75,9 @@ typedef struct _gc_info_t {
size_t num_1block;
size_t num_2block;
size_t max_block;
#if MICROPY_GC_SPLIT_HEAP_AUTO
size_t max_new_split;
#endif
} gc_info_t;
void gc_info(gc_info_t *info);
......
......@@ -64,7 +64,12 @@ MP_DEFINE_CONST_FUN_OBJ_0(gc_isenabled_obj, gc_isenabled);
STATIC mp_obj_t gc_mem_free(void) {
gc_info_t info;
gc_info(&info);
#if MICROPY_GC_SPLIT_HEAP_AUTO
// Include max_new_split value here as a more useful heuristic
return MP_OBJ_NEW_SMALL_INT(info.free + info.max_new_split);
#else
return MP_OBJ_NEW_SMALL_INT(info.free);
#endif
}
MP_DEFINE_CONST_FUN_OBJ_0(gc_mem_free_obj, gc_mem_free);
......
......@@ -616,6 +616,11 @@
#define MICROPY_GC_SPLIT_HEAP (0)
#endif
// Whether regions should be added/removed from the split heap as needed.
#ifndef MICROPY_GC_SPLIT_HEAP_AUTO
#define MICROPY_GC_SPLIT_HEAP_AUTO (0)
#endif
// Hook to run code during time consuming garbage collector operations
#ifndef MICROPY_GC_HOOK_LOOP
#define MICROPY_GC_HOOK_LOOP
......@@ -1847,6 +1852,16 @@ typedef double mp_float_t;
#define MP_PLAT_FREE_EXEC(ptr, size) m_del(byte, ptr, size)
#endif
// Allocating new heap area at runtime requires port to be able to allocate from system heap
#if MICROPY_GC_SPLIT_HEAP_AUTO
#ifndef MP_PLAT_ALLOC_HEAP
#define MP_PLAT_ALLOC_HEAP(size) malloc(size)
#endif
#ifndef MP_PLAT_FREE_HEAP
#define MP_PLAT_FREE_HEAP(ptr) free(ptr)
#endif
#endif
// This macro is used to do all output (except when MICROPY_PY_IO is defined)
#ifndef MP_PLAT_PRINT_STRN
#define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len)
......
......@@ -50,6 +50,17 @@ STATIC void plat_print_strn(void *env, const char *str, size_t len) {
const mp_print_t mp_plat_print = {NULL, plat_print_strn};
int mp_print_indent(const mp_print_t * print, const char * indent_str){
if(!indent_str) return 0;
int len = 0;
len += mp_print_str(print, "\n");
size_t num_indents = MP_PRINT_GET_EXT(print)->indent_depth;
for(size_t i = 0; i < num_indents; i++){
len += mp_print_str(print, indent_str);
}
return len;
}
int mp_print_str(const mp_print_t *print, const char *str) {
size_t len = strlen(str);
if (len) {
......
......@@ -56,6 +56,8 @@ typedef struct _mp_print_ext_t {
mp_print_t base;
const char *item_separator;
const char *key_separator;
const char *indent;
size_t indent_depth;
} mp_print_ext_t;
#define MP_PRINT_GET_EXT(print) ((mp_print_ext_t *)print)
......@@ -68,6 +70,7 @@ extern const mp_print_t mp_plat_print;
extern const mp_print_t mp_sys_stdout_print;
#endif
int mp_print_indent(const mp_print_t * print, const char * indent_str);
int mp_print_str(const mp_print_t *print, const char *str);
int mp_print_strn(const mp_print_t *print, const char *str, size_t len, int flags, char fill, int width);
#if MICROPY_PY_BUILTINS_FLOAT
......
......@@ -87,6 +87,7 @@ typedef struct _mp_state_mem_area_t {
byte *gc_pool_end;
size_t gc_last_free_atb_index;
size_t gc_last_used_block; // The block ID of the highest block allocated in the area
} mp_state_mem_area_t;
// This structure hold information about the memory allocation system.
......
......@@ -86,13 +86,23 @@ STATIC void dict_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_
if (MICROPY_PY_COLLECTIONS_ORDEREDDICT && self->base.type != &mp_type_dict && kind != PRINT_JSON) {
mp_printf(print, "%q(", self->base.type->name);
}
const char *indent_str = NULL;
if(kind == PRINT_JSON){
indent_str = MP_PRINT_GET_EXT(print)->indent;
}
mp_print_str(print, "{");
size_t cur = 0;
mp_map_elem_t *next = NULL;
if(kind == PRINT_JSON){
MP_PRINT_GET_EXT(print)->indent_depth++;
}
while ((next = dict_iter_next(self, &cur)) != NULL) {
if (!first) {
mp_print_str(print, item_separator);
}
mp_print_indent(print, indent_str);
first = false;
bool add_quote = MICROPY_PY_UJSON && kind == PRINT_JSON && !mp_obj_is_str_or_bytes(next->key);
if (add_quote) {
......@@ -105,6 +115,10 @@ STATIC void dict_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_
mp_print_str(print, key_separator);
mp_obj_print_helper(print, next->value, kind);
}
if(kind == PRINT_JSON){
MP_PRINT_GET_EXT(print)->indent_depth--;
}
mp_print_indent(print, indent_str);
mp_print_str(print, "}");
if (MICROPY_PY_COLLECTIONS_ORDEREDDICT && self->base.type != &mp_type_dict && kind != PRINT_JSON) {
mp_print_str(print, ")");
......
......@@ -160,6 +160,67 @@ qstr mp_obj_fun_get_name(mp_const_obj_t fun_in) {
return mp_obj_code_get_name(fun, bc);
}
// returns a tuple that is somewhat compatible to inspect.FullArgSpec
// FullArgSpec(args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations)
mp_obj_t mp_obj_fun_get_argspec(mp_const_obj_t fun_in) {
mp_obj_t tuple[7] = {
// args
mp_obj_new_list(0, NULL),
// varargs
mp_const_none,
// varkw
mp_const_none,
// defaults
mp_const_none,
// kwonlyargs
mp_obj_new_list(0, NULL),
// kwonlydefaults
mp_const_none,
// annotations
mp_const_none,
};
const mp_obj_fun_bc_t *fun = MP_OBJ_TO_PTR(fun_in);
#if MICROPY_EMIT_NATIVE
if (fun->base.type == &mp_type_fun_native || fun->base.type == &mp_type_native_gen_wrap) {
return mp_const_none;
}
#endif
const byte *bc = fun->bytecode;
MP_BC_PRELUDE_SIG_DECODE(bc);
MP_BC_PRELUDE_SIZE_DECODE(bc);
// args and kwonlyargs
const uint8_t *arg_names = mp_decode_uint_skip(bc);
for (size_t i = 0; i < (n_pos_args + n_kwonly_args); i++) {
qstr arg_qstr = mp_decode_uint(&arg_names);
#if MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE
arg_qstr = fun->context->constants.qstr_table[arg_qstr];
#endif
mp_obj_t target = i >= n_pos_args ? tuple[4] : tuple[0];
mp_obj_list_append(target, MP_OBJ_NEW_QSTR(arg_qstr));
}
// varargs
if ((scope_flags & MP_SCOPE_FLAG_VARARGS) != 0) {
tuple[1] = MP_OBJ_NEW_QSTR(MP_QSTR_args);
}
// varkw
if ((scope_flags & MP_SCOPE_FLAG_VARKEYWORDS) != 0) {
tuple[2] = MP_OBJ_NEW_QSTR(MP_QSTR_kwargs);
}
// defaults: not implemented (tuple of n_def_pos_args length, with
// contents from fun->extra_args)
// kwonlydefaults: not implemented
// annotations: not implemented
return mp_obj_new_tuple(7, tuple);
}
#if MICROPY_CPYTHON_COMPAT
STATIC void fun_bc_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) {
(void)kind;
......@@ -349,6 +410,10 @@ void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in);
dest[0] = MP_OBJ_FROM_PTR(self->context->module.globals);
}
if (attr == MP_QSTR___argspec__) {
dest[0] = mp_obj_fun_get_argspec(self_in);
}
}
#endif
......
......@@ -52,13 +52,26 @@ STATIC void list_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t k
item_separator = MP_PRINT_GET_EXT(print)->item_separator;
#endif
}
const char *indent_str = NULL;
if(kind == PRINT_JSON){
indent_str = MP_PRINT_GET_EXT(print)->indent;
}
mp_print_str(print, "[");
if(kind == PRINT_JSON){
MP_PRINT_GET_EXT(print)->indent_depth++;
}
for (size_t i = 0; i < o->len; i++) {
if (i > 0) {
mp_print_str(print, item_separator);
}
mp_print_indent(print, indent_str);
mp_obj_print_helper(print, o->items[i], kind);
}
if(kind == PRINT_JSON){
MP_PRINT_GET_EXT(print)->indent_depth--;
}
mp_print_indent(print, indent_str);
mp_print_str(print, "]");
}
......
......@@ -39,6 +39,7 @@
void mp_obj_tuple_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) {
mp_obj_tuple_t *o = MP_OBJ_TO_PTR(o_in);
const char *item_separator = ", ";
if (MICROPY_PY_UJSON && kind == PRINT_JSON) {
mp_print_str(print, "[");
......@@ -49,12 +50,24 @@ void mp_obj_tuple_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t
mp_print_str(print, "(");
kind = PRINT_REPR;
}
const char *indent_str = NULL;
if(kind == PRINT_JSON){
indent_str = MP_PRINT_GET_EXT(print)->indent;
}
if(kind == PRINT_JSON){
MP_PRINT_GET_EXT(print)->indent_depth++;
}
for (size_t i = 0; i < o->len; i++) {
if (i > 0) {
mp_print_str(print, item_separator);
}
mp_print_indent(print, indent_str);
mp_obj_print_helper(print, o->items[i], kind);
}
if(kind == PRINT_JSON){
MP_PRINT_GET_EXT(print)->indent_depth--;
}
mp_print_indent(print, indent_str);
if (MICROPY_PY_UJSON && kind == PRINT_JSON) {
mp_print_str(print, "]");
} else {
......
set(ST3M_VERSION_PATH "${CMAKE_CURRENT_BINARY_DIR}/include/st3m_version.c")
if (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/include/")
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include/")
endif()
idf_component_register(
SRCS
st3m_audio.c
......@@ -15,6 +19,8 @@ idf_component_register(
st3m_usb_msc.c
st3m_usb.c
st3m_console.c
st3m_pcm.c
st3m_media.c
st3m_mode.c
st3m_captouch.c
st3m_ringbuffer.c
......@@ -36,6 +42,11 @@ idf_component_register(
esp_timer
esp_netif
usb
audio_mod
audio_mp3
video_mpeg
video_gif
bl00mbox
)
idf_component_get_property(tusb_lib tinyusb COMPONENT_LIB)
......
menu "debug config"
choice DEBUG_GDB
prompt "gdb or tinyusb/mass storage"
default DEBUG_GDB_DISABLED
config DEBUG_GDB_DISABLED
bool "tinyusb/mass storage mode, no gdb"
config DEBUG_GDB_ENABLED
bool "usb gdb mode, no tinyusb/mass storage"
endchoice
endmenu
......@@ -10,107 +10,13 @@ and the release process.
import subprocess
import sys
import os
class Tag:
def __init__(self, name, rc):
self.name = name
self.rc = rc
def __repr__(self):
return self.name
def tags_for_commit(release, commit):
res = []
tags = (
subprocess.check_output(
[
"git",
"tag",
"--contains",
commit,
]
)
.decode()
.strip()
)
for tag in tags.split("\n"):
tag = tag.strip()
if not tag:
continue
if not tag.startswith("v" + release):
continue
if tag == "v" + release:
res.append(Tag(tag, False))
continue
if tag.startswith("v" + release + "+rc"):
res.append(Tag(tag, True))
continue
return res
def get_version():
commit = subprocess.check_output(["git", "rev-parse", "HEAD"]).decode().strip()
branches = (
subprocess.check_output(
[
"git",
"branch",
"--format",
"%(refname)",
"--contains",
commit,
]
)
.decode()
.strip()
)
release = None
for branch in branches.split("\n"):
branch = branch.strip()
if not branch:
continue
parts = branch.split("/")
if len(parts) != 4:
continue
if parts[:3] != ["refs", "heads", "release"]:
continue
v = parts[3]
release = v
break
main_count = (
subprocess.check_output(
[
"git",
"rev-list",
"--count",
commit,
]
)
.decode()
.strip()
)
version = None
if release is None:
version = f"v0-dev{main_count}"
return version
tags = tags_for_commit(release, commit)
if not tags:
return f"v{release}-dev{main_count}"
releases = sorted([t for t in tags if not t.rc])
candidates = sorted([t for t in tags if t.rc])
if releases:
return str(releases[0])
else:
return str(candidates[0])
def get_git_based_version():
return subprocess.check_output(
["git", "describe", "--tags"]
).decode().strip()
fmt = None
......@@ -118,7 +24,15 @@ if len(sys.argv) > 1:
if sys.argv[1] == "-c":
fmt = "C"
v = get_version()
v = None
if os.environ.get('CI') is not None:
tag = os.environ.get('CI_COMMIT_TAG')
if tag is not None:
# If we're building a tag, just use that as a version.
v = tag
if v is None:
v = get_git_based_version()
if fmt == "C":
print('const char *st3m_version = "' + v + '";')
else:
......
#include "st3m_audio.h"
#include "st3m_scope.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "flow3r_bsp.h"
#include "flow3r_bsp_max98091.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "bl00mbox.h"
#include "st3m_pcm.h"
static const char *TAG = "st3m-audio";
// TODO: clean up
static void bl00mbox_init_wrapper(uint32_t sample_rate, uint16_t max_len) {
bl00mbox_init();
}
static bool bl00mbox_audio_render_wrapper(int16_t *rx, int16_t *tx,
uint16_t len) {
bl00mbox_audio_render(rx, tx, len);
return true;
}
/* You can add your own audio engine here by simply adding a valid struct to
* this list! For details about the fields check out st3m_audio.h.
*/
static const st3m_audio_engine_t engines[] = {
{
.name = "bl00mbox",
.render_fun = bl00mbox_audio_render_wrapper,
.init_fun = bl00mbox_init_wrapper,
},
{
.name = "PCM",
.render_fun = st3m_pcm_audio_render,
.init_fun = NULL,
}
};
static const uint8_t num_engines =
(sizeof(engines)) / (sizeof(st3m_audio_engine_t));
typedef struct {
int32_t volume;
bool mute;
bool active; // whether the engine has been filling tx in the last run
} _engine_data_t;
#define TIMEOUT_MS 1000
static void _audio_player_task(void *data);
......@@ -29,7 +70,7 @@ static bool _headphones_connected(void);
const static float headphones_maximum_volume_system_dB = 3;
const static float speaker_maximum_volume_system_dB = 14;
// Output, either speakers or headphones. Holds volume/mute state and limits,
// Output, either speaker or headphones. Holds volume/mute state and limits,
// and calculated software volume.
//
// An output's apply function configures the actual physical output, ie. by
......@@ -63,7 +104,6 @@ static float _output_set_volume(st3m_audio_output_t *out, float vol_dB) {
}
if (vol_dB < out->volume_min) {
vol_dB = SILLY_LOW_VOLUME_DB;
out->mute = true;
}
out->volume = vol_dB;
_output_apply(out);
......@@ -130,6 +170,7 @@ static float _output_get_volume_relative(st3m_audio_output_t *out) {
static void _audio_headphones_apply(st3m_audio_output_t *out) {
bool mute = out->mute;
float vol_dB = out->volume;
if (out->volume < (SILLY_LOW_VOLUME_DB + 1)) mute = true;
bool headphones = _headphones_connected();
if (!headphones) {
......@@ -152,6 +193,7 @@ static void _audio_headphones_apply(st3m_audio_output_t *out) {
static void _audio_speaker_apply(st3m_audio_output_t *out) {
bool mute = out->mute;
float vol_dB = out->volume;
if (out->volume < (SILLY_LOW_VOLUME_DB + 1)) mute = true;
bool headphones = _headphones_connected();
if (headphones) {
......@@ -174,24 +216,43 @@ static void _audio_speaker_apply(st3m_audio_output_t *out) {
typedef struct {
flow3r_bsp_audio_jacksense_state_t jacksense;
// True if system should pretend headphones are plugged in.
// True if system should ignore jacksense and use
// headphones_detection_override_state to determine
// whether headphones are connected (true) or not (false)
bool headphones_detection_override;
// Defaults to true
bool headphones_detection_override_state;
// The two output channels.
st3m_audio_output_t headphones;
st3m_audio_output_t speaker;
// Denormalized setting data that can be read back by user.
float headset_mic_gain_dB;
int16_t headset_mic_gain_software;
float onboard_mic_gain_dB;
int16_t onboard_mic_gain_software;
float line_in_gain_dB;
int16_t line_in_gain_software;
uint8_t speaker_eq_on;
bool headset_mic_allowed;
bool onboard_mic_allowed;
bool line_in_allowed;
bool onboard_mic_to_speaker_allowed;
st3m_audio_input_source_t engines_source;
st3m_audio_input_source_t engines_target_source;
st3m_audio_input_source_t thru_source;
st3m_audio_input_source_t thru_target_source;
st3m_audio_input_source_t source;
uint8_t headset_gain;
_engine_data_t *engines_data;
// Software-based audio pipe settings.
int32_t input_thru_vol;
int32_t input_thru_vol_int;
bool input_thru_mute;
// Main player function callback.
st3m_audio_player_function_t function;
} st3m_audio_state_t;
SemaphoreHandle_t state_mutex;
......@@ -206,6 +267,7 @@ static st3m_audio_state_t state = {
.line_in = false,
},
.headphones_detection_override = false,
.headphones_detection_override_state = true,
.headphones =
{
.volume = 0,
......@@ -225,12 +287,28 @@ static st3m_audio_state_t state = {
.apply = _audio_speaker_apply,
},
.source = st3m_audio_input_source_none,
.headset_gain = 0,
.speaker_eq_on = true,
.headset_mic_allowed = true,
.onboard_mic_allowed = true,
.line_in_allowed = true,
.onboard_mic_to_speaker_allowed = false,
.headset_mic_gain_dB = 0,
.onboard_mic_gain_dB = 0,
.line_in_gain_dB = 0,
.headset_mic_gain_software = 256,
.onboard_mic_gain_software = 256,
.line_in_gain_software = 256,
.input_thru_vol = 0,
.input_thru_vol_int = 0,
.input_thru_mute = true,
.function = st3m_audio_player_function_dummy,
.input_thru_vol_int = 32768,
.input_thru_mute = false, // deprecated
.engines_target_source = st3m_audio_input_source_none,
.engines_source = st3m_audio_input_source_none,
.thru_source = st3m_audio_input_source_none,
.thru_target_source = st3m_audio_input_source_none,
.source = st3m_audio_input_source_none,
};
// Returns whether we should be outputting audio through headphones. If not,
......@@ -238,48 +316,251 @@ static st3m_audio_state_t state = {
//
// Lock must be taken.
static bool _headphones_connected(void) {
return state.jacksense.headphones || state.headphones_detection_override;
if (state.headphones_detection_override) {
return state.headphones_detection_override_state;
}
return state.jacksense.headphones;
}
static void _audio_input_set_source(st3m_audio_input_source_t source) {
LOCK;
st3m_audio_input_source_t prev_source = state.source;
UNLOCK;
if (source == prev_source) return;
LOCK;
state.source = source;
UNLOCK;
switch (source) {
case st3m_audio_input_source_line_in:
flow3r_bsp_audio_input_set_source(
flow3r_bsp_audio_input_source_line_in);
break;
case st3m_audio_input_source_onboard_mic:
flow3r_bsp_audio_input_set_source(
flow3r_bsp_audio_input_source_onboard_mic);
break;
case st3m_audio_input_source_headset_mic:
flow3r_bsp_audio_input_set_source(
flow3r_bsp_audio_input_source_headset_mic);
break;
default:
flow3r_bsp_audio_input_set_source(
flow3r_bsp_audio_input_source_none);
break;
}
}
static bool _check_engines_source_avail(st3m_audio_input_source_t source) {
switch (source) {
case st3m_audio_input_source_none:
return true;
case st3m_audio_input_source_auto:
return true;
case st3m_audio_input_source_line_in:
return state.line_in_allowed && state.jacksense.line_in;
case st3m_audio_input_source_headset_mic:
return state.headset_mic_allowed && state.jacksense.headset;
case st3m_audio_input_source_onboard_mic:
return state.onboard_mic_allowed;
}
return false;
}
bool st3m_audio_input_engines_get_source_avail(
st3m_audio_input_source_t source) {
bool ret = false;
if (source == st3m_audio_input_source_auto) {
LOCK;
ret =
ret || _check_engines_source_avail(st3m_audio_input_source_line_in);
ret = ret ||
_check_engines_source_avail(st3m_audio_input_source_headset_mic);
ret = ret ||
_check_engines_source_avail(st3m_audio_input_source_onboard_mic);
UNLOCK;
} else {
LOCK;
ret = _check_engines_source_avail(source);
UNLOCK;
}
return ret;
}
void _update_engines_source() {
st3m_audio_input_source_t source;
LOCK;
source = state.engines_target_source;
if (source == st3m_audio_input_source_auto) {
if (_check_engines_source_avail(st3m_audio_input_source_line_in)) {
source = st3m_audio_input_source_line_in;
} else if (_check_engines_source_avail(
st3m_audio_input_source_headset_mic)) {
source = st3m_audio_input_source_headset_mic;
} else if (_check_engines_source_avail(
st3m_audio_input_source_onboard_mic)) {
source = st3m_audio_input_source_onboard_mic;
} else {
source = st3m_audio_input_source_none;
}
}
bool switch_source = _check_engines_source_avail(source);
source = switch_source ? source : st3m_audio_input_source_none;
switch_source = switch_source && (state.engines_source != source);
state.engines_source = source;
UNLOCK;
if (switch_source) _audio_input_set_source(source);
}
void st3m_audio_input_engines_set_source(st3m_audio_input_source_t source) {
LOCK;
state.engines_target_source = source;
UNLOCK;
}
static bool _check_thru_source_avail(st3m_audio_input_source_t source) {
bool avail = _check_engines_source_avail(source);
if ((source == st3m_audio_input_source_onboard_mic) &&
(!state.onboard_mic_to_speaker_allowed) &&
(!state.jacksense.headphones)) {
avail = false;
}
if ((state.engines_source != st3m_audio_input_source_none) &&
(state.engines_source != source)) {
avail = false;
}
return avail;
}
bool st3m_audio_input_thru_get_source_avail(st3m_audio_input_source_t source) {
bool ret = false;
if (source == st3m_audio_input_source_auto) {
LOCK;
ret = ret || _check_thru_source_avail(st3m_audio_input_source_line_in);
ret = ret ||
_check_thru_source_avail(st3m_audio_input_source_headset_mic);
ret = ret ||
_check_thru_source_avail(st3m_audio_input_source_onboard_mic);
UNLOCK;
} else {
LOCK;
ret = _check_thru_source_avail(source);
UNLOCK;
}
return ret;
}
void _update_thru_source() {
st3m_audio_input_source_t source;
LOCK;
source = state.thru_target_source;
if (source == st3m_audio_input_source_auto) {
if ((state.engines_source != st3m_audio_input_source_none) &&
((state.engines_source != st3m_audio_input_source_onboard_mic) ||
(state.jacksense.headphones))) {
source = state.engines_source;
} else if (_check_thru_source_avail(st3m_audio_input_source_line_in)) {
source = st3m_audio_input_source_line_in;
} else if (_check_thru_source_avail(
st3m_audio_input_source_headset_mic)) {
source = st3m_audio_input_source_headset_mic;
} else if (_check_thru_source_avail(
st3m_audio_input_source_onboard_mic) &&
(state.jacksense.headphones)) {
source = st3m_audio_input_source_onboard_mic;
} else {
source = st3m_audio_input_source_none;
}
}
bool switch_source = _check_thru_source_avail(source);
source = switch_source ? source : st3m_audio_input_source_none;
if (state.engines_source != st3m_audio_input_source_none) {
if (state.engines_source == source) {
state.thru_source = state.engines_source;
} else {
state.thru_source = st3m_audio_input_source_none;
}
switch_source = false;
}
state.thru_source = source;
if (state.thru_source == state.source) {
switch_source = false;
}
UNLOCK;
if (switch_source) _audio_input_set_source(source);
}
static void _update_jacksense() {
void st3m_audio_input_thru_set_source(st3m_audio_input_source_t source) {
LOCK;
state.thru_target_source = source;
UNLOCK;
}
void _update_sink() {
flow3r_bsp_audio_jacksense_state_t st;
flow3r_bsp_audio_read_jacksense(&st);
static bool _speaker_eq_on_prev = false;
// Update volume to trigger mutes if needed. But only do that if the
// jacks actually changed.
LOCK;
if (memcmp(&state.jacksense, &st,
sizeof(flow3r_bsp_audio_jacksense_state_t)) != 0) {
if ((memcmp(&state.jacksense, &st,
sizeof(flow3r_bsp_audio_jacksense_state_t)) != 0) ||
(_speaker_eq_on_prev != state.speaker_eq_on)) {
memcpy(&state.jacksense, &st,
sizeof(flow3r_bsp_audio_jacksense_state_t));
_output_apply(&state.speaker);
_output_apply(&state.headphones);
bool _speaker_eq = (!_headphones_connected()) && state.speaker_eq_on;
flow3r_bsp_max98091_set_speaker_eq(_speaker_eq);
_speaker_eq_on_prev = state.speaker_eq_on;
}
UNLOCK;
}
void st3m_audio_player_function_dummy(int16_t *rx, int16_t *tx, uint16_t len) {
for (uint16_t i = 0; i < len; i++) {
tx[i] = 0;
}
static void _update_routing() {
// order important
_update_sink();
_update_engines_source();
_update_thru_source();
}
void st3m_audio_init(void) {
state_mutex = xSemaphoreCreateRecursiveMutex();
assert(state_mutex != NULL);
state.function = st3m_audio_player_function_dummy;
flow3r_bsp_audio_init();
{
_engine_data_t *tmp = malloc(sizeof(_engine_data_t) * num_engines);
LOCK;
state.engines_data = tmp;
UNLOCK;
}
st3m_audio_input_thru_set_volume_dB(-20);
_update_jacksense();
for (uint8_t i = 0; i < num_engines; i++) {
LOCK;
state.engines_data[i].volume = 4096;
state.engines_data[i].mute = false;
state.engines_data[i].active = false; // is ignored by engine anyways
UNLOCK;
if (engines[i].init_fun != NULL) {
(*engines[i].init_fun)(FLOW3R_BSP_AUDIO_SAMPLE_RATE,
FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE);
}
}
_update_routing();
_output_apply(&state.speaker);
_output_apply(&state.headphones);
bool _speaker_eq = (!_headphones_connected()) && state.speaker_eq_on;
flow3r_bsp_max98091_set_speaker_eq(_speaker_eq);
xTaskCreate(&_audio_player_task, "audio", 10000, NULL,
configMAX_PRIORITIES - 1, NULL);
xTaskCreate(&_jacksense_update_task, "jacksense", 2048, NULL,
configMAX_PRIORITIES - 2, NULL);
xTaskCreate(&_jacksense_update_task, "jacksense", 2048, NULL, 8, NULL);
ESP_LOGI(TAG, "Audio task started");
}
......@@ -288,11 +569,15 @@ static void _audio_player_task(void *data) {
int16_t buffer_tx[FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2];
int16_t buffer_rx[FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2];
int16_t buffer_rx_dummy[FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2];
int32_t output_acc[FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2];
int16_t engines_tx[FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2];
memset(buffer_tx, 0, sizeof(buffer_tx));
memset(buffer_rx, 0, sizeof(buffer_rx));
size_t count;
memset(buffer_rx_dummy, 0, sizeof(buffer_rx_dummy));
bool hwmute = flow3r_bsp_audio_has_hardware_mute();
size_t count;
st3m_audio_input_source_t source_prev = st3m_audio_input_source_none;
while (true) {
count = 0;
......@@ -308,39 +593,132 @@ static void _audio_player_task(void *data) {
continue;
}
int32_t engines_vol[num_engines];
bool engines_mute[num_engines];
bool engines_active[num_engines];
LOCK;
for (uint8_t e = 0; e < num_engines; e++) {
engines_vol[e] = state.engines_data[e].volume;
engines_mute[e] = state.engines_data[e].mute;
}
st3m_audio_input_source_t source = state.source;
st3m_audio_input_source_t engines_source = state.engines_source;
st3m_audio_input_source_t thru_source = state.thru_source;
bool headphones = _headphones_connected();
st3m_audio_player_function_t function = state.function;
int32_t software_volume = headphones ? state.headphones.volume_software
: state.speaker.volume_software;
bool software_mute =
headphones ? state.headphones.mute : state.speaker.mute;
bool input_thru_mute = state.input_thru_mute;
int32_t input_thru_vol_int = state.input_thru_vol_int;
UNLOCK;
(*function)(buffer_rx, buffer_tx, FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2);
// <RX SIGNAL PREPROCESSING>
if (!hwmute && software_mute) {
// Software muting needed. Only used on P1.
for (int i = 0; i < (FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2);
i += 2) {
buffer_tx[i] = 0;
int32_t rx_gain = 256; // unity
uint8_t rx_chan = 3; // stereo = 0; left = 1; right = 2; off = 3;
if (source != source_prev) {
// state change: throw away buffer
source_prev = source;
memset(buffer_rx, 0, sizeof(buffer_rx));
} else {
LOCK;
if (source == st3m_audio_input_source_headset_mic) {
rx_gain = state.headset_mic_gain_software;
rx_chan = 0; // not sure, don't have one here, need to test
} else if (source == st3m_audio_input_source_line_in) {
rx_gain = state.line_in_gain_software;
rx_chan = 0;
} else if (source == st3m_audio_input_source_onboard_mic) {
rx_gain = state.onboard_mic_gain_software;
rx_chan = 1;
}
UNLOCK;
}
if (rx_chan == 0) {
// keep stereo image
for (uint16_t i = 0; i < FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2;
i++) {
buffer_rx[i] = (buffer_rx[i] * rx_gain) >> 8;
}
} else if (rx_chan < 3) {
// mix one of the input channels to both rx stereo chans (easier
// mono sources)
for (uint16_t i = 0; i < FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2;
i++) {
uint16_t j = (i / 2) * 2 + rx_chan - 1;
buffer_rx[i] = (buffer_rx[j] * rx_gain) >> 8;
}
}
int16_t *engines_rx;
if (engines_source == st3m_audio_input_source_none) {
engines_rx = buffer_rx_dummy;
} else {
for (int i = 0; i < (FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2);
i += 2) {
int32_t acc = buffer_tx[i];
engines_rx = buffer_rx;
}
// </RX SIGNAL PREPROCESSING>
acc = (acc * software_volume) >> 15;
// <ACCUMULATING ENGINES>
if (!input_thru_mute) {
acc += (((int32_t)buffer_rx[i]) * input_thru_vol_int) >> 15;
bool output_acc_uninit = true;
for (uint8_t e = 0; e < num_engines; e++) {
// always run function even when muted, else the engine
// might suffer from being deprived of the passage of time
engines_active[e] = (*engines[e].render_fun)(
engines_rx, engines_tx, FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2);
if ((!engines_active[e]) || (!engines_vol[e]) || engines_mute[e])
continue;
if (output_acc_uninit) {
for (uint16_t i = 0; i < FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2;
i++) {
output_acc[i] = (engines_tx[i] * engines_vol[e]) >> 12;
}
} else {
for (uint16_t i = 0; i < FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2;
i++) {
output_acc[i] += (engines_tx[i] * engines_vol[e]) >> 12;
}
}
buffer_tx[i] = acc;
output_acc_uninit = false;
}
if (output_acc_uninit) {
memset(output_acc, 0, sizeof(output_acc));
}
LOCK;
for (uint8_t e = 0; e < num_engines; e++) {
state.engines_data[e].active = engines_active[e];
}
UNLOCK;
// </ACCUMULATING ENGINES>
// <VOLUME AND THRU>
for (uint16_t i = 0; i < FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE; i++) {
st3m_scope_write((output_acc[2 * i] + output_acc[2 * i + 1]) >> 3);
}
for (int i = 0; i < (FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2); i += 1) {
if ((thru_source != st3m_audio_input_source_none) &&
((engines_source == thru_source) ||
(engines_source == st3m_audio_input_source_none)) &&
(!input_thru_mute)) {
output_acc[i] += (buffer_rx[i] * input_thru_vol_int) >> 15;
}
output_acc[i] = (output_acc[i] * software_volume) >> 15;
if (output_acc[i] > 32767) output_acc[i] = 32767;
if (output_acc[i] < -32767) output_acc[i] = -32767;
buffer_tx[i] = output_acc[i];
}
// </VOLUME AND THRU>
flow3r_bsp_audio_write(buffer_tx, sizeof(buffer_tx), &count, 1000);
if (count != sizeof(buffer_tx)) {
ESP_LOGE(TAG, "audio_write: count (%d) != length (%d)\n", count,
......@@ -355,22 +733,25 @@ static void _jacksense_update_task(void *data) {
TickType_t last_wake = xTaskGetTickCount();
while (1) {
vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(100)); // 10 Hz
_update_jacksense();
vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(50)); // 20 Hz
_update_routing();
}
}
// BSP wrappers that don't need locking.
void st3m_audio_headphones_line_in_set_hardware_thru(bool enable) {
return;
flow3r_bsp_audio_headphones_line_in_set_hardware_thru(enable);
}
void st3m_audio_speaker_line_in_set_hardware_thru(bool enable) {
return;
flow3r_bsp_audio_speaker_line_in_set_hardware_thru(enable);
}
void st3m_audio_line_in_set_hardware_thru(bool enable) {
return;
flow3r_bsp_audio_line_in_set_hardware_thru(enable);
}
......@@ -397,43 +778,67 @@ GETTER(float, audio_headphones_get_maximum_volume_dB,
GETTER(float, audio_speaker_get_maximum_volume_dB, state.speaker.volume_max)
GETTER(bool, audio_headphones_get_mute, state.headphones.mute)
GETTER(bool, audio_speaker_get_mute, state.speaker.mute)
GETTER(st3m_audio_input_source_t, audio_input_engines_get_source,
state.engines_source)
GETTER(st3m_audio_input_source_t, audio_input_thru_get_source,
state.thru_source)
GETTER(st3m_audio_input_source_t, audio_input_engines_get_target_source,
state.engines_target_source)
GETTER(st3m_audio_input_source_t, audio_input_thru_get_target_source,
state.thru_target_source)
GETTER(st3m_audio_input_source_t, audio_input_get_source, state.source)
GETTER(uint8_t, audio_headset_get_gain_dB, state.headset_gain)
GETTER(float, audio_headset_mic_get_gain_dB, state.headset_mic_gain_dB)
GETTER(float, audio_onboard_mic_get_gain_dB, state.onboard_mic_gain_dB)
GETTER(float, audio_line_in_get_gain_dB, state.line_in_gain_dB)
GETTER(float, audio_input_thru_get_volume_dB, state.input_thru_vol)
GETTER(bool, audio_input_thru_get_mute, state.input_thru_mute)
GETTER(bool, audio_speaker_get_eq_on, state.speaker_eq_on)
GETTER(bool, audio_headset_mic_get_allowed, state.headset_mic_allowed)
GETTER(bool, audio_onboard_mic_get_allowed, state.onboard_mic_allowed)
GETTER(bool, audio_line_in_get_allowed, state.line_in_allowed)
GETTER(bool, audio_onboard_mic_to_speaker_get_allowed,
state.onboard_mic_to_speaker_allowed)
#undef GETTER
// Locked global API functions.
uint8_t st3m_audio_headset_set_gain_dB(uint8_t gain_dB) {
flow3r_bsp_audio_headset_set_gain_dB(gain_dB);
float st3m_audio_headset_mic_set_gain_dB(float gain_dB) {
if (gain_dB > 42) gain_dB = 42;
if (gain_dB < -9) gain_dB = -9;
int8_t hw_gain = flow3r_bsp_audio_headset_set_gain_dB(gain_dB);
int16_t software_gain = 256. * expf((gain_dB - hw_gain) * NAT_LOG_DB);
LOCK;
state.headset_gain = gain_dB;
state.headset_mic_gain_dB = gain_dB;
state.headset_mic_gain_software = software_gain;
UNLOCK;
return gain_dB;
}
void st3m_audio_input_set_source(st3m_audio_input_source_t source) {
switch (source) {
case st3m_audio_input_source_none:
flow3r_bsp_audio_input_set_source(
flow3r_bsp_audio_input_source_none);
break;
case st3m_audio_input_source_line_in:
flow3r_bsp_audio_input_set_source(
flow3r_bsp_audio_input_source_line_in);
break;
case st3m_audio_input_source_headset_mic:
flow3r_bsp_audio_input_set_source(
flow3r_bsp_audio_input_source_headset_mic);
break;
case st3m_audio_input_source_onboard_mic:
flow3r_bsp_audio_input_set_source(
flow3r_bsp_audio_input_source_onboard_mic);
break;
float st3m_audio_line_in_set_gain_dB(float gain_dB) {
if (gain_dB > 30) gain_dB = 30;
if (gain_dB < -24) gain_dB = -24;
int16_t software_gain = 256. * expf(gain_dB * NAT_LOG_DB);
LOCK;
state.line_in_gain_dB = gain_dB;
state.line_in_gain_software = software_gain;
UNLOCK;
return gain_dB;
}
float st3m_audio_onboard_mic_set_gain_dB(float gain_dB) {
if (gain_dB > 42) gain_dB = 42;
if (gain_dB < 0) gain_dB = 0;
int16_t software_gain = 256. * expf(gain_dB * NAT_LOG_DB);
LOCK;
state.source = source;
state.onboard_mic_gain_dB = gain_dB;
state.onboard_mic_gain_software = software_gain;
UNLOCK;
return gain_dB;
}
void st3m_audio_speaker_set_eq_on(bool enabled) {
LOCK;
state.speaker_eq_on = enabled;
UNLOCK;
}
......@@ -443,21 +848,40 @@ void st3m_audio_input_thru_set_mute(bool mute) {
UNLOCK;
}
float st3m_audio_input_thru_set_volume_dB(float vol_dB) {
if (vol_dB > 0) vol_dB = 0;
void st3m_audio_headset_mic_set_allowed(bool allowed) {
LOCK;
state.input_thru_vol_int = (int32_t)(32768. * expf(vol_dB * NAT_LOG_DB));
state.input_thru_vol = vol_dB;
state.headset_mic_allowed = allowed;
UNLOCK;
}
void st3m_audio_onboard_mic_set_allowed(bool allowed) {
LOCK;
state.onboard_mic_allowed = allowed;
UNLOCK;
return vol_dB;
}
void st3m_audio_set_player_function(st3m_audio_player_function_t fun) {
void st3m_audio_line_in_set_allowed(bool allowed) {
LOCK;
state.function = fun;
state.line_in_allowed = allowed;
UNLOCK;
}
void st3m_audio_onboard_mic_to_speaker_set_allowed(bool allowed) {
LOCK;
state.onboard_mic_to_speaker_allowed = allowed;
UNLOCK;
}
float st3m_audio_input_thru_set_volume_dB(float vol_dB) {
if (vol_dB > 0) vol_dB = 0;
int16_t vol = 32768. * expf(vol_dB * NAT_LOG_DB);
LOCK;
state.input_thru_vol_int = vol;
state.input_thru_vol = vol_dB;
UNLOCK;
return vol_dB;
}
bool st3m_audio_headphones_are_connected(void) {
LOCK;
bool res = _headphones_connected();
......@@ -500,9 +924,11 @@ float st3m_audio_headphones_set_volume_dB(float vol_dB) {
LOCKED(float, _output_set_volume(&state.headphones, vol_dB));
}
void st3m_audio_headphones_detection_override(bool enable) {
void st3m_audio_headphones_detection_override(bool enable,
bool override_state) {
LOCK;
state.headphones_detection_override = enable;
state.headphones_detection_override_state = override_state;
_output_apply(&state.headphones);
_output_apply(&state.speaker);
UNLOCK;
......
......@@ -10,23 +10,53 @@ typedef enum {
// Headset microphone on left jack.
st3m_audio_input_source_headset_mic = 2,
// Onboard microphone (enabled red LED).
st3m_audio_input_source_onboard_mic = 3
st3m_audio_input_source_onboard_mic = 3,
// auto switching depending on availability
// line in preferred to headset mic preferred to onboard mic.
st3m_audio_input_source_auto = 4
} st3m_audio_input_source_t;
typedef void (*st3m_audio_player_function_t)(int16_t* tx, int16_t* rx,
uint16_t len);
/* The default audio task takes a function of prototype
* &st3m_audio_player_function_t, loops it and sets software volume/adds
* software thru. tx is the stereo zipped l/r output, rx is the stereo zipped
* input, each buffer the size of len.
/* Initializes the audio engine and passes sample rate as well as max buffer
* length. At this point those values are always 48000/128, but this might
* become variable in the future. However, we see flow3r primarily as a real
* time instrument, and longer buffers introduce latency; the current buffer
* length corresponds to 1.3ms latency which isn't much, but given the up to
* 10ms captouch latency on top we shouldn't be super careless here.
*/
void st3m_audio_set_player_function(st3m_audio_player_function_t fun);
/* Dummy for st3m_audio_set_player_function that just writes zeros to the
* output. Default state.
typedef void (*st3m_audio_engine_init_function_t)(uint32_t sample_rate,
uint16_t max_len);
/* Renders the output of the audio engine and returns whether or not it has
* overwritten tx. Always called for each buffer, no exceptions. This means you
* can keep track of time within the engine easily and use the audio player task
* to handle musical events (the current 1.3ms buffer rate is well paced for
* this), but it also puts the burden on you of exiting early if there's nothing
* to do.
*
* rx (input) and tx (output) are both stereo interlaced, i.e. the even indices
* represent the left channel, the odd indices represent the right channel. The
* length is the total length of the buffer so that each channel has len/2 data
* points. len is always even.
*
* The function must never modify rx. This is so that we can pass the same
* buffer to all the engines without having to memcpy by default, so if you need
* to modify rx please do your own memcpy of it.
*
* In a similar manner, tx is not cleaned up when calling the function, it
* carries random junk data that is not supposed to be read by the user. The
* return value indicates whether tx should be used or if tx should be ignored
* andit should be treated as if you had written all zeroes into it (without you
* actually doing so). If you choose to return true please make sure you have
* overwritten the entirety of tx with valid data.
*/
void st3m_audio_player_function_dummy(int16_t* rx, int16_t* tx, uint16_t len);
typedef bool (*st3m_audio_engine_render_function_t)(int16_t* rx, int16_t* tx,
uint16_t len);
typedef struct {
char* name; // used for UI, no longer than 14 characters
st3m_audio_engine_render_function_t render_fun;
st3m_audio_engine_init_function_t init_fun; // optional, else NULL
} st3m_audio_engine_t;
/* Initializes I2S bus, the audio task and required data structures.
* Expects an initialized I2C bus, will fail ungracefully otherwise (TODO).
......@@ -43,15 +73,22 @@ bool st3m_audio_headphones_are_connected(void);
*/
bool st3m_audio_headset_is_connected(void);
/* Returns true if the lin-in jack is connected to a cable. */
/* Returns true if the line-in jack is connected to a cable. */
bool st3m_audio_line_in_is_connected(void);
/* If a sleeve contact mic doesn't pull the detection pin low enough the
* codec's built in headphone detection might fail. Calling this function
* with 'enable = 1' overrides the detection and assumes there's headphones
* plugged in. Call with 'enable = 0' to revert to automatic detection.
/* Set to 'enable = 1' if the system should ignore jacksense and use
* headphones_detection_override_state to determine whether headphones are
* connected (true) or not (false).
*
* Use cases:
* - If a sleeve contact mic doesn't pull the detection pin low enough the
* codec's built in headphone detection might fail.
* - If the headset only has a mic connected but we wish to use the internal
* speaker anyway
*
* Call with 'enable = 0' to revert to automatic detection.
*/
void st3m_audio_headphones_detection_override(bool enable);
void st3m_audio_headphones_detection_override(bool enable, bool override_state);
/* Attempts to set target volume for the headphone output/onboard speakers
* respectively, clamps/rounds if necessary and returns the actual volume.
......@@ -151,20 +188,31 @@ void st3m_audio_line_in_set_hardware_thru(bool enable);
* Note: The onboard digital mic turns on an LED on the top board if it receives
* a clock signal which is considered a good proxy for its capability of reading
* data.
*
* TODO: check if sources are available
*/
void st3m_audio_input_set_source(st3m_audio_input_source_t source);
void st3m_audio_input_engines_set_source(st3m_audio_input_source_t source);
st3m_audio_input_source_t st3m_audio_input_engines_get_source(void);
st3m_audio_input_source_t st3m_audio_input_engines_get_target_source(void);
void st3m_audio_input_thru_set_source(st3m_audio_input_source_t source);
st3m_audio_input_source_t st3m_audio_input_thru_get_source(void);
st3m_audio_input_source_t st3m_audio_input_thru_get_target_source(void);
/* Returns the currently selected input source.
*/
st3m_audio_input_source_t st3m_audio_input_get_source(void);
/* Hardware preamp gain, 0dB-50dB. TODO: figure out if int/float inconsistency
* is a good thing here compared to all other _dB functions.
/* Gain of headset mic source
*/
uint8_t st3m_audio_headset_set_gain_dB(uint8_t gain_dB);
uint8_t st3m_audio_headset_get_gain_dB(void);
float st3m_audio_headset_mic_set_gain_dB(float gain_dB);
float st3m_audio_headset_mic_get_gain_dB(void);
/* Gain of onboard mic source
*/
float st3m_audio_onboard_mic_set_gain_dB(float gain_dB);
float st3m_audio_onboard_mic_get_gain_dB(void);
/* Gain of line in source
*/
float st3m_audio_line_in_set_gain_dB(float gain_dB);
float st3m_audio_line_in_get_gain_dB(void);
/* You can route whatever source is selected with st3m_audio_input_set_source to
* the audio output. Use these to control volume and mute.
......@@ -174,6 +222,28 @@ float st3m_audio_input_thru_get_volume_dB(void);
void st3m_audio_input_thru_set_mute(bool mute);
bool st3m_audio_input_thru_get_mute(void);
/* Activate a EQ preset when speakers are enabled, or don't :D
*/
void st3m_audio_speaker_set_eq_on(bool enable);
bool st3m_audio_speaker_get_eq_on(void);
void st3m_audio_headset_mic_set_allowed(bool allowed);
bool st3m_audio_headset_mic_get_allowed(void);
void st3m_audio_onboard_mic_set_allowed(bool allowed);
bool st3m_audio_onboard_mic_get_allowed(void);
void st3m_audio_line_in_set_allowed(bool allowed);
bool st3m_audio_line_in_get_allowed(void);
void st3m_audio_onboard_mic_to_speaker_set_allowed(bool allowed);
bool st3m_audio_onboard_mic_to_speaker_get_allowed(void);
bool st3m_audio_input_engines_get_source_avail(
st3m_audio_input_source_t source);
bool st3m_audio_input_thru_get_source_avail(st3m_audio_input_source_t source);
/*
HEADPHONE PORT POLICY
......
#include "st3m_captouch.h"
#include "esp_err.h"
#include "esp_log.h"
// super thin shim
#include "flow3r_bsp_captouch.h"
#include "sdkconfig.h"
void st3m_captouch_init() { flow3r_bsp_captouch_init(); }
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
bool st3m_captouch_calibrating() { return flow3r_bsp_captouch_calibrating(); }
#include <string.h>
static const char *TAG = "st3m-captouch";
static SemaphoreHandle_t _mu = NULL;
static st3m_captouch_state_t _state = {};
static bool _request_calibration = false;
static bool _calibrating = false;
static inline void _pad_feed(st3m_petal_pad_state_t *pad, uint16_t data,
bool top) {
ringbuffer_write(&pad->rb, data);
int32_t thres = top ? 8000 : 12000;
pad->pressed = data > thres;
if (pad->pressed) {
pad->pressure = data - thres;
} else {
pad->pressure = 0;
}
}
static inline void _petal_process(st3m_petal_state_t *petal, bool top) {
if (top) {
petal->pressed =
petal->base.pressed || petal->ccw.pressed || petal->cw.pressed;
petal->pressure =
(petal->base.pressure + petal->ccw.pressure + petal->cw.pressure) /
3;
int32_t left = ringbuffer_avg(&petal->ccw.rb);
int32_t right = ringbuffer_avg(&petal->cw.rb);
int32_t base = ringbuffer_avg(&petal->base.rb);
petal->pos_distance = (left + right) / 2 - base;
petal->pos_angle = right - left;
#if defined(CONFIG_FLOW3R_HW_GEN_P3)
petal->pos_distance = -petal->pos_distance;
#endif
} else {
petal->pressed = petal->base.pressed || petal->tip.pressed;
petal->pressure = (petal->base.pressure + petal->tip.pressure) / 2;
int32_t base = ringbuffer_avg(&petal->base.rb);
int32_t tip = ringbuffer_avg(&petal->tip.rb);
petal->pos_distance = tip - base;
petal->pos_angle = 0;
}
void st3m_captouch_calibration_request() {
flow3r_bsp_captouch_calibration_request();
}
static void _on_data(const flow3r_bsp_captouch_state_t *st) {
xSemaphoreTake(_mu, portMAX_DELAY);
for (size_t ix = 0; ix < 10; ix++) {
bool top = (ix % 2) == 0;
if (top) {
#if defined(CONFIG_FLOW3R_HW_GEN_P3)
// Hack for P3 badges, pretend tip is base.
_pad_feed(&_state.petals[ix].base, st->petals[ix].tip.raw, true);
#else
_pad_feed(&_state.petals[ix].base, st->petals[ix].base.raw, true);
#endif
_pad_feed(&_state.petals[ix].cw, st->petals[ix].cw.raw, true);
_pad_feed(&_state.petals[ix].ccw, st->petals[ix].ccw.raw, true);
_petal_process(&_state.petals[ix], true);
} else {
_pad_feed(&_state.petals[ix].base, st->petals[ix].base.raw, false);
_pad_feed(&_state.petals[ix].tip, st->petals[ix].tip.raw, false);
_petal_process(&_state.petals[ix], false);
}
}
if (_request_calibration) {
_request_calibration = false;
flow3r_bsp_captouch_calibrate();
}
_calibrating = flow3r_bsp_captouch_calibrating();
xSemaphoreGive(_mu);
void st3m_captouch_get(flow3r_bsp_captouch_data_t *dest) {
flow3r_bsp_captouch_get(dest);
}
void st3m_captouch_init(void) {
assert(_mu == NULL);
_mu = xSemaphoreCreateMutex();
assert(_mu != NULL);
esp_err_t ret = flow3r_bsp_captouch_init(_on_data);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Captouch init failed: %s", esp_err_to_name(ret));
}
}
bool st3m_captouch_calibrating(void) {
xSemaphoreTake(_mu, portMAX_DELAY);
bool res = _calibrating || _request_calibration;
xSemaphoreGive(_mu);
return res;
}
void st3m_captouch_request_calibration(void) {
xSemaphoreTake(_mu, portMAX_DELAY);
_request_calibration = true;
xSemaphoreGive(_mu);
}
void st3m_captouch_get_all(st3m_captouch_state_t *dest) {
xSemaphoreTake(_mu, portMAX_DELAY);
memcpy(dest, &_state, sizeof(_state));
xSemaphoreGive(_mu);
}
void st3m_captouch_get_petal(st3m_petal_state_t *dest, uint8_t petal_ix) {
if (petal_ix > 9) {
petal_ix = 9;
}
xSemaphoreTake(_mu, portMAX_DELAY);
memcpy(dest, &_state.petals[petal_ix], sizeof(_state.petals[petal_ix]));
xSemaphoreGive(_mu);
}
\ No newline at end of file
void st3m_captouch_refresh_events() { flow3r_bsp_captouch_refresh_events(); }
......@@ -37,81 +37,14 @@
// touch. Top petals have two degrees of freedom, bottom petals have a
// single degree of freedom (distance from center).
#include "st3m_ringbuffer.h"
// NOTE: keep the enum definitions below in-sync with flow3r_bsp_captouch.h, as
// they are converted by numerical value internally.
// One of the four possible touch points (pads) on a petal. Top petals have
// base/cw/ccw. Bottom petals have base/tip.
typedef enum {
// Pad away from centre of badge.
st3m_petal_pad_tip = 0,
// Pad going counter-clockwise around the badge.
st3m_petal_pad_ccw = 1,
// Pad going clockwise around the badge.
st3m_petal_pad_cw = 2,
// Pad going towards the centre of the badge.
st3m_petal_pad_base = 3,
} st3m_petal_pad_kind_t;
// Each petal can be either top or bottom (that is, on the bottom PCB or top
// PCB).
typedef enum {
// Petal on the top layer. Has base, cw, ccw pads.
st3m_petal_top = 0,
// petal on the bottom layer. Has base and tip fields.
st3m_petal_bottom = 1,
} st3m_petal_kind_t;
// State of capacitive touch for a petal's pad.
typedef struct {
// Raw data ringbuffer.
st3m_ringbuffer_t rb;
// Whether the pad is currently being touched. Calculated from ringbuffer
// data.
bool pressed;
// How strongly the pad is currently being pressed, in arbitrary units.
uint16_t pressure;
} st3m_petal_pad_state_t;
// State of capacitive touch for a petal.
typedef struct {
// Is this a top or bottom petal?
st3m_petal_kind_t kind;
// Valid if top or bottom.
st3m_petal_pad_state_t base;
// Valid if bottom.
st3m_petal_pad_state_t tip;
// Valid if top.
st3m_petal_pad_state_t cw;
// Valid if top.
st3m_petal_pad_state_t ccw;
// Whether the petal is currently being touched. Calculated from individual
// pad data.
bool pressed;
// How strongly the petal is currently being pressed, in arbitrary units.
uint16_t pressure;
// Touch position on petal, calculated from pad data.
//
// Arbitrary units around (0, 0).
// TODO(q3k): normalize and document.
float pos_distance;
float pos_angle;
} st3m_petal_state_t;
typedef struct {
// Petal 0 is a top petal next to the USB socket. Then, all other petals
// follow clockwise.
st3m_petal_state_t petals[10];
} st3m_captouch_state_t;
#include <stdbool.h>
#include "flow3r_bsp_captouch.h"
void st3m_captouch_init(void);
bool st3m_captouch_calibrating(void);
void st3m_captouch_request_calibration(void);
void st3m_captouch_get_all(st3m_captouch_state_t *dest);
void st3m_captouch_get_petal(st3m_petal_state_t *dest, uint8_t petal_ix);
\ No newline at end of file
void st3m_captouch_calibration_request(void);
void st3m_captouch_get(flow3r_bsp_captouch_data_t *dest);
void st3m_captouch_refresh_events();
......@@ -2,29 +2,39 @@
#include <math.h>
#define TAU 6.283185307179586
#define NTAU 0.15915494309189535
st3m_rgb_t st3m_hsv_to_rgb(st3m_hsv_t hsv) {
float r = 0, g = 0, b = 0;
hsv.v = hsv.v > 1 ? 1 : (hsv.v < 0 ? 0 : hsv.v);
hsv.s = hsv.s > 1 ? 1 : (hsv.s < 0 ? 0 : hsv.s);
if (hsv.h < 0 || hsv.h >= TAU) {
int remainder = hsv.h / TAU;
if (hsv.h < 0) remainder -= 1;
hsv.h -= ((float)remainder) * TAU;
}
if (hsv.s == 0) {
r = hsv.v;
g = hsv.v;
b = hsv.v;
} else {
int i;
int16_t i;
float f, p, q, t;
float hue = hsv.h * NTAU * 6;
if (hsv.h == 360)
hsv.h = 0;
else
hsv.h = hsv.h / 60;
i = (int)truncf(hsv.h);
f = hsv.h - i;
i = (int16_t)truncf(hue);
f = hue - i;
p = hsv.v * (1.0 - hsv.s);
q = hsv.v * (1.0 - (hsv.s * f));
t = hsv.v * (1.0 - (hsv.s * (1.0 - f)));
while (i < 0) {
i += 6 * (1 << 13);
}
i = i % 6;
switch (i) {
case 0:
r = hsv.v;
......@@ -65,9 +75,49 @@ st3m_rgb_t st3m_hsv_to_rgb(st3m_hsv_t hsv) {
}
st3m_rgb_t rgb = {
.r = r * 255,
.g = g * 255,
.b = b * 255,
.r = r,
.g = g,
.b = b,
};
return rgb;
}
st3m_hsv_t st3m_rgb_to_hsv(st3m_rgb_t rgb) {
st3m_hsv_t hsv;
float min;
float max;
rgb.r = rgb.r > 1 ? 1 : (rgb.r < 0 ? 0 : rgb.r);
rgb.g = rgb.g > 1 ? 1 : (rgb.g < 0 ? 0 : rgb.g);
rgb.b = rgb.b > 1 ? 1 : (rgb.b < 0 ? 0 : rgb.b);
min = rgb.r < rgb.g ? (rgb.r < rgb.b ? rgb.r : rgb.b)
: (rgb.g < rgb.b ? rgb.g : rgb.b);
max = rgb.r > rgb.g ? (rgb.r > rgb.b ? rgb.r : rgb.b)
: (rgb.g > rgb.b ? rgb.g : rgb.b);
hsv.v = max;
if (hsv.v == 0.) {
hsv.h = 0.;
hsv.s = 0.;
return hsv;
}
hsv.s = (max - min) / hsv.v;
if (hsv.s == 0) {
hsv.h = 0;
return hsv;
}
if ((max == rgb.r) && ((max != rgb.b) || (max == rgb.g))) {
hsv.h = (TAU / 6) * (rgb.g - rgb.b) / (max - min);
} else if (max == rgb.g) {
hsv.h = (TAU / 3) + (TAU / 6) * (rgb.b - rgb.r) / (max - min);
} else {
hsv.h = (TAU * 2 / 3) + (TAU / 6) * (rgb.r - rgb.g) / (max - min);
}
if (hsv.h < 0) hsv.h += TAU;
return hsv;
}
......@@ -3,11 +3,11 @@
#include <stdint.h>
typedef struct {
// From 0 to 255.
// From 0.0 to 1.0.
uint8_t r;
uint8_t g;
uint8_t b;
float r;
float g;
float b;
} st3m_rgb_t;
typedef struct {
......@@ -20,3 +20,6 @@ typedef struct {
// Convert an HSV colour to an RGB colour.
st3m_rgb_t st3m_hsv_to_rgb(st3m_hsv_t hsv);
// Convert an RGB colour to an HSV colour.
st3m_hsv_t st3m_rgb_to_hsv(st3m_rgb_t rgb);
......@@ -4,10 +4,12 @@
#include <sys/fcntl.h>
#include "driver/uart.h"
#include "driver/usb_serial_jtag.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_vfs.h"
#include "esp_vfs_dev.h"
#include "esp_vfs_usb_serial_jtag.h"
#include "freertos/FreeRTOS.h"
#include "freertos/ringbuf.h"
#include "freertos/semphr.h"
......@@ -72,6 +74,18 @@ int st3m_uart0_debug(const char *fmt, ...) {
return strlen(buf);
}
#if CONFIG_DEBUG_GDB_ENABLED
void _usb_read_task(void *data) {
uint8_t buf[32];
while (1) {
int n = usb_serial_jtag_read_bytes(buf, sizeof(buf), portMAX_DELAY);
if (n > 0) {
st3m_console_cdc_on_rx(buf, n);
}
}
}
#endif
void st3m_console_init(void) {
assert(_state.mu == NULL);
_state.mu = xSemaphoreCreateMutex();
......@@ -105,13 +119,27 @@ void st3m_console_init(void) {
abort();
}
#if CONFIG_DEBUG_GDB_ENABLED
esp_vfs_dev_usb_serial_jtag_set_tx_line_endings(ESP_LINE_ENDINGS_LF);
usb_serial_jtag_driver_config_t usb_serial_jtag_config =
USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT();
/* Install USB-SERIAL-JTAG driver for interrupt-driven reads and writes */
ESP_ERROR_CHECK(usb_serial_jtag_driver_install(&usb_serial_jtag_config));
esp_vfs_usb_serial_jtag_use_driver();
xTaskCreate(&_usb_read_task, "usbserial", 2048, NULL,
configMAX_PRIORITIES - 3, NULL);
#endif
// Switch over to new console. From now on, all stdio is going through this
// code.
ESP_LOGI(TAG, "switching to st3m console...");
fflush(stdout);
freopen("/console/stdin", "r", stdin);
#if !CONFIG_DEBUG_GDB_ENABLED
freopen("/console/stdout", "w", stdout);
freopen("/console/stderr", "w", stderr);
#endif
ESP_LOGI(TAG, "st3m console running");
}
......@@ -145,16 +173,6 @@ void st3m_gfx_splash(const char *c);
// Called by st3m_usb_cdc when it has the opportunity to send some data to the
// host.
size_t st3m_console_cdc_on_txpoll(uint8_t *buffer, size_t bufsize) {
// I have no idea why this is needed, but it is. Otherwise a large backlog
// of data cuases the IN endpoint to get stuck.
//
// I've spend three days debugging this.
//
// No, I'm not fine. Thanks for asking, though. I appreciate it.
if (bufsize > 0) {
bufsize -= 1;
}
int64_t now = esp_timer_get_time();
xSemaphoreTake(_state.mu, portMAX_DELAY);
......