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
  • 89-apps-should-be-able-to-specify-if-they-want-wifi-to-be-disabled-when-entering-them
  • 9Rmain
  • allow-reloading-sunmenu
  • always-have-a-wifi-instance
  • anon/gpndemo
  • anon/update-sim
  • anon/webflasher
  • app_text_viewer
  • audio_input
  • audio_io
  • blm_dev_chan
  • ch3/bl00mbox_docs
  • ci-1690580595
  • dev_p4
  • dev_p4-iggy
  • dev_p4-iggy-rebased
  • dx/dldldld
  • dx/fb-save-restore
  • dx/hint-hint
  • dx/jacksense-headset-mic-only
  • events
  • fil3s-limit-filesize
  • fil3s-media
  • fpletz/flake
  • gr33nhouse-improvements
  • history-rewrite
  • icon-flower
  • iggy/stemming
  • iggy/stemming_merge
  • led_fix_fix
  • main
  • main+schneider
  • media_has_video_has_audio
  • micropython_api
  • mixer2
  • moon2_demo_temp
  • moon2_migrate_apps
  • more-accurate-battery
  • pippin/ctx_sprite_sheet_support
  • pippin/display-python-errors-on-display
  • pippin/make_empty_drawlists_skip_render_and_blit
  • pippin/more-accurate-battery
  • pippin/tcp_redirect_hack
  • pippin/tune_ctx_config_update_from_upstream
  • pippin/uhm_flash_access_bust
  • pressable_bugfix
  • py_only_update_fps_overlay_when_changing
  • q3k/doom-poc
  • q3k/render-to-texture
  • rahix/flow3rseeds
  • raw_captouch_new
  • raw_captouch_old
  • release/1.0.0
  • release/1.1.0
  • release/1.1.1
  • release/1.2.0
  • release/1.3.0
  • release/1.4.0
  • restore_blit
  • return_of_melodic_demo
  • rev4_micropython
  • schneider/application-remove-name
  • schneider/bhi581
  • schneider/factory_test
  • schneider/recovery
  • scope_hack
  • sdkconfig-spiram-tinyusb
  • sec/auto-nick
  • sec/blinky
  • sector_size_512
  • shoegaze-fps
  • smaller_gradient_lut
  • store_delta_ms_and_ins_as_class_members
  • task_cleanup
  • uctx-wip
  • w1f1-in-sim
  • widgets_draw
  • wifi-json-error-handling
  • wip-docs
  • wip-tinyusb
  • v1.0.0
  • v1.0.0+rc1
  • v1.0.0+rc2
  • v1.0.0+rc3
  • v1.0.0+rc4
  • v1.0.0+rc5
  • v1.0.0+rc6
  • v1.1.0
  • v1.1.0+rc1
  • v1.1.1
  • v1.2.0
  • v1.2.0+rc1
  • v1.3.0
  • v1.4.0
94 results

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
  • 9Rmain
  • anon/gpndemo
  • anon/update-sim
  • anon/webflasher
  • audio_input
  • audio_io
  • bl00mbox
  • bl00mbox_old
  • captouch-threshold
  • ch3/bl00mbox_docs
  • ci-1690580595
  • compressor
  • dev_p4
  • dev_p4-iggy
  • dev_p4-iggy-rebased
  • dos
  • dos-main-patch-50543
  • events
  • fm_fix
  • fm_fix2
  • fpletz/flake
  • history-rewrite
  • icon-flower
  • iggy/stemming
  • iggy/stemming_merge
  • json-error
  • main
  • main+schneider
  • media-buf
  • micropython_api
  • moon2_applications
  • moon2_demo_temp
  • moon2_gay_drums
  • passthrough
  • phhw
  • pippin/display-python-errors-on-display
  • pippin/make_empty_drawlists_skip_render_and_blit
  • pippin/media_framework
  • pippin/uhm_flash_access_bust
  • pressable_bugfix
  • q3k/doom-poc
  • rahix/big-flow3r
  • rahix/flow3rseeds
  • raw_captouch_new
  • raw_captouch_old
  • release/1.0.0
  • release/1.1.0
  • release/1.1.1
  • rev4_micropython
  • schneider/application-remove-name
  • schneider/bhi581
  • schneider/factory_test
  • schneider/recovery
  • scope
  • scope_hack
  • sdkconfig-spiram-tinyusb
  • sec/auto-nick
  • sec/blinky
  • simtest
  • slewtest
  • t
  • test
  • test2
  • uctx-wip
  • view-think
  • vm-pending
  • vsync
  • wave
  • wip-docs
  • wip-tinyusb
  • v1.0.0
  • v1.0.0+rc1
  • v1.0.0+rc2
  • v1.0.0+rc3
  • v1.0.0+rc4
  • v1.0.0+rc5
  • v1.0.0+rc6
  • v1.1.0
  • v1.1.0+rc1
  • v1.1.1
  • v1.2.0
  • v1.2.0+rc1
  • v1.3.0
