17 Commits

Author SHA1 Message Date
Adolfo Reyna 214287193e feat(bibleverse): editorial v6 - Georgia Italic serif pretty font, 15px lift, menu auto-show 10s, animated book picker, no black bar
Main / Build (BookPlayer) (push) Has been cancelled
Main / Build (Brainfuck) (push) Has been cancelled
Main / Build (Breakout) (push) Has been cancelled
Main / Build (Calculator) (push) Has been cancelled
Main / Build (Diceware) (push) Has been cancelled
Main / Build (EpubReader) (push) Has been cancelled
Main / Build (GPIO) (push) Has been cancelled
Main / Build (GraphicsDemo) (push) Has been cancelled
Main / Build (HelloWorld) (push) Has been cancelled
Main / Build (M5UnitTest) (push) Has been cancelled
Main / Build (Magic8Ball) (push) Has been cancelled
Main / Build (MediaKeys) (push) Has been cancelled
Main / Build (MystifyDemo) (push) Has been cancelled
Main / Build (SerialConsole) (push) Has been cancelled
Main / Build (Snake) (push) Has been cancelled
Main / Build (TamaTac) (push) Has been cancelled
Main / Build (TodoList) (push) Has been cancelled
Main / Build (TwoEleven) (push) Has been cancelled
Main / Bundle (push) Has been cancelled
- Pretty font: embedded LVGL C Georgia Italic 26/22/20/18/16 + regular 24 (lv_font_conv --lv-include lvgl.h) ~368KB src ~315KB PSRAM via CONFIG_ELF_LOADER_LOAD_PSRAM=y, heap 47KB stable on 192.168.68.129 0.8.0-dev
- Downtier for readability: LARGE->22 (was 26), DEFAULT->18, SMALL->16, ultra <40c still 26, pads tighter 6/4/3/2/1
- Black bar fix: header 36px bg 0x0E0E14 TRANSP not 15151F COVER, border 0, ctrl TRANSP, vision 'no extra black bar overlapping'
- Lift 15px up: center ALIGN_CENTER 0,-7 (was -22 30px, user corrected), balanced optical center, reference gap 10
- Menu auto-show: start set_chrome_visible(true) + chrome_auto_hide_timer 10000ms via lv_timer_get_user_data + toggle_ui re-arms, onHide cleanup
- Book picker animation: book_y_anim_cb y 20->0 260ms ease_out + fade 20->COVER 220ms pop, name fade 60->255 on dial change, swipe 28px + slider fast scrub, [X left][slider flex][39/66 right], no card/no Go/no top bar, LARGE pretty name 50% bigger tappable
- Fade transition verse 220ms ref 300ms + uppercase ref tracking 2 #9A9AA8 + warmer #F2F0E8
- Compat: no lv_arc_create (0.7 fleet), 0 missing LVGL syms undef=0, build 4.3M
- Skill docs: SKILL.md v6 black-bar+downtier+auto-hide+anim, pretty-font.md v6, immersive-ui.md v6

Screenshots: Zephaniah 1:3 8683 bytes menu visible (top ref + bottom pills), after 6882 immersive, Habakkuk 2:9 no crop vision PASS
Co-Authored-By: internal-model
2026-07-12 12:07:04 -04:00
Adolfo Reyna 9773a78f6b rlcd: fix Mp3Player pop/repetition and VoiceRecorder mic pitch
- Mp3Player: increase task stack 4096->6144, prio 5->6, remove taskYIELD causing audio gaps
- VoiceRecorder: configure I2S as stereo for ES7210 (dual mic) like working MicroPython,
  then downmix left channel to mono WAV to fix low-pitch mic-like recording
2026-07-11 23:54:17 -04:00
Adolfo Reyna e9c396df66 feat(BibleVerse): immersive offline bible app - ESP32 color screen
- Single verse fullscreen, auto-advances every minute, tap toggles chrome
- Offline WEB bible split per-book: books.json + 66 bin/idx pairs (~4.1MB)
  - bin = NUL-terminated UTF-8 concatenation, idx = {offset,cnum,vnum}
  - RAM tiny: loads one book at a time, biggest Psalms ~215KB vs 3.8MB monolith