83 results
Show changes
Showing
with 1711 additions and 652 deletions
......@@ -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
......@@ -13,105 +13,10 @@ 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_git_based_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])
return subprocess.check_output(
["git", "describe", "--tags"]
).decode().strip()
fmt = None
......
#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");
}
......
......@@ -23,21 +23,11 @@ int64_t st3m_counter_rate_average(st3m_counter_rate_t *rate) {
if (start < 0) start = 9;
if (end < 0) end = 9;
int i = start;
int64_t sum = 0;
int64_t count = 0;
while (i != end) {
int j = (i + 1) % 10;
int64_t diff = rate->sample_us[j] - rate->sample_us[i];
sum += diff;
count++;
i = j;
}
int32_t count = (end - start + 10) % 10;
if (count == 0) {
return INT64_MAX;
}
int32_t sum = rate->sample_us[end] - rate->sample_us[start];
return sum / count;
}
......@@ -62,7 +52,7 @@ void st3m_counter_timer_sample(st3m_counter_timer_t *timer, int64_t val) {
}
}
int64_t st3m_counter_timer_average(st3m_counter_timer_t *timer) {
int32_t st3m_counter_timer_average(st3m_counter_timer_t *timer) {
int start = timer->read_offs - 1;
int end = timer->write_offs - 1;
if (start < 0) start = 9;
......
......@@ -25,5 +25,5 @@ typedef struct {
void st3m_counter_timer_init(st3m_counter_timer_t *rate);
void st3m_counter_timer_sample(st3m_counter_timer_t *timer, int64_t val);
int64_t st3m_counter_timer_average(st3m_counter_timer_t *rate);
int32_t st3m_counter_timer_average(st3m_counter_timer_t *rate);
uint8_t st3m_counter_timer_report(st3m_counter_timer_t *rate, int seconds);
......@@ -263,6 +263,10 @@ static esp_err_t _st3m_fs_sd_probe_unlocked(st3m_fs_sd_props_t *props) {
esp_err_to_name(ret));
return ret;
}
// useful for collecting info about SDcards that are problematic, ask users
// about this to potentially add to blocklist. Also in about menu.
ESP_LOGI(TAG, "SD CID: mfg_id: %i, oem_id: %i, date: %i", _card.cid.mfg_id,
_card.cid.oem_id, _card.cid.date);
_status = st3m_fs_sd_status_probed;
......@@ -309,6 +313,22 @@ esp_err_t st3m_fs_sd_probe(st3m_fs_sd_props_t *props) {
return ret;
}
int32_t st3m_fs_sd_oem_id() {
if (_status == st3m_fs_sd_status_probed ||
_status == st3m_fs_sd_status_mounted) {
return _card.cid.oem_id;
}
return -1;
}
int32_t st3m_fs_sd_mfg_id() {
if (_status == st3m_fs_sd_status_probed ||
_status == st3m_fs_sd_status_mounted) {
return _card.cid.mfg_id;
}
return -1;
}
int32_t st3m_fs_sd_read10(uint8_t lun, uint32_t lba, uint32_t offset,
void *buffer, uint32_t bufsize) {
if (offset != 0) {
......
......@@ -42,6 +42,12 @@ esp_err_t st3m_fs_sd_unmount(void);
// No-op if card is already ejected.
esp_err_t st3m_fs_sd_eject(void);
// for identifying sdcards in python land to warn about known problematic
// SDcards
int32_t st3m_fs_sd_oem_id();
int32_t st3m_fs_sd_mfg_id();
// SCSI Read (10) and Write (10) compatible operations. Offset must be 0. If an
// error occurs during the read/write operation, the card will be checked for
// errors, and might get ejected if deemed uanvailable/broken.
......
......@@ -8,6 +8,7 @@
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
// clang-format off
#include "ctx_config.h"
......@@ -18,39 +19,201 @@
#include "st3m_counter.h"
#include "st3m_version.h"
static const char *TAG = "st3m-gfx";
#define ST3M_GFX_BLIT_TASK 1
#if CTX_ST3M_FB_INTERNAL_RAM
#undef ST3M_GFX_BLIT_TASK
#define ST3M_GFX_BLIT_TASK 0
#endif
#define ST3M_GFX_DEFAULT_MODE (16 | st3m_gfx_osd)
static st3m_gfx_mode default_mode = ST3M_GFX_DEFAULT_MODE;
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
EXT_RAM_BSS_ATTR static uint8_t
st3m_fb2[FLOW3R_BSP_DISPLAY_WIDTH * FLOW3R_BSP_DISPLAY_HEIGHT * 4];
#endif
// Framebuffer descriptors, containing framebuffer and ctx for each of the
// framebuffers.
#if CTX_ST3M_FB_INTERNAL_RAM
// without EXT_RAM_BSS_ATTR is removed 8bit and 16bit modes go
// faster but it is not possible to enable wifi
static uint16_t st3m_fb[FLOW3R_BSP_DISPLAY_WIDTH * FLOW3R_BSP_DISPLAY_HEIGHT];
uint8_t st3m_pal[256 * 3];
#else
EXT_RAM_BSS_ATTR uint8_t st3m_pal[256 * 3];
EXT_RAM_BSS_ATTR static uint8_t
st3m_fb[FLOW3R_BSP_DISPLAY_WIDTH * FLOW3R_BSP_DISPLAY_HEIGHT * 4];
#endif
#if ST3M_GFX_BLIT_TASK
EXT_RAM_BSS_ATTR static uint8_t
st3m_fb_copy[FLOW3R_BSP_DISPLAY_WIDTH * FLOW3R_BSP_DISPLAY_HEIGHT * 2];
#endif
EXT_RAM_BSS_ATTR static uint8_t scratch[40 * 1024];
// Get a free drawlist ctx to draw into.
//
// These live in SPIRAM, as we don't have enough space in SRAM/IRAM.
EXT_RAM_BSS_ATTR static st3m_framebuffer_desc_t
framebuffer_descs[ST3M_GFX_NBUFFERS];
// ticks_to_wait can be used to limit the time to wait for a free ctx
// descriptor, or portDELAY_MAX can be specified to wait forever. If the timeout
// expires, NULL will be returned.
static Ctx *st3m_gfx_drawctx_free_get(TickType_t ticks_to_wait);
// Submit a filled ctx descriptor to the rasterization pipeline.
static void st3m_gfx_pipe_put(void);
static const char *TAG = "st3m-gfx";
#define N_DRAWLISTS 3
// we keep the OSD buffer the same size as the main framebuffer,
// allowing us to do different combos of which buffer is osd and not
static st3m_gfx_mode _st3m_gfx_mode = st3m_gfx_default + 1;
static Ctx *ctx = NULL;
// each frame buffer has an associated rasterizer context
static Ctx *fb_ctx = NULL;
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
static Ctx *osd_ctx = NULL;
#define ST3M_OSD_LOCK_TIMEOUT 500
SemaphoreHandle_t st3m_osd_lock;
#endif
SemaphoreHandle_t st3m_fb_lock;
SemaphoreHandle_t st3m_fb_copy_lock;
static st3m_ctx_desc_t dctx_descs[ST3M_GFX_NCTX];
typedef struct {
Ctx *user_ctx;
// Queue of free framebuffer descriptors, written into by crtc once rendered,
// read from by rasterizer when new frame starts.
static QueueHandle_t framebuffer_freeq = NULL;
// Queue of framebuffer descriptors to blit out.
static QueueHandle_t framebuffer_blitq = NULL;
int osd_y0;
int osd_x0;
int osd_y1;
int osd_x1;
static st3m_counter_rate_t blit_rate;
static st3m_counter_timer_t blit_read_time;
static st3m_counter_timer_t blit_work_time;
static st3m_counter_timer_t blit_write_time;
int blit_x; // upper left pixel in framebuffer coordinates
int blit_y; //
static QueueHandle_t dctx_freeq = NULL;
static QueueHandle_t dctx_rastq = NULL;
st3m_gfx_mode mode;
uint8_t *blit_src;
} st3m_gfx_drawlist;
static st3m_gfx_drawlist drawlists[N_DRAWLISTS];
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
static int _st3m_osd_y1 =
0; // the corner coordinates of the part of osd that needs to
static int _st3m_osd_x1 = 0; // be composited - more might be composited
static int _st3m_osd_y0 = 0;
static int _st3m_osd_x0 = 0;
#endif
static float smoothed_fps = 0.0f;
static QueueHandle_t user_ctx_freeq = NULL;
static QueueHandle_t user_ctx_rastq = NULL;
static QueueHandle_t user_ctx_blitq = NULL;
static st3m_counter_rate_t rast_rate;
static st3m_counter_timer_t rast_read_fb_time;
static st3m_counter_timer_t rast_read_dctx_time;
static st3m_counter_timer_t rast_work_time;
static st3m_counter_timer_t rast_write_time;
static TaskHandle_t graphics_rast_task;
#if ST3M_GFX_BLIT_TASK
static TaskHandle_t graphics_blit_task;
#endif
static int _st3m_gfx_low_latency = 0;
static int st3m_gfx_fb_width = 0;
static int st3m_gfx_fb_height = 0;
static int st3m_gfx_blit_x = 0;
static int st3m_gfx_blit_y = 0;
static int st3m_gfx_geom_dirty = 0;
static inline int st3m_gfx_scale(st3m_gfx_mode mode) {
switch ((int)(mode & st3m_gfx_4x)) {
case st3m_gfx_4x:
return 4;
case st3m_gfx_3x:
return 3;
case st3m_gfx_2x:
return 2;
}
return 1;
}
///////////////////////////////////////////////////////
// get the bits per pixel for a given mode
static inline int _st3m_gfx_bpp(st3m_gfx_mode mode) {
st3m_gfx_mode set_mode = _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
if (mode == st3m_gfx_default) {
mode = set_mode;
} else if (mode == st3m_gfx_osd) {
if ((st3m_gfx_bpp(set_mode) == 16) || (st3m_gfx_bpp(set_mode) == 8))
return 32;
else
return 16;
}
int bpp = (mode & 63);
if (bpp >= 2 && bpp < 4) bpp = 2;
if (bpp >= 4 && bpp < 8) bpp = 4;
if (bpp >= 8 && bpp < 16) bpp = 8;
return bpp;
}
int st3m_gfx_bpp(st3m_gfx_mode mode) { return _st3m_gfx_bpp(mode); }
static Ctx *st3m_gfx_ctx_int(st3m_gfx_mode mode) {
st3m_gfx_mode set_mode = _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
if (mode == st3m_gfx_osd) {
if ((_st3m_gfx_bpp(set_mode) == 16) || (st3m_gfx_bpp(set_mode) == 8))
return osd_ctx;
return osd_ctx;
}
#endif
Ctx *ctx = st3m_gfx_drawctx_free_get(1000);
static TaskHandle_t crtc_task;
static TaskHandle_t rast_task;
if (set_mode & st3m_gfx_direct_ctx) {
if (set_mode & st3m_gfx_smart_redraw) return ctx;
return fb_ctx;
}
if (!ctx) return NULL;
return ctx;
}
static void st3m_gfx_viewport_transform(Ctx *ctx, int reset) {
int scale = st3m_gfx_scale(_st3m_gfx_mode ? _st3m_gfx_mode : default_mode);
int32_t offset_x = FLOW3R_BSP_DISPLAY_WIDTH / 2 / scale;
int32_t offset_y = FLOW3R_BSP_DISPLAY_HEIGHT / 2 / scale;
if (reset)
ctx_identity(
ctx); // this might break/need revisiting with tiled rendering
ctx_apply_transform(ctx, 1.0 / scale, 0, offset_x, 0, 1.0 / scale, offset_y,
0, 0, 1);
}
static void st3m_gfx_start_frame(Ctx *ctx) {
int scale = st3m_gfx_scale(_st3m_gfx_mode);
if (scale > 1) {
ctx_rectangle(ctx, -120, -120, 240, 240);
ctx_clip(ctx);
}
}
Ctx *st3m_gfx_ctx(st3m_gfx_mode mode) {
Ctx *ctx = st3m_gfx_ctx_int(mode);
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
if (mode == st3m_gfx_osd) {
xSemaphoreTake(st3m_osd_lock,
ST3M_OSD_LOCK_TIMEOUT / portTICK_PERIOD_MS);
}
#endif
ctx_save(ctx);
if (mode != st3m_gfx_osd) st3m_gfx_viewport_transform(ctx, 0);
st3m_gfx_start_frame(ctx);
return ctx;
}
// Attempt to receive from a queue forever, but log an error if it takes longer
// than two seconds to get something.
......@@ -68,114 +231,459 @@ static void xQueueReceiveNotifyStarved(QueueHandle_t q, void *dst,
}
}
static void st3m_gfx_crtc_task(void *_arg) {
(void)_arg;
float st3m_gfx_fps(void) { return smoothed_fps; }
while (true) {
int descno;
void st3m_gfx_set_palette(uint8_t *pal_in, int count) {
if (count > 256) count = 256;
if (count < 0) count = 0;
for (int i = 0; i < count * 3; i++) st3m_pal[i] = pal_in[i];
}
int64_t start = esp_timer_get_time();
xQueueReceiveNotifyStarved(framebuffer_blitq, &descno,
"crtc task starved!");
int64_t end = esp_timer_get_time();
st3m_counter_timer_sample(&blit_read_time, end - start);
void st3m_gfx_set_default_mode(st3m_gfx_mode mode) {
if ((mode & (1 | 2 | 4 | 8 | 16 | 32)) == mode) {
default_mode &= ~(1 | 2 | 4 | 8 | 16 | 32);
default_mode |= mode;
} else if (mode == st3m_gfx_2x) {
default_mode &= ~st3m_gfx_4x;
default_mode |= st3m_gfx_2x;
} else if (mode == st3m_gfx_3x) {
default_mode &= ~st3m_gfx_4x;
default_mode |= st3m_gfx_3x;
} else if (mode == st3m_gfx_4x) {
default_mode &= ~st3m_gfx_4x;
default_mode |= st3m_gfx_4x;
} else if (mode == st3m_gfx_osd) {
default_mode |= st3m_gfx_osd;
} else if (mode == st3m_gfx_low_latency) {
default_mode |= st3m_gfx_low_latency;
} else if (mode == st3m_gfx_lock) {
default_mode |= st3m_gfx_lock;
} else if (mode == st3m_gfx_direct_ctx) {
default_mode |= st3m_gfx_direct_ctx;
} else
default_mode = mode;
if (default_mode & st3m_gfx_smart_redraw) {
default_mode &= ~63;
default_mode |= 16;
}
start = esp_timer_get_time();
flow3r_bsp_display_send_fb(framebuffer_descs[descno].buffer);
end = esp_timer_get_time();
st3m_counter_timer_sample(&blit_work_time, end - start);
if (default_mode & st3m_gfx_lock) {
default_mode &= ~st3m_gfx_lock;
_st3m_gfx_mode = default_mode + 1;
st3m_gfx_set_mode(st3m_gfx_default);
default_mode |= st3m_gfx_lock;
} else {
_st3m_gfx_mode = default_mode + 1;
st3m_gfx_set_mode(st3m_gfx_default);
}
}
start = esp_timer_get_time();
xQueueSend(framebuffer_freeq, &descno, portMAX_DELAY);
end = esp_timer_get_time();
st3m_counter_timer_sample(&blit_write_time, end - start);
static void st3m_gfx_init_palette(st3m_gfx_mode mode) {
switch (mode & 0xf) {
case 1:
for (int i = 0; i < 2; i++) {
st3m_pal[i * 3 + 0] = i * 255;
st3m_pal[i * 3 + 1] = i * 255;
st3m_pal[i * 3 + 2] = i * 255;
}
break;
case 2:
for (int i = 0; i < 4; i++) {
st3m_pal[i * 3 + 0] = (i * 255) / 3;
st3m_pal[i * 3 + 1] = (i * 255) / 3;
st3m_pal[i * 3 + 2] = (i * 255) / 3;
}
break;
case 4: {
#if 0
// ega palette
int idx = 0;
for (int i = 0; i < 2; i++)
for (int r = 0; r < 2; r++)
for (int g = 0; g < 2; g++)
for (int b = 0; b < 2; b++) {
st3m_pal[idx++] = (r * 127) * (i * 2);
st3m_pal[idx++] = (g * 127) * (i * 2);
st3m_pal[idx++] = (b * 127) * (i * 2);
}
#else
// night-mode
for (int i = 0; i < 16; i++) {
st3m_pal[i * 3 + 0] = (i * 255) / 15;
st3m_pal[i * 3 + 1] = ((i * 255) / 15) / 3;
st3m_pal[i * 3 + 2] = ((i * 255) / 15) / 5;
}
break;
#endif
} break;
case 8: // grayscale
for (int i = 0; i < 256; i++) {
st3m_pal[i * 3 + 0] = i;
st3m_pal[i * 3 + 1] = i;
st3m_pal[i * 3 + 2] = i;
}
break;
case st3m_gfx_rgb332:
for (int i = 0; i < 256; i++) {
st3m_pal[i * 3 + 0] = (((i >> 5) & 7) * 255) / 7;
st3m_pal[i * 3 + 1] = (((i >> 2) & 7) * 255) / 7;
st3m_pal[i * 3 + 2] =
((((i & 3) << 1) | ((i >> 2) & 1)) * 255) / 7;
}
break;
case st3m_gfx_sepia:
for (int i = 0; i < 256; i++) {
st3m_pal[i * 3 + 0] = i;
st3m_pal[i * 3 + 1] = (i / 255.0) * (i / 255.0) * 255;
st3m_pal[i * 3 + 2] =
(i / 255.0) * (i / 255.0) * (i / 255.0) * 255;
}
break;
case st3m_gfx_cool:
for (int i = 0; i < 256; i++) {
st3m_pal[i * 3 + 0] =
(i / 255.0) * (i / 255.0) * (i / 255.0) * 255;
st3m_pal[i * 3 + 1] = (i / 255.0) * (i / 255.0) * 255;
st3m_pal[i * 3 + 2] = i;
}
break;
}
}
st3m_gfx_mode st3m_gfx_set_mode(st3m_gfx_mode mode) {
if ((mode == _st3m_gfx_mode) || (0 != (default_mode & st3m_gfx_lock))) {
return (mode ? mode : default_mode);
}
if (mode == st3m_gfx_default)
mode = default_mode;
else if (mode == st3m_gfx_low_latency)
mode = default_mode | st3m_gfx_low_latency;
else if (mode == st3m_gfx_osd)
mode = default_mode | st3m_gfx_osd;
_st3m_gfx_mode = (mode == default_mode) ? st3m_gfx_default : mode;
if (((mode & st3m_gfx_low_latency) != 0) ||
((mode & st3m_gfx_direct_ctx) != 0))
_st3m_gfx_low_latency = (N_DRAWLISTS - 1);
else
_st3m_gfx_low_latency = 0;
st3m_counter_rate_sample(&blit_rate);
st3m_gfx_fbconfig(240, 240, 0, 0);
if (st3m_counter_rate_report(&blit_rate, 1)) {
float rate = 1000000.0 / st3m_counter_rate_average(&blit_rate);
float read = st3m_counter_timer_average(&blit_read_time) / 1000.0;
float work = st3m_counter_timer_average(&blit_work_time) / 1000.0;
float write = st3m_counter_timer_average(&blit_write_time) / 1000.0;
// Mark variables as used even if debug is disabled.
(void)rate;
(void)read;
(void)work;
(void)write;
ESP_LOGD(
TAG,
"blitting: %.3f/sec, read %.3fms, work %.3fms, write %.3fms",
(double)rate, (double)read, (double)work, (double)write);
return mode;
}
st3m_gfx_mode st3m_gfx_get_mode(void) {
return _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
}
uint8_t *st3m_gfx_fb(st3m_gfx_mode mode, int *width, int *height, int *stride) {
st3m_gfx_mode set_mode = _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
int bpp = _st3m_gfx_bpp(set_mode);
if (mode == st3m_gfx_palette) {
if (stride) *stride = 3;
if (width) *width = 1;
if (height) *height = 256;
return st3m_pal;
} else if (mode == st3m_gfx_default) {
if (stride) *stride = st3m_gfx_fb_width * bpp / 8;
if (width) *width = FLOW3R_BSP_DISPLAY_WIDTH;
if (height) *height = FLOW3R_BSP_DISPLAY_HEIGHT;
return ((uint8_t *)st3m_fb);
}
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
else if (mode == st3m_gfx_osd) {
if (stride) *stride = FLOW3R_BSP_DISPLAY_WIDTH * bpp / 8;
if (width) *width = FLOW3R_BSP_DISPLAY_WIDTH;
if (height) *height = FLOW3R_BSP_DISPLAY_HEIGHT;
return st3m_fb2;
}
int scale = st3m_gfx_scale(set_mode);
if (stride) *stride = FLOW3R_BSP_DISPLAY_WIDTH * bpp / 8;
if (width) *width = FLOW3R_BSP_DISPLAY_WIDTH / scale;
if (height) *height = FLOW3R_BSP_DISPLAY_HEIGHT / scale;
#endif
return (uint8_t *)st3m_fb;
}
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
static void *osd_fb = st3m_fb2;
#endif
static void st3m_gfx_blit(st3m_gfx_drawlist *drawlist) {
st3m_gfx_mode set_mode = drawlist->mode;
uint8_t *blit_src = drawlist->blit_src;
int bits = _st3m_gfx_bpp(set_mode);
static st3m_gfx_mode prev_mode;
if (set_mode != prev_mode) {
st3m_gfx_init_palette(set_mode);
}
xSemaphoreTake(st3m_fb_copy_lock, portMAX_DELAY);
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
int scale = st3m_gfx_scale(set_mode);
int osd_x0 = drawlist->osd_x0, osd_x1 = drawlist->osd_x1,
osd_y0 = drawlist->osd_y0, osd_y1 = drawlist->osd_y1;
if ((scale > 1) || ((set_mode & st3m_gfx_osd) && (osd_y0 != osd_y1))) {
if (((set_mode & st3m_gfx_osd) && (osd_y0 != osd_y1))) {
if ((set_mode & 0xf) == st3m_gfx_rgb332) bits = 9;
xSemaphoreTake(st3m_osd_lock,
ST3M_OSD_LOCK_TIMEOUT / portTICK_PERIOD_MS);
flow3r_bsp_display_send_fb_osd(blit_src, bits, scale, osd_fb,
osd_x0, osd_y0, osd_x1, osd_y1);
xSemaphoreGive(st3m_osd_lock);
} else {
if ((set_mode & 0xf) == st3m_gfx_rgb332) bits = 9;
flow3r_bsp_display_send_fb_osd(blit_src, bits, scale, NULL, 0, 0, 0,
0);
}
} else
#endif
{
if ((set_mode & 0xf) == st3m_gfx_rgb332) bits = 9;
flow3r_bsp_display_send_fb(blit_src, bits);
}
xSemaphoreGive(st3m_fb_copy_lock);
prev_mode = set_mode;
}
#if ST3M_GFX_BLIT_TASK
static void st3m_gfx_blit_task(void *_arg) {
while (true) {
int desc_no = 0;
xQueueReceiveNotifyStarved(user_ctx_blitq, &desc_no,
"blit task starved (user_ctx)!");
st3m_gfx_drawlist *drawlist = &drawlists[desc_no];
st3m_gfx_blit(drawlist);
xQueueSend(user_ctx_freeq, &desc_no, portMAX_DELAY);
}
}
#endif
static void st3m_gfx_rast_task(void *_arg) {
(void)_arg;
st3m_gfx_set_mode(st3m_gfx_default);
int bits = 0;
st3m_gfx_mode prev_set_mode = ST3M_GFX_DEFAULT_MODE - 1;
#if ST3M_GFX_BLIT_TASK
int direct_blit = 0;
#endif
while (true) {
int fb_descno, dctx_descno;
int64_t start = esp_timer_get_time();
xQueueReceiveNotifyStarved(framebuffer_freeq, &fb_descno,
"rast task starved (freeq)!");
st3m_framebuffer_desc_t *fb = &framebuffer_descs[fb_descno];
int64_t end = esp_timer_get_time();
st3m_counter_timer_sample(&rast_read_fb_time, end - start);
start = esp_timer_get_time();
xQueueReceiveNotifyStarved(dctx_rastq, &dctx_descno,
"rast task starved (dctx)!");
st3m_ctx_desc_t *draw = &dctx_descs[dctx_descno];
end = esp_timer_get_time();
st3m_counter_timer_sample(&rast_read_dctx_time, end - start);
ctx_set_textureclock(framebuffer_descs[0].ctx,
ctx_textureclock(framebuffer_descs[0].ctx) + 1);
// Render drawctx into fbctx.
start = esp_timer_get_time();
ctx_render_ctx(draw->ctx, fb->ctx);
ctx_drawlist_clear(draw->ctx);
end = esp_timer_get_time();
st3m_counter_timer_sample(&rast_work_time, end - start);
start = esp_timer_get_time();
xQueueSend(dctx_freeq, &dctx_descno, portMAX_DELAY);
xQueueSend(framebuffer_blitq, &fb_descno, portMAX_DELAY);
end = esp_timer_get_time();
st3m_counter_timer_sample(&rast_write_time, end - start);
int desc_no = 0;
int tc = ctx_textureclock(fb_ctx) + 1;
xQueueReceiveNotifyStarved(user_ctx_rastq, &desc_no,
"rast task starved (user_ctx)!");
st3m_gfx_drawlist *drawlist = &drawlists[desc_no];
st3m_gfx_mode set_mode = drawlist->mode;
xSemaphoreTake(st3m_fb_lock, portMAX_DELAY);
ctx_set_textureclock(fb_ctx, tc);
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
ctx_set_textureclock(osd_ctx, tc);
ctx_set_textureclock(ctx, tc);
#endif
if (st3m_gfx_geom_dirty || (prev_set_mode != set_mode)) {
int was_geom_dirty = (prev_set_mode == set_mode);
bits = _st3m_gfx_bpp(set_mode);
st3m_gfx_geom_dirty = 0;
#if ST3M_GFX_BLIT_TASK
if ((bits > 16))
direct_blit = 1;
else
direct_blit = 0;
#endif
int stride = (bits * st3m_gfx_fb_width) / 8;
switch (bits) {
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
case 1:
ctx_rasterizer_reinit(fb_ctx, st3m_fb, 0, 0,
st3m_gfx_fb_width, st3m_gfx_fb_height,
stride, CTX_FORMAT_GRAY1);
break;
case 2:
ctx_rasterizer_reinit(fb_ctx, st3m_fb, 0, 0,
st3m_gfx_fb_width, st3m_gfx_fb_height,
stride, CTX_FORMAT_GRAY2);
break;
case 4:
ctx_rasterizer_reinit(fb_ctx, st3m_fb, 0, 0,
st3m_gfx_fb_width, st3m_gfx_fb_height,
stride, CTX_FORMAT_GRAY4);
break;
case 8:
case 9:
if ((set_mode & 0xf) == 9)
ctx_rasterizer_reinit(
fb_ctx, st3m_fb, 0, 0, st3m_gfx_fb_width,
st3m_gfx_fb_height, stride, CTX_FORMAT_RGB332);
else
ctx_rasterizer_reinit(
fb_ctx, st3m_fb, 0, 0, st3m_gfx_fb_width,
st3m_gfx_fb_height, stride, CTX_FORMAT_GRAY8);
break;
#endif
case 16:
ctx_rasterizer_reinit(fb_ctx, st3m_fb, 0, 0,
st3m_gfx_fb_width, st3m_gfx_fb_height,
stride,
CTX_FORMAT_RGB565_BYTESWAPPED);
break;
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
case 24:
ctx_rasterizer_reinit(fb_ctx, st3m_fb, 0, 0, 240, 240,
240 * 3, CTX_FORMAT_RGB8);
break;
case 32:
ctx_rasterizer_reinit(fb_ctx, st3m_fb, 0, 0, 240, 240,
240 * 4, CTX_FORMAT_RGBA8);
break;
#endif
}
if ((set_mode & st3m_gfx_smart_redraw) == 0) {
if (!was_geom_dirty) memset(st3m_fb, 0, sizeof(st3m_fb));
}
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
st3m_gfx_viewport_transform(osd_ctx, 1);
if (!was_geom_dirty) memset(st3m_fb2, 0, sizeof(st3m_fb2));
#endif
prev_set_mode = set_mode;
}
st3m_counter_rate_sample(&rast_rate);
if ((set_mode & st3m_gfx_direct_ctx) == 0) {
if ((set_mode & st3m_gfx_smart_redraw)) {
ctx_start_frame(ctx);
ctx_render_ctx(drawlist->user_ctx, ctx);
ctx_end_frame(ctx);
} else {
ctx_save(fb_ctx);
ctx_render_ctx(drawlist->user_ctx, fb_ctx);
ctx_restore(fb_ctx);
}
ctx_drawlist_clear(drawlist->user_ctx);
}
#if ST3M_GFX_BLIT_TASK
if (direct_blit) {
#endif
drawlist->blit_src = st3m_fb;
st3m_gfx_blit(drawlist);
xSemaphoreGive(st3m_fb_lock);
xQueueSend(user_ctx_freeq, &desc_no, portMAX_DELAY);
#if ST3M_GFX_BLIT_TASK
} else {
drawlist->blit_src = st3m_fb_copy;
xSemaphoreTake(st3m_fb_copy_lock, portMAX_DELAY);
int disp_stride = 240 * bits / 8;
if ((st3m_gfx_fb_width == 240) && (drawlist->blit_x == 0)) {
int blit_offset = st3m_gfx_blit_y * 240 * bits / 8;
int overlap = (st3m_gfx_blit_y + 240) - st3m_gfx_fb_height;
if (overlap > 0) {
// vertical overlap, 2 memcpys
int start_scans = 240 - overlap;
memcpy(st3m_fb_copy, st3m_fb + blit_offset,
start_scans * disp_stride);
memcpy(st3m_fb_copy + start_scans * disp_stride, st3m_fb,
overlap * disp_stride);
} else { // best case
memcpy(st3m_fb_copy, st3m_fb + blit_offset,
240 * disp_stride);
}
} else {
int fb_stride = st3m_gfx_fb_width * bits / 8;
int scan_offset = drawlist->blit_x * bits / 8;
int scan_overlap = (drawlist->blit_x + 240) - st3m_gfx_fb_width;
if (scan_overlap <= 0) { // only vertical wrap-around
int blit_offset =
(st3m_gfx_blit_y * 240 + drawlist->blit_x) * bits / 8;
int overlap = (st3m_gfx_blit_y + 240) - st3m_gfx_fb_height;
if (overlap <= 0) overlap = 0;
int start_scans = 240 - overlap;
for (int i = 0; i < start_scans; i++)
memcpy(st3m_fb_copy + i * disp_stride,
st3m_fb + blit_offset + i * fb_stride,
disp_stride);
for (int i = 0; i < overlap; i++)
memcpy(st3m_fb_copy + (i + start_scans) * disp_stride,
st3m_fb + (drawlist->blit_x * bits / 8) +
i * fb_stride,
disp_stride);
} else { // generic case, handles both horizontal and vertical
// wrap-around
int start_bit = 240 - scan_overlap;
int blit_offset = (st3m_gfx_blit_y)*fb_stride;
int overlap = (st3m_gfx_blit_y + 240) - st3m_gfx_fb_height;
if (overlap <= 0) overlap = 0;
int start_scans = 240 - overlap;
int start_bytes = start_bit * bits / 8;
int scan_overlap_bytes = scan_overlap * bits / 8;
for (int i = 0; i < start_scans; i++)
memcpy(
st3m_fb_copy + i * disp_stride,
st3m_fb + blit_offset + i * fb_stride + scan_offset,
start_bytes);
for (int i = 0; i < overlap; i++)
memcpy(st3m_fb_copy + (i + start_scans) * disp_stride,
st3m_fb + scan_offset + i * fb_stride,
start_bytes);
// second pass over scanlines, filling in second half (which
// is wrapped to start of fb-scans)
for (int i = 0; i < start_scans; i++)
memcpy(st3m_fb_copy + i * disp_stride + start_bytes,
st3m_fb + blit_offset + i * fb_stride,
scan_overlap_bytes);
for (int i = 0; i < overlap; i++)
memcpy(st3m_fb_copy + (i + start_scans) * disp_stride +
start_bytes,
st3m_fb + i * fb_stride, scan_overlap_bytes);
}
}
xSemaphoreGive(st3m_fb_copy_lock);
xSemaphoreGive(st3m_fb_lock);
xQueueSend(user_ctx_blitq, &desc_no, portMAX_DELAY);
}
#endif
if (st3m_counter_rate_report(&rast_rate, 1)) {
st3m_counter_rate_sample(&rast_rate);
float rate = 1000000.0 / st3m_counter_rate_average(&rast_rate);
float read_fb =
st3m_counter_timer_average(&rast_read_fb_time) / 1000.0;
float read_dctx =
st3m_counter_timer_average(&rast_read_dctx_time) / 1000.0;
float work = st3m_counter_timer_average(&rast_work_time) / 1000.0;
float write = st3m_counter_timer_average(&rast_write_time) / 1000.0;
// Mark variables as used even if debug is disabled.
(void)rate;
(void)read_fb;
(void)read_dctx;
(void)work;
(void)write;
ESP_LOGD(TAG,
"rasterization: %.3f/sec, read fb %.3fms, read dctx "
"%.3fms, work %.3fms, write %.3fms",
(double)rate, (double)read_fb, (double)read_dctx,
(double)work, (double)write);
}
smoothed_fps = smoothed_fps * 0.6 + 0.4 * rate;
}
}
void st3m_gfx_flow3r_logo(Ctx *ctx, float x, float y, float dim) {
static int frameno = 0;
static int dir = 1;
frameno += dir;
if (frameno > 7 || frameno < -6) {
dir *= -1;
frameno += dir;
}
ctx_save(ctx);
ctx_translate(ctx, x, y);
ctx_scale(ctx, dim, dim);
ctx_translate(ctx, -0.5f, -0.5f);
ctx_linear_gradient(ctx, 0.18f, 0.5f, 0.95f, 0.5f);
ctx_linear_gradient(ctx, 0.18f - frameno * 0.02, 0.5f,
0.95f + frameno * 0.02, 0.5f);
ctx_gradient_add_stop(ctx, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f);
ctx_gradient_add_stop(ctx, 0.2f, 1.0f, 1.0f, 0.0f, 1.0f);
ctx_gradient_add_stop(ctx, 0.4f, 0.0f, 1.0f, 0.0f, 1.0f);
......@@ -351,176 +859,279 @@ void st3m_gfx_show_textview(st3m_gfx_textview_t *tv) {
return;
}
st3m_ctx_desc_t *target = st3m_gfx_drawctx_free_get(portMAX_DELAY);
Ctx *ctx = st3m_gfx_ctx(st3m_gfx_default);
ctx_save(target->ctx);
st3m_gfx_fbconfig(240, 240, 0, 0);
ctx_save(ctx);
// Draw background.
ctx_rgb(target->ctx, 0, 0, 0);
ctx_rectangle(target->ctx, -120, -120, 240, 240);
ctx_fill(target->ctx);
ctx_rgb(ctx, 0, 0, 0);
ctx_rectangle(ctx, -120, -120, 240, 240);
ctx_fill(ctx);
st3m_gfx_flow3r_logo(target->ctx, 0, -30, 150);
st3m_gfx_flow3r_logo(ctx, 0, -30, 150);
int y = 20;
ctx_gray(target->ctx, 1.0);
ctx_text_align(target->ctx, CTX_TEXT_ALIGN_CENTER);
ctx_text_baseline(target->ctx, CTX_TEXT_BASELINE_MIDDLE);
ctx_font_size(target->ctx, 20.0);
ctx_gray(ctx, 1.0);
ctx_text_align(ctx, CTX_TEXT_ALIGN_CENTER);
ctx_text_baseline(ctx, CTX_TEXT_BASELINE_MIDDLE);
ctx_font_size(ctx, 20.0);
// Draw title, if any.
if (tv->title != NULL) {
ctx_move_to(target->ctx, 0, y);
ctx_text(target->ctx, tv->title);
ctx_move_to(ctx, 0, y);
ctx_text(ctx, tv->title);
y += 20;
}
ctx_font_size(target->ctx, 15.0);
ctx_gray(target->ctx, 0.8);
ctx_font_size(ctx, 15.0);
ctx_gray(ctx, 0.8);
// Draw messages.
const char **lines = tv->lines;
if (lines != NULL) {
while (*lines != NULL) {
const char *text = *lines++;
ctx_move_to(target->ctx, 0, y);
ctx_text(target->ctx, text);
ctx_move_to(ctx, 0, y);
ctx_text(ctx, text);
y += 15;
}
}
// Draw version.
ctx_font_size(target->ctx, 15.0);
ctx_gray(target->ctx, 0.6);
ctx_move_to(target->ctx, 0, 100);
ctx_text(target->ctx, st3m_version);
ctx_font_size(ctx, 15.0);
ctx_gray(ctx, 0.6);
ctx_move_to(ctx, 0, 90);
ctx_text(ctx, st3m_version);
ctx_restore(ctx);
ctx_restore(target->ctx);
st3m_gfx_end_frame(ctx);
}
st3m_gfx_drawctx_pipe_put(target);
static void set_pixels_ctx(Ctx *ctx, void *user_data, int x, int y, int w,
int h, void *buf) {
uint16_t *src = buf;
for (int scan = y; scan < y + h; scan++) {
uint16_t *dst = (uint16_t *)&st3m_fb[(scan * 240 + x) * 2];
for (int u = 0; u < w; u++) *(dst++) = *(src++);
}
}
void st3m_gfx_init(void) {
// Make sure we're not being re-initialized.
assert(framebuffer_freeq == NULL);
st3m_counter_rate_init(&blit_rate);
st3m_counter_timer_init(&blit_read_time);
st3m_counter_timer_init(&blit_work_time);
st3m_counter_timer_init(&blit_write_time);
st3m_counter_rate_init(&rast_rate);
st3m_counter_timer_init(&rast_read_fb_time);
st3m_counter_timer_init(&rast_read_dctx_time);
st3m_counter_timer_init(&rast_work_time);
st3m_counter_timer_init(&rast_write_time);
flow3r_bsp_display_init();
// Create framebuffer queues.
framebuffer_freeq = xQueueCreate(ST3M_GFX_NBUFFERS + 1, sizeof(int));
assert(framebuffer_freeq != NULL);
framebuffer_blitq = xQueueCreate(ST3M_GFX_NBUFFERS + 1, sizeof(int));
assert(framebuffer_blitq != NULL);
// Create drawlist ctx queues.
dctx_freeq = xQueueCreate(ST3M_GFX_NCTX + 1, sizeof(int));
assert(dctx_freeq != NULL);
dctx_rastq = xQueueCreate(ST3M_GFX_NCTX + 1, sizeof(int));
assert(dctx_rastq != NULL);
for (int i = 0; i < ST3M_GFX_NBUFFERS; i++) {
// Setup framebuffer descriptor.
st3m_framebuffer_desc_t *fb_desc = &framebuffer_descs[i];
fb_desc->num = i;
fb_desc->ctx = ctx_new_for_framebuffer(
fb_desc->buffer, FLOW3R_BSP_DISPLAY_WIDTH,
FLOW3R_BSP_DISPLAY_HEIGHT, FLOW3R_BSP_DISPLAY_WIDTH * 2,
CTX_FORMAT_RGB565_BYTESWAPPED);
if (i) {
ctx_set_texture_source(fb_desc->ctx, framebuffer_descs[0].ctx);
ctx_set_texture_cache(fb_desc->ctx, framebuffer_descs[0].ctx);
}
assert(fb_desc->ctx != NULL);
// Rotate by 180 deg and translate x and y by 120 px to have (0,0) at
// the center of the screen
int32_t offset_x = FLOW3R_BSP_DISPLAY_WIDTH / 2;
int32_t offset_y = FLOW3R_BSP_DISPLAY_HEIGHT / 2;
ctx_apply_transform(fb_desc->ctx, -1, 0, offset_x, 0, -1, offset_y, 0,
0, 1);
// Push descriptor to freeq.
BaseType_t res = xQueueSend(framebuffer_freeq, &i, 0);
assert(res == pdTRUE);
}
for (int i = 0; i < ST3M_GFX_NCTX; i++) {
// Setup dctx descriptor.
st3m_ctx_desc_t *dctx_desc = &dctx_descs[i];
dctx_desc->num = i;
dctx_desc->ctx = ctx_new_drawlist(FLOW3R_BSP_DISPLAY_WIDTH,
user_ctx_freeq = xQueueCreate(N_DRAWLISTS, sizeof(int));
assert(user_ctx_freeq != NULL);
user_ctx_rastq = xQueueCreate(1, sizeof(int));
assert(user_ctx_rastq != NULL);
user_ctx_blitq = xQueueCreate(1, sizeof(int));
assert(user_ctx_blitq != NULL);
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
st3m_osd_lock = xSemaphoreCreateMutex();
#endif
st3m_fb_lock = xSemaphoreCreateMutex();
st3m_fb_copy_lock = xSemaphoreCreateMutex();
ctx = ctx_new_cb(FLOW3R_BSP_DISPLAY_WIDTH, FLOW3R_BSP_DISPLAY_HEIGHT,
CTX_FORMAT_RGB565_BYTESWAPPED, set_pixels_ctx, NULL, NULL,
NULL, sizeof(scratch), scratch,
CTX_FLAG_HASH_CACHE | CTX_FLAG_KEEP_DATA);
assert(ctx != NULL);
// Setup rasterizers for frame buffer formats
fb_ctx = ctx_new_for_framebuffer(
st3m_fb, FLOW3R_BSP_DISPLAY_WIDTH, FLOW3R_BSP_DISPLAY_HEIGHT,
FLOW3R_BSP_DISPLAY_WIDTH * 2, CTX_FORMAT_RGB565_BYTESWAPPED);
assert(fb_ctx != NULL);
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
osd_ctx = ctx_new_for_framebuffer(
st3m_fb2, FLOW3R_BSP_DISPLAY_WIDTH, FLOW3R_BSP_DISPLAY_HEIGHT,
FLOW3R_BSP_DISPLAY_WIDTH * 4, CTX_FORMAT_RGBA8);
assert(osd_ctx != NULL);
st3m_gfx_viewport_transform(osd_ctx, 0);
ctx_set_texture_source(osd_ctx, fb_ctx);
ctx_set_texture_cache(osd_ctx, fb_ctx);
ctx_set_texture_source(ctx, fb_ctx);
ctx_set_texture_cache(ctx, fb_ctx);
#endif
// Setup user_ctx descriptor.
for (int i = 0; i < N_DRAWLISTS; i++) {
drawlists[i].user_ctx = ctx_new_drawlist(FLOW3R_BSP_DISPLAY_WIDTH,
FLOW3R_BSP_DISPLAY_HEIGHT);
ctx_set_texture_cache(dctx_desc->ctx, framebuffer_descs[0].ctx);
assert(dctx_desc->ctx != NULL);
assert(drawlists[i].user_ctx != NULL);
ctx_set_texture_cache(drawlists[i].user_ctx, fb_ctx);
// Push descriptor to freeq.
BaseType_t res = xQueueSend(dctx_freeq, &i, 0);
BaseType_t res = xQueueSend(user_ctx_freeq, &i, 0);
assert(res == pdTRUE);
}
// Start crtc.
BaseType_t res = xTaskCreate(st3m_gfx_crtc_task, "crtc", 4096, NULL,
ESP_TASK_PRIO_MIN + 3, &crtc_task);
// Start rasterization, scan-out
BaseType_t res =
xTaskCreatePinnedToCore(st3m_gfx_rast_task, "gfx-rast", 8192, NULL,
ESP_TASK_PRIO_MIN + 1, &graphics_rast_task, 0);
assert(res == pdPASS);
// Start rast.
res = xTaskCreate(st3m_gfx_rast_task, "rast", 8192, NULL,
ESP_TASK_PRIO_MIN + 1, &rast_task);
#if ST3M_GFX_BLIT_TASK
res = xTaskCreate(st3m_gfx_blit_task, "gfx-blit", 2048, NULL,
ESP_TASK_PRIO_MIN + 2, &graphics_blit_task);
assert(res == pdPASS);
#endif
}
static int last_descno = 0;
static Ctx *st3m_gfx_drawctx_free_get(TickType_t ticks_to_wait) {
BaseType_t res = xQueueReceive(user_ctx_freeq, &last_descno, ticks_to_wait);
if (res != pdTRUE) return NULL;
st3m_gfx_drawlist *drawlist = &drawlists[last_descno];
st3m_gfx_mode set_mode = _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
drawlist->mode = set_mode;
st3m_ctx_desc_t *st3m_gfx_drawctx_free_get(TickType_t ticks_to_wait) {
int descno;
BaseType_t res = xQueueReceive(dctx_freeq, &descno, ticks_to_wait);
if (res != pdTRUE) {
return NULL;
if (set_mode & st3m_gfx_direct_ctx) {
while (!uxSemaphoreGetCount(st3m_fb_lock)) vTaskDelay(0);
if ((set_mode & st3m_gfx_smart_redraw)) {
ctx_start_frame(ctx);
ctx_save(ctx);
return ctx;
}
return fb_ctx;
}
return &dctx_descs[descno];
return drawlist->user_ctx;
}
void st3m_gfx_drawctx_pipe_put(st3m_ctx_desc_t *desc) {
xQueueSend(dctx_rastq, &desc->num, portMAX_DELAY);
static void st3m_gfx_pipe_put(void) {
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
st3m_gfx_drawlist *drawlist = &drawlists[last_descno];
drawlist->osd_x0 = _st3m_osd_x0;
drawlist->osd_y0 = _st3m_osd_y0;
drawlist->osd_x1 = _st3m_osd_x1;
drawlist->osd_y1 = _st3m_osd_y1;
drawlist->blit_x = st3m_gfx_blit_x;
drawlist->blit_y = st3m_gfx_blit_y;
#endif
xQueueSend(user_ctx_rastq, &last_descno, portMAX_DELAY);
}
uint8_t st3m_gfx_drawctx_pipe_full(void) {
return uxQueueSpacesAvailable(dctx_rastq) == 0;
static Ctx *st3m_gfx_ctx_int(st3m_gfx_mode mode);
void st3m_gfx_end_frame(Ctx *ctx) {
ctx_restore(ctx);
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
if (ctx == st3m_gfx_ctx_int(st3m_gfx_osd)) {
xSemaphoreGive(st3m_osd_lock);
return;
}
#endif
st3m_gfx_mode set_mode = _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
if ((set_mode & st3m_gfx_smart_redraw) && (set_mode & st3m_gfx_direct_ctx))
ctx_end_frame(ctx);
void st3m_gfx_flush(void) {
st3m_gfx_pipe_put();
}
uint8_t st3m_gfx_pipe_available(void) {
st3m_gfx_mode set_mode = _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
if ((set_mode & st3m_gfx_EXPERIMENTAL_think_per_draw) &&
(smoothed_fps > 13.0))
return 1;
return uxQueueMessagesWaiting(user_ctx_freeq) > _st3m_gfx_low_latency;
}
uint8_t st3m_gfx_pipe_full(void) {
st3m_gfx_mode set_mode = _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
if ((set_mode & st3m_gfx_EXPERIMENTAL_think_per_draw) &&
(smoothed_fps > 13.0))
return 0;
return uxQueueSpacesAvailable(user_ctx_rastq) == 0;
}
void st3m_gfx_flush(int timeout_ms) {
ESP_LOGW(TAG, "Pipeline flush/reset requested...");
// Drain all workqs and freeqs.
xQueueReset(dctx_freeq);
xQueueReset(dctx_rastq);
xQueueReset(framebuffer_freeq);
xQueueReset(framebuffer_blitq);
xQueueReset(user_ctx_freeq);
xQueueReset(user_ctx_rastq);
// Delay, making sure pipeline tasks have returned all used descriptors. One
// second is enough to make sure we've processed everything.
vTaskDelay(1000 / portTICK_PERIOD_MS);
vTaskDelay(timeout_ms / portTICK_PERIOD_MS);
// And drain again.
xQueueReset(framebuffer_freeq);
xQueueReset(dctx_freeq);
// Now, re-submit all descriptors to freeqs.
for (int i = 0; i < ST3M_GFX_NBUFFERS; i++) {
BaseType_t res = xQueueSend(framebuffer_freeq, &i, 0);
assert(res == pdTRUE);
}
for (int i = 0; i < ST3M_GFX_NCTX; i++) {
BaseType_t res = xQueueSend(dctx_freeq, &i, 0);
xQueueReset(user_ctx_freeq);
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
_st3m_osd_x0 = 0;
_st3m_osd_y0 = 0;
_st3m_osd_x1 = 0;
_st3m_osd_y1 = 0;
#endif
for (int i = 0; i < N_DRAWLISTS; i++) {
ctx_drawlist_clear(drawlists[i].user_ctx);
BaseType_t res = xQueueSend(user_ctx_freeq, &i, 0);
assert(res == pdTRUE);
}
ESP_LOGW(TAG, "Pipeline flush/reset done.");
}
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
void st3m_gfx_overlay_clip(int x0, int y0, int x1, int y1) {
if (y1 < 0) y1 = 0;
if (y1 > FLOW3R_BSP_DISPLAY_HEIGHT) y1 = FLOW3R_BSP_DISPLAY_HEIGHT;
if (y0 < 0) y0 = 0;
if (y0 > FLOW3R_BSP_DISPLAY_HEIGHT) y0 = FLOW3R_BSP_DISPLAY_HEIGHT;
if (x1 < 0) x1 = 0;
if (x1 > FLOW3R_BSP_DISPLAY_WIDTH) x1 = FLOW3R_BSP_DISPLAY_WIDTH;
if (x0 < 0) x0 = 0;
if (x0 > FLOW3R_BSP_DISPLAY_WIDTH) x0 = FLOW3R_BSP_DISPLAY_WIDTH;
if ((x1 < x0) || (y1 < y0)) {
_st3m_osd_x0 = _st3m_osd_y0 = _st3m_osd_x1 = _st3m_osd_y1 = 0;
} else {
_st3m_osd_x0 = x0;
_st3m_osd_y0 = y0;
_st3m_osd_x1 = x1;
_st3m_osd_y1 = y1;
}
}
#endif
void st3m_gfx_fbconfig(int width, int height, int blit_x, int blit_y) {
if (width <= 0) width = st3m_gfx_fb_width;
if (height <= 0) height = st3m_gfx_fb_height;
st3m_gfx_mode set_mode = _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
int bits = st3m_gfx_bpp(set_mode);
if (width > CTX_MAX_SCANLINE_LENGTH) width = CTX_MAX_SCANLINE_LENGTH;
if ((width * height * bits) / 8 > (240 * 240 * 4))
height = 240 * 240 * 4 * 8 / (width * bits);
blit_x %= width;
blit_y %= height;
if ((st3m_gfx_fb_width != width) || (st3m_gfx_fb_height != height)) {
st3m_gfx_fb_width = width;
st3m_gfx_fb_height = height;
st3m_gfx_geom_dirty++;
}
st3m_gfx_blit_x = blit_x;
st3m_gfx_blit_y = blit_y;
}
void st3m_gfx_get_fbconfig(int *width, int *height, int *blit_x, int *blit_y) {
if (width) *width = st3m_gfx_fb_width;
if (height) *height = st3m_gfx_fb_height;
if (blit_x) *blit_x = st3m_gfx_blit_x;
if (blit_y) *blit_y = st3m_gfx_blit_y;
}