- Adaptive text scaling by length: <40 ultra-large (Jesus wept), <100 large,
  100-200 default, 200-350 small, 350+ compact (Esther 8:9 492c)
- Persistence via user_data_path: progress.txt + favorites.txt (like BookPlayer)
- LVGL fixes: use lvgl_get_text_font(FONT_SIZE_*) - direct montserrat not exported,
  no gradients (bg_grad_color not exported), LV_OPA_95 -> OPA_COVER, format-truncation werror silenced
- Deploys to 192.168.68.112 via PUT /api/apps/install (dashboard port 80, dev 6666 closed)

Tested: build esp32s3 local-sdk 0 missing symbols, 4.2MB .app, screenshot Gen 2:10

Refs: tools/convert_bible.py regenerates assets from WEB source
2026-07-11 17:08:22 -04:00
Adolfo Reyna 12e99f896a perf: Rate-limit progress bar updates to prevent redundant layout invalidation and redraws 2026-07-09 22:27:44 -04:00
Adolfo Reyna efea28e5d6 feat: Add BookPlayer app and update CI build matrix 2026-07-08 23:20:03 -04:00
Adolfo Reyna 364dc6b2f7 fix(reynabot): Replace unsupported Unicode emojis with custom girl/boy PNG assets 2026-07-01 22:56:31 -04:00
Adolfo Reyna 5eb9620468 feat(reynabot): Show dedicated speaker selection screen on launch 2026-07-01 22:51:23 -04:00
Adolfo Reyna 6bf06c8867 feat(reynabot): Add speaker selection buttons for Grace and Elias 2026-07-01 22:43:36 -04:00
Adolfo Reyna a4be0d73ae feat(reynabot): Improve UI and update voice endpoint to kids gateway 2026-07-01 13:45:04 -04:00
Adolfo Reyna 98ae52d3a8 Implement Mp3Player dynamic application 2026-06-28 22:22:57 -04:00
Adolfo Reyna 0fb3c22f79 feat: add McpScreen phase 2 display tools
Main / Build (Brainfuck) (push) Has been cancelled
Main / Build (Breakout) (push) Has been cancelled
Main / Build (Calculator) (push) Has been cancelled
Main / Build (Diceware) (push) Has been cancelled
Main / Build (EpubReader) (push) Has been cancelled
Main / Build (GPIO) (push) Has been cancelled
Main / Build (GraphicsDemo) (push) Has been cancelled
Main / Build (HelloWorld) (push) Has been cancelled
Main / Build (M5UnitTest) (push) Has been cancelled
Main / Build (Magic8Ball) (push) Has been cancelled
Main / Build (MediaKeys) (push) Has been cancelled
Main / Build (MystifyDemo) (push) Has been cancelled
Main / Build (SerialConsole) (push) Has been cancelled
Main / Build (Snake) (push) Has been cancelled
Main / Build (TamaTac) (push) Has been cancelled
Main / Build (TodoList) (push) Has been cancelled
Main / Build (TwoEleven) (push) Has been cancelled
Main / Bundle (push) Has been cancelled
2026-06-25 12:43:08 -04:00
Adolfo Reyna ac9d6dc0d3 feat: add Tactility MCP Screen foundation 2026-06-24 23:48:21 -04:00
Adolfo Reyna fb800bb5bd docs: add McpScreen implementation plan 2026-06-24 23:19:03 -04:00
Adolfo Reyna e5233efdf3 chore: save Tactility app experiments 2026-06-24 23:17:40 -04:00
Adolfo Reyna ca55f16085 feat(audiotest): add AudioTest application for testing recording and playback 2026-06-23 18:32:44 -04:00
Adolfo Reyna 3c6acf47a5 Add touch support to Snake game by dividing the screen into Up/Down/Left/Right zones
Main / Build (Brainfuck) (push) Has been cancelled
Main / Build (Breakout) (push) Has been cancelled
Main / Build (Calculator) (push) Has been cancelled
Main / Build (Diceware) (push) Has been cancelled
Main / Build (EpubReader) (push) Has been cancelled
Main / Build (GPIO) (push) Has been cancelled
Main / Build (GraphicsDemo) (push) Has been cancelled
Main / Build (HelloWorld) (push) Has been cancelled
Main / Build (M5UnitTest) (push) Has been cancelled
Main / Build (Magic8Ball) (push) Has been cancelled
Main / Build (MediaKeys) (push) Has been cancelled
Main / Build (MystifyDemo) (push) Has been cancelled
Main / Build (SerialConsole) (push) Has been cancelled
Main / Build (Snake) (push) Has been cancelled
Main / Build (TamaTac) (push) Has been cancelled
Main / Build (TodoList) (push) Has been cancelled
Main / Build (TwoEleven) (push) Has been cancelled
Main / Bundle (push) Has been cancelled
2026-06-23 12:16:33 -04:00
Adolfo Reyna 23e969c1b6 Configure HelloWorld manifest target platforms for esp32s3
Main / Build (Brainfuck) (push) Has been cancelled
Main / Build (Breakout) (push) Has been cancelled
Main / Build (Calculator) (push) Has been cancelled
Main / Build (Diceware) (push) Has been cancelled
Main / Build (EpubReader) (push) Has been cancelled
Main / Build (GPIO) (push) Has been cancelled
Main / Build (GraphicsDemo) (push) Has been cancelled
Main / Build (HelloWorld) (push) Has been cancelled
Main / Build (M5UnitTest) (push) Has been cancelled
Main / Build (Magic8Ball) (push) Has been cancelled
Main / Build (MediaKeys) (push) Has been cancelled
Main / Build (MystifyDemo) (push) Has been cancelled
Main / Build (SerialConsole) (push) Has been cancelled
Main / Build (Snake) (push) Has been cancelled
Main / Build (TamaTac) (push) Has been cancelled
Main / Build (TodoList) (push) Has been cancelled
Main / Build (TwoEleven) (push) Has been cancelled
Main / Bundle (push) Has been cancelled
2026-06-23 11:59:51 -04:00
190 changed files with 20906 additions and 4 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
Build:
strategy:
matrix:
app_name: [Brainfuck, Breakout, Calculator, Diceware, EpubReader, GPIO, GraphicsDemo, HelloWorld, M5UnitTest, Magic8Ball, MediaKeys, MystifyDemo, SerialConsole, Snake, TamaTac, TodoList, TwoEleven]
app_name: [Brainfuck, Breakout, BookPlayer, Calculator, Diceware, EpubReader, GPIO, GraphicsDemo, HelloWorld, M5UnitTest, Magic8Ball, MediaKeys, MystifyDemo, SerialConsole, Snake, TamaTac, TodoList, TwoEleven]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+16
View File
@@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.20)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
if (DEFINED ENV{TACTILITY_SDK_PATH})
set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH})
else()
set(TACTILITY_SDK_PATH "../../release/TactilitySDK")
message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}")
endif()
include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake")
set(EXTRA_COMPONENT_DIRS ${TACTILITY_SDK_PATH})
project(AudioTest)
tactility_project(AudioTest)
+6
View File
@@ -0,0 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c)
idf_component_register(
SRCS ${SOURCE_FILES}
REQUIRES TactilitySDK
)
+337
View File
@@ -0,0 +1,337 @@
/**
* Audio Test - Record & Playback
*
* Records from the ES8311 microphone via I2S and plays it back through
* the FM8002E speaker amplifier. Uses the Tactility kernel I2S controller API.
*
* UI: Two buttons - Record and Play
* Record: holds recording for up to ~3 seconds (16kHz 16-bit mono ≈ 96KB)
* Play: plays back the last recorded buffer
*/
#include <tt_app.h>
#include <tt_lvgl.h>
#include <tt_lvgl_toolbar.h>
#include <tactility/device.h>
#include <tactility/drivers/i2s_controller.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#define TAG "AudioTest"
/* ─── Audio params ─── */
#define SAMPLE_RATE 16000
#define BITS_PER_SAMPLE 16
#define RECORD_SECONDS 3
/* 16-bit mono → 2 bytes per sample */
#define AUDIO_BUF_SIZE (SAMPLE_RATE * (BITS_PER_SAMPLE / 8) * RECORD_SECONDS)
/* Chunk size for read/write calls (512 samples = 1024 bytes at 16-bit) */
#define CHUNK_SAMPLES 512
#define CHUNK_BYTES (CHUNK_SAMPLES * (BITS_PER_SAMPLE / 8))
/* ─── App state ─── */
typedef enum {
STATE_IDLE,
STATE_RECORDING,
STATE_PLAYING
} AudioState;
typedef struct {
struct Device* i2s_dev;
uint8_t* audio_buf;
size_t recorded_bytes;
AudioState state;
lv_obj_t* btn_record;
lv_obj_t* btn_play;
lv_obj_t* lbl_status;
lv_obj_t* bar_progress;
TaskHandle_t task_handle;
} AppCtx;
/* ─── Forward decls ─── */
static void record_task(void* arg);
static void play_task(void* arg);
static void update_ui(AppCtx* ctx);
/* ─── Callbacks ─── */
static void on_record_click(lv_event_t* e) {
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
if (ctx->state != STATE_IDLE) return;
if (ctx->i2s_dev == NULL) return;
ctx->state = STATE_RECORDING;
ctx->recorded_bytes = 0;
update_ui(ctx);
xTaskCreate(record_task, "audio_rec", 4096, ctx, 5, &ctx->task_handle);
}
static void on_play_click(lv_event_t* e) {
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
if (ctx->state != STATE_IDLE) return;
if (ctx->recorded_bytes == 0) return;
if (ctx->i2s_dev == NULL) return;
ctx->state = STATE_PLAYING;
update_ui(ctx);
xTaskCreate(play_task, "audio_play", 4096, ctx, 5, &ctx->task_handle);
}
/* ─── Update UI labels/buttons from any context ─── */
static void update_ui(AppCtx* ctx) {
if (ctx->lbl_status == NULL) return;
switch (ctx->state) {
case STATE_RECORDING:
lv_label_set_text(ctx->lbl_status, "🔴 Recording...");
lv_obj_add_state(ctx->btn_record, LV_STATE_DISABLED);
lv_obj_add_state(ctx->btn_play, LV_STATE_DISABLED);
break;
case STATE_PLAYING:
lv_label_set_text(ctx->lbl_status, "🔊 Playing...");
lv_obj_add_state(ctx->btn_record, LV_STATE_DISABLED);
lv_obj_add_state(ctx->btn_play, LV_STATE_DISABLED);
break;
case STATE_IDLE:
default:
if (ctx->recorded_bytes > 0) {
char buf[64];
snprintf(buf, sizeof(buf), "Ready (%u bytes recorded)", (unsigned)ctx->recorded_bytes);
lv_label_set_text(ctx->lbl_status, buf);
} else {
lv_label_set_text(ctx->lbl_status, "Press Record to capture audio");
}
lv_obj_clear_state(ctx->btn_record, LV_STATE_DISABLED);
if (ctx->recorded_bytes > 0) {
lv_obj_clear_state(ctx->btn_play, LV_STATE_DISABLED);
} else {
lv_obj_add_state(ctx->btn_play, LV_STATE_DISABLED);
}
break;
}
}
/* ─── Record task ─── */
static void record_task(void* arg) {
AppCtx* ctx = (AppCtx*)arg;
size_t total = 0;
size_t bytes_read = 0;
/* Configure I2S for recording */
struct I2sConfig cfg = {
.communication_format = I2S_FORMAT_STAND_I2S,
.sample_rate = SAMPLE_RATE,
.bits_per_sample = BITS_PER_SAMPLE,
.channel_left = 0,
.channel_right = I2S_CHANNEL_NONE
};
device_lock(ctx->i2s_dev);
error_t err = i2s_controller_set_config(ctx->i2s_dev, &cfg);
device_unlock(ctx->i2s_dev);
if (err != ERROR_NONE) {
ESP_LOGE(TAG, "Failed to set I2S config for recording: %d", err);
tt_lvgl_lock(portMAX_DELAY);
ctx->state = STATE_IDLE;
update_ui(ctx);
tt_lvgl_unlock();
vTaskDelete(NULL);
return;
}
ESP_LOGI(TAG, "Recording started (max %d bytes)", AUDIO_BUF_SIZE);
while (total < AUDIO_BUF_SIZE && ctx->state == STATE_RECORDING) {
size_t remaining = AUDIO_BUF_SIZE - total;
size_t to_read = (remaining < CHUNK_BYTES) ? remaining : CHUNK_BYTES;
err = i2s_controller_read(ctx->i2s_dev,
ctx->audio_buf + total,
to_read,
&bytes_read,
pdMS_TO_TICKS(1000));
if (err != ERROR_NONE) {
ESP_LOGE(TAG, "I2S read error: %d", err);
break;
}
total += bytes_read;
/* Update progress bar */
int pct = (int)((total * 100) / AUDIO_BUF_SIZE);
tt_lvgl_lock(portMAX_DELAY);
lv_bar_set_value(ctx->bar_progress, pct, LV_ANIM_OFF);
tt_lvgl_unlock();
}
ctx->recorded_bytes = total;
ctx->state = STATE_IDLE;
ctx->task_handle = NULL;
ESP_LOGI(TAG, "Recording complete: %u bytes", (unsigned)total);
tt_lvgl_lock(portMAX_DELAY);
lv_bar_set_value(ctx->bar_progress, 0, LV_ANIM_OFF);
update_ui(ctx);
tt_lvgl_unlock();
vTaskDelete(NULL);
}
/* ─── Play task ─── */
static void play_task(void* arg) {
AppCtx* ctx = (AppCtx*)arg;
size_t total = 0;
size_t bytes_written = 0;
/* Configure I2S for playback */
struct I2sConfig cfg = {
.communication_format = I2S_FORMAT_STAND_I2S,
.sample_rate = SAMPLE_RATE,
.bits_per_sample = BITS_PER_SAMPLE,
.channel_left = 0,
.channel_right = I2S_CHANNEL_NONE
};
device_lock(ctx->i2s_dev);
error_t err = i2s_controller_set_config(ctx->i2s_dev, &cfg);
device_unlock(ctx->i2s_dev);
if (err != ERROR_NONE) {
ESP_LOGE(TAG, "Failed to set I2S config for playback: %d", err);
tt_lvgl_lock(portMAX_DELAY);
ctx->state = STATE_IDLE;
update_ui(ctx);
tt_lvgl_unlock();
vTaskDelete(NULL);
return;
}
ESP_LOGI(TAG, "Playback started (%u bytes)", (unsigned)ctx->recorded_bytes);
while (total < ctx->recorded_bytes && ctx->state == STATE_PLAYING) {
size_t remaining = ctx->recorded_bytes - total;
size_t to_write = (remaining < CHUNK_BYTES) ? remaining : CHUNK_BYTES;
err = i2s_controller_write(ctx->i2s_dev,
ctx->audio_buf + total,
to_write,
&bytes_written,
pdMS_TO_TICKS(1000));
if (err != ERROR_NONE) {
ESP_LOGE(TAG, "I2S write error: %d", err);
break;
}
total += bytes_written;
/* Update progress bar */
int pct = (int)((total * 100) / ctx->recorded_bytes);
tt_lvgl_lock(portMAX_DELAY);
lv_bar_set_value(ctx->bar_progress, pct, LV_ANIM_OFF);
tt_lvgl_unlock();
}
ctx->state = STATE_IDLE;
ctx->task_handle = NULL;
ESP_LOGI(TAG, "Playback complete");
tt_lvgl_lock(portMAX_DELAY);
lv_bar_set_value(ctx->bar_progress, 0, LV_ANIM_OFF);
update_ui(ctx);
tt_lvgl_unlock();
vTaskDelete(NULL);
}
/* ─── App lifecycle ─── */
static AppCtx g_ctx;
static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
memset(&g_ctx, 0, sizeof(g_ctx));
/* Allocate audio buffer */
g_ctx.audio_buf = (uint8_t*)malloc(AUDIO_BUF_SIZE);
if (g_ctx.audio_buf == NULL) {
ESP_LOGE(TAG, "Failed to allocate audio buffer (%d bytes)", AUDIO_BUF_SIZE);
}
/* Find I2S device */
g_ctx.i2s_dev = device_find_by_name("i2s0");
if (g_ctx.i2s_dev == NULL) {
ESP_LOGE(TAG, "I2S device 'i2s0' not found!");
} else {
ESP_LOGI(TAG, "Found I2S device: %s", g_ctx.i2s_dev->name);
}
/* ─── UI ─── */
lv_obj_t* toolbar = tt_lvgl_toolbar_create_for_app(parent, app);
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
/* Status label */
g_ctx.lbl_status = lv_label_create(parent);
lv_label_set_text(g_ctx.lbl_status, "Initializing...");
lv_obj_set_width(g_ctx.lbl_status, lv_pct(90));
lv_label_set_long_mode(g_ctx.lbl_status, LV_LABEL_LONG_WRAP);
lv_obj_set_style_text_align(g_ctx.lbl_status, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(g_ctx.lbl_status, LV_ALIGN_TOP_MID, 0, 50);
/* Progress bar */
g_ctx.bar_progress = lv_bar_create(parent);
lv_obj_set_size(g_ctx.bar_progress, lv_pct(80), 10);
lv_bar_set_range(g_ctx.bar_progress, 0, 100);
lv_bar_set_value(g_ctx.bar_progress, 0, LV_ANIM_OFF);
lv_obj_align(g_ctx.bar_progress, LV_ALIGN_CENTER, 0, -10);
/* Button container */
lv_obj_t* btn_container = lv_obj_create(parent);
lv_obj_remove_style_all(btn_container);
lv_obj_set_size(btn_container, lv_pct(90), 50);
lv_obj_set_flex_flow(btn_container, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(btn_container, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_align(btn_container, LV_ALIGN_CENTER, 0, 30);
/* Record button */
g_ctx.btn_record = lv_btn_create(btn_container);
lv_obj_set_size(g_ctx.btn_record, 100, 40);
lv_obj_t* lbl_rec = lv_label_create(g_ctx.btn_record);
lv_label_set_text(lbl_rec, LV_SYMBOL_AUDIO " Record");
lv_obj_center(lbl_rec);
lv_obj_add_event_cb(g_ctx.btn_record, on_record_click, LV_EVENT_CLICKED, &g_ctx);
/* Play button */
g_ctx.btn_play = lv_btn_create(btn_container);
lv_obj_set_size(g_ctx.btn_play, 100, 40);
lv_obj_t* lbl_play = lv_label_create(g_ctx.btn_play);
lv_label_set_text(lbl_play, LV_SYMBOL_PLAY " Play");
lv_obj_center(lbl_play);
lv_obj_add_event_cb(g_ctx.btn_play, on_play_click, LV_EVENT_CLICKED, &g_ctx);
/* Check initialization status */
if (g_ctx.i2s_dev == NULL) {
lv_label_set_text(g_ctx.lbl_status, "ERROR: I2S device not found");
lv_obj_add_state(g_ctx.btn_record, LV_STATE_DISABLED);
lv_obj_add_state(g_ctx.btn_play, LV_STATE_DISABLED);
} else if (g_ctx.audio_buf == NULL) {
lv_label_set_text(g_ctx.lbl_status, "ERROR: Out of memory");
lv_obj_add_state(g_ctx.btn_record, LV_STATE_DISABLED);
lv_obj_add_state(g_ctx.btn_play, LV_STATE_DISABLED);
} else {
g_ctx.state = STATE_IDLE;
update_ui(&g_ctx);
}
}
int main(int argc, char* argv[]) {
tt_app_register((AppRegistration) {
.onShow = onShowApp
});
return 0;
}
+10
View File
@@ -0,0 +1,10 @@
[manifest]
version=0.1
[target]
sdk=0.7.0-dev
platforms=esp32s3
[app]
id=one.tactility.audiotest
versionName=0.1.0
versionCode=1
name=Audio Test
+16
View File
@@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.20)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
if (DEFINED ENV{TACTILITY_SDK_PATH})
set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH})
else()
set(TACTILITY_SDK_PATH "../../release/TactilitySDK")
message(WARNING "TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}")
endif()
include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake")
set(EXTRA_COMPONENT_DIRS ${TACTILITY_SDK_PATH})
project(BibleVerse)
tactility_project(BibleVerse)
+84
View File
@@ -0,0 +1,84 @@
# BibleVerse for Tactility
Single-verse bible reader. Shows one verse fullscreen, auto-advances every minute. Tap to toggle controls.
## UX
- **Default view (no chrome)**: immersive context — just the verse text + reference, centered, large font. Like BookPlayer image-only view.
- **Tap**: shows header (current ref + position) and footer controls:
- Close X, Prev, Pause/Play (auto-advance), Next, Highlight (star), Audio memo placeholder.
- Highlighted verses saved to `favorites.txt` in app user data. Long-term: record voice memo, read aloud TTS.
## Bible data format (split per book)
We fetch public domain bibles from https://github.com/sajeevavahini/bibles (Zefania XML).
Offline conversion:
```
python3 tools/convert_bible.py --src "World English Bible.xml" --dst assets/bible
```
Result structure:
```
assets/bible/
books.json # {"books":[{"bnumber":1,"bname":"Genesis","verses":1533,...}], total_verses, translation, version}
01.bin / 01.idx # Genesis
02.bin / 02.idx # Exodus
...
66.bin / 66.idx
```
- `.bin`: concatenated NUL-terminated UTF8 verse texts. No parsing needed, low RAM.
- `.idx`: header 10 bytes: magic 'BIBK', uint16 version, uint32 count. Then per entry 8 bytes: uint32 offset, uint16 cnum, uint16 vnum.
This split makes memory footprint tiny for ESP32:
- Only one book loaded at a time (max ~215KB for Psalms, typical 12-100KB).
- Index ~19KB max for Psalms, typically <5KB.
## SD card fallback
App also scans:
- `/sdcard/bibles/` and `/sdcard/bible/`
If `books.json` not found in assets, it will try to use SD folder. This lets users drop extra translations offline.
## Settings persistence
Same pattern as EpubReader / BookPlayer (but those two didn't persist in BookPlayer, EpubReader does):
Using Tactility app user data paths:
- `tt_app_get_user_data_path()` -> e.g. `/sdcard/user/app/one.tactility.bibleverse`
- `progress.txt`: stores global verse index + b/c/v for debug
```
1234
1 1 1
0 0
```
- `favorites.txt`: list of global indices, one per line.
This lives on SD card (or internal data partition), survives app reinstalls.
## Build (color screen device, esp32s3)
Same as BookPlayer — external app, compiled for esp32s3 target (covers CYD-2432s028r, m5stack-cores3, etc).
```bash
# source IDF (example)
source ~/esp/esp-idf/export.sh
export TACTILITY_SDK_PATH=/path/to/TactilitySDK # or use --local-sdk
python3 tactility.py Apps/BibleVerse build esp32s3 --local-sdk
```
Output: `build/BibleVerse.app` installable via Tactility file manager or `tactility.py ... install <ip>`
## Future extensions
- Voice memo: record to `/sdcard/bible_memos/<global>.wav` using same I2S code as BookPlayer
- TTS read aloud: use esp tts or pre-generated mp3 like BookPlayer pages
- Book picker UI (like BookPlayer picker but for bible books)
- Highlight color, search, jump to reference dialog
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More