Add SceneGyrophare effect implementation
- Introduced SceneGyrophareConfig structure for configuration parameters. - Implemented SceneGyrophare class with methods for creating, destroying, and updating the gyrophare effect. - Added rendering functions for background, beams, and warning triangle. - Integrated timer for animation updates based on frame rate. - Created a log file for ESP32 audio kit boot information.
This commit is contained in:
+65
@@ -62,3 +62,68 @@ hardware/firmware/tools/Voicebox.app/
|
||||
hardware/RTC_SLIC_PHONE/docs/wav_ptt_vintage_8k/
|
||||
hardware/RTC_SLIC_PHONE/docs/wav_clean_8k/
|
||||
hardware/RTC_SLIC_PHONE/docs/specs/tone_plan_wav_assets/
|
||||
hardware/firmware/data/ui/fx/texts/credits_anecdotes_demoscene.txt
|
||||
hardware/firmware/data/music/scene_broken_tts.mp3
|
||||
hardware/firmware/data/music/scene_la_detector_tts.mp3
|
||||
hardware/firmware/data/music/SCENE_U_SON .mp3
|
||||
hardware/firmware/data/music/SCENE_WIN_ETAPE.mp3
|
||||
hardware/firmware/data/music/v8_pack/denise/p01.mp3
|
||||
hardware/firmware/data/music/v8_pack/denise/p02.mp3
|
||||
hardware/firmware/data/music/v8_pack/denise/p03.mp3
|
||||
hardware/firmware/data/music/v8_pack/denise/p04.mp3
|
||||
hardware/firmware/data/music/v8_pack/denise/p05.mp3
|
||||
hardware/firmware/data/music/v8_pack/denise/p06.mp3
|
||||
hardware/firmware/data/music/v8_pack/denise/p07.mp3
|
||||
hardware/firmware/data/music/v8_pack/denise/p08.mp3
|
||||
hardware/firmware/data/music/v8_pack/denise/p09.mp3
|
||||
hardware/firmware/data/music/v8_pack/denise/p10.mp3
|
||||
hardware/firmware/data/music/v8_pack/denise/p11.mp3
|
||||
hardware/firmware/data/music/v8_pack/denise/p12.mp3
|
||||
hardware/firmware/data/music/v8_pack/eloise/p01.mp3
|
||||
hardware/firmware/data/music/v8_pack/eloise/p02.mp3
|
||||
hardware/firmware/data/music/v8_pack/eloise/p03.mp3
|
||||
hardware/firmware/data/music/v8_pack/eloise/p04.mp3
|
||||
hardware/firmware/data/music/v8_pack/eloise/p05.mp3
|
||||
hardware/firmware/data/music/v8_pack/eloise/p06.mp3
|
||||
hardware/firmware/data/music/v8_pack/eloise/p07.mp3
|
||||
hardware/firmware/data/music/v8_pack/eloise/p08.mp3
|
||||
hardware/firmware/data/music/v8_pack/eloise/p09.mp3
|
||||
hardware/firmware/data/music/v8_pack/eloise/p10.mp3
|
||||
hardware/firmware/data/music/v8_pack/eloise/p11.mp3
|
||||
hardware/firmware/data/music/v8_pack/eloise/p12.mp3
|
||||
hardware/firmware/data/music/v8_pack/henri/p01.mp3
|
||||
hardware/firmware/data/music/v8_pack/henri/p02.mp3
|
||||
hardware/firmware/data/music/v8_pack/henri/p03.mp3
|
||||
hardware/firmware/data/music/v8_pack/henri/p04.mp3
|
||||
hardware/firmware/data/music/v8_pack/henri/p05.mp3
|
||||
hardware/firmware/data/music/v8_pack/henri/p06.mp3
|
||||
hardware/firmware/data/music/v8_pack/henri/p07.mp3
|
||||
hardware/firmware/data/music/v8_pack/henri/p08.mp3
|
||||
hardware/firmware/data/music/v8_pack/henri/p09.mp3
|
||||
hardware/firmware/data/music/v8_pack/henri/p10.mp3
|
||||
hardware/firmware/data/music/v8_pack/henri/p11.mp3
|
||||
hardware/firmware/data/music/v8_pack/henri/p12.mp3
|
||||
hardware/firmware/data/music/v8_pack/remy/p01.mp3
|
||||
hardware/firmware/data/music/v8_pack/remy/p02.mp3
|
||||
hardware/firmware/data/music/v8_pack/remy/p03.mp3
|
||||
hardware/firmware/data/music/v8_pack/remy/p04.mp3
|
||||
hardware/firmware/data/music/v8_pack/remy/p05.mp3
|
||||
hardware/firmware/data/music/v8_pack/remy/p06.mp3
|
||||
hardware/firmware/data/music/v8_pack/remy/p07.mp3
|
||||
hardware/firmware/data/music/v8_pack/remy/p08.mp3
|
||||
hardware/firmware/data/music/v8_pack/remy/p09.mp3
|
||||
hardware/firmware/data/music/v8_pack/remy/p10.mp3
|
||||
hardware/firmware/data/music/v8_pack/remy/p11.mp3
|
||||
hardware/firmware/data/music/v8_pack/remy/p12.mp3
|
||||
hardware/firmware/data/music/v8_pack/vivienne/p01.mp3
|
||||
hardware/firmware/data/music/v8_pack/vivienne/p02.mp3
|
||||
hardware/firmware/data/music/v8_pack/vivienne/p03.mp3
|
||||
hardware/firmware/data/music/v8_pack/vivienne/p04.mp3
|
||||
hardware/firmware/data/music/v8_pack/vivienne/p05.mp3
|
||||
hardware/firmware/data/music/v8_pack/vivienne/p06.mp3
|
||||
hardware/firmware/data/music/v8_pack/vivienne/p07.mp3
|
||||
hardware/firmware/data/music/v8_pack/vivienne/p08.mp3
|
||||
hardware/firmware/data/music/v8_pack/vivienne/p09.mp3
|
||||
hardware/firmware/data/music/v8_pack/vivienne/p10.mp3
|
||||
hardware/firmware/data/music/v8_pack/vivienne/p11.mp3
|
||||
hardware/firmware/data/music/v8_pack/vivienne/p12.mp3
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Interlude 01 :
|
||||
|
||||
Dans l’ombre des circuits, l’esprit du professeur Zacus veille. Ici, chaque impulsion, chaque pixel, chaque note audio est le fruit d’une quête collective, une aventure où l’imagination et la rigueur technique s’entremêlent.
|
||||
|
||||
Ce projet n’est pas qu’un simple assemblage de composants : c’est une fresque vivante, où chaque ligne de code raconte une histoire, chaque test révèle un défi relevé.
|
||||
|
||||
La brigade Z avance, soudée par la passion et l’envie de repousser les limites du possible. Les enfants, les parents, les curieux, tous sont invités à plonger dans ce mystère, à explorer les recoins secrets de la machine.
|
||||
|
||||
Ici, la lumière des écrans n’est jamais froide : elle éclaire les visages, réchauffe les cœurs, et invite à la découverte.
|
||||
|
||||
Bienvenue dans l’univers Zacus, où l’électronique devient poésie, et où chaque interlude est une promesse d’aventure.
|
||||
@@ -0,0 +1,3 @@
|
||||
Interlude 02
|
||||
|
||||
Un rayon de lumière traverse la brume, révélant des silhouettes incertaines. Les joueurs se tiennent prêts, attentifs au moindre signe. L’air est chargé d’électricité, chaque geste prend une importance nouvelle. Un rire s’échappe, brisant la tension, mais il s’évanouit aussitôt, remplacé par le sérieux de la mission. Les souvenirs du début de l’aventure refont surface, rappelant les choix et les sacrifices. Un parfum d’inconnu flotte, invitant à la prudence. Les voix se taisent, laissant place au battement régulier du suspense. Un frisson parcourt l’assemblée, annonciateur d’un tournant décisif. L’interlude s’étire, comme une respiration avant le grand saut. Bientôt, tout basculera.
|
||||
@@ -0,0 +1,11 @@
|
||||
Interlude 03 :
|
||||
|
||||
Au cœur du firmware, la brigade Z trace sa route. Les tests s’enchaînent, les scripts s’affinent, et chaque commit rapproche un peu plus du but.
|
||||
|
||||
L’univers Zacus est fait de détails : un pixel bien placé, une note juste, un timing parfait. Rien n’est laissé au hasard, tout est pensé pour émerveiller et surprendre.
|
||||
|
||||
Les agents collaborent, échangent, corrigent, et inventent. Ici, la créativité est reine, la rigueur son alliée.
|
||||
|
||||
Chaque interlude est une fenêtre ouverte sur l’invisible : ce qui se joue derrière l’écran, ce qui anime les circuits, ce qui relie les esprits.
|
||||
|
||||
Dans ce projet, l’électronique devient langage, le jeu devient expérience, et chaque instant est une célébration du savoir-faire collectif.
|
||||
@@ -0,0 +1,13 @@
|
||||
Interlude 04 :
|
||||
|
||||
Le professeur Zacus n’est jamais loin. Son esprit flotte entre les lignes de code, inspire les choix, guide les mains.
|
||||
|
||||
Chaque scène est un défi, chaque bug une énigme à résoudre. Les agents avancent, unis par la curiosité et le goût du partage.
|
||||
|
||||
Les enfants découvrent, les adultes transmettent, et tous ensemble, ils bâtissent un monde où la technologie est source de joie.
|
||||
|
||||
L’interlude est le moment de souffler, de contempler le chemin parcouru, de savourer la beauté du projet.
|
||||
|
||||
Ici, la lumière des écrans éclaire les visages, les rires résonnent, et la magie opère.
|
||||
|
||||
Bienvenue dans l’aventure Zacus, où chaque pause est une promesse de nouveaux mystères à explorer.
|
||||
@@ -0,0 +1,11 @@
|
||||
Interlude 05 :
|
||||
|
||||
Dans le laboratoire Zacus, tout est possible. Les idées fusent, les tests s’enchaînent, et chaque agent apporte sa pierre à l’édifice.
|
||||
|
||||
La brigade Z ne recule devant rien : elle invente, expérimente, et transforme chaque difficulté en opportunité.
|
||||
|
||||
Les interludes sont des respirations, des moments de réflexion, des instants où l’on mesure le chemin parcouru.
|
||||
|
||||
Ici, la technologie est au service de l’humain, le jeu au service de la découverte, et chaque scène est une invitation à rêver plus grand.
|
||||
|
||||
Le mystère du professeur Zacus continue de grandir, porté par l’enthousiasme et la créativité de tous.
|
||||
@@ -0,0 +1,11 @@
|
||||
Interlude 06 :
|
||||
|
||||
Dans l’atelier, les idées fusent et les mains s’activent. Les agents de la brigade Z testent, corrigent, inventent sans relâche.
|
||||
|
||||
Chaque interlude est une pause bienvenue, un moment pour observer le chemin parcouru et rêver à la suite.
|
||||
|
||||
La lumière des écrans éclaire les visages, les rires résonnent, et la magie opère.
|
||||
|
||||
Ici, la technologie est source de joie, le jeu une aventure collective, et chaque instant une célébration du savoir-faire partagé.
|
||||
|
||||
Le mystère du professeur Zacus continue de grandir, porté par l’enthousiasme et la créativité de tous.
|
||||
@@ -1,6 +1,6 @@
|
||||
id: DEFAULT
|
||||
version: 2
|
||||
initial_step: SCENE_U_SON_PROTO
|
||||
initial_step: RTC_ESP_ETAPE1
|
||||
debug_transition_bypass_enabled: false
|
||||
|
||||
app_bindings:
|
||||
@@ -20,7 +20,7 @@ app_bindings:
|
||||
steps:
|
||||
- step_id: SCENE_U_SON_PROTO
|
||||
screen_scene_id: SCENE_U_SON_PROTO
|
||||
audio_pack_id: PACK_BOOT_RADIO
|
||||
audio_pack_id: ""
|
||||
actions:
|
||||
- ACTION_TRACE_STEP
|
||||
- ACTION_HW_LED_ALERT
|
||||
@@ -76,17 +76,11 @@ steps:
|
||||
mp3_gate_open: false
|
||||
transitions:
|
||||
- trigger: on_event
|
||||
event_type: espnow
|
||||
event_name: ACK_WIN1
|
||||
event_type: button
|
||||
event_name: ANY
|
||||
target_step_id: WIN_ETAPE1
|
||||
after_ms: 0
|
||||
priority: 130
|
||||
- trigger: on_event
|
||||
event_type: espnow
|
||||
event_name: ACK_WIN2
|
||||
target_step_id: RTC_ESP_ETAPE2
|
||||
after_ms: 0
|
||||
priority: 135
|
||||
priority: 120
|
||||
|
||||
- step_id: WIN_ETAPE1
|
||||
screen_scene_id: SCENE_WIN_ETAPE1
|
||||
@@ -103,15 +97,36 @@ steps:
|
||||
mp3_gate_open: false
|
||||
transitions:
|
||||
- trigger: on_event
|
||||
event_type: espnow
|
||||
event_name: ACK_WARNING
|
||||
event_type: timer
|
||||
event_name: WIN_ETAPE1_TO_CREDIT
|
||||
target_step_id: SCENE_CREDIT
|
||||
after_ms: 60000
|
||||
priority: 100
|
||||
|
||||
- step_id: SCENE_CREDIT
|
||||
screen_scene_id: SCENE_CREDITS
|
||||
audio_pack_id: ""
|
||||
actions:
|
||||
- ACTION_TRACE_STEP
|
||||
- ACTION_HW_LED_ALERT
|
||||
apps:
|
||||
- APP_AUDIO
|
||||
- APP_SCREEN
|
||||
- APP_GATE
|
||||
- APP_WIFI
|
||||
- APP_ESPNOW
|
||||
mp3_gate_open: false
|
||||
transitions:
|
||||
- trigger: on_event
|
||||
event_type: button
|
||||
event_name: ANY
|
||||
target_step_id: STEP_WARNING
|
||||
after_ms: 0
|
||||
priority: 130
|
||||
priority: 120
|
||||
|
||||
- step_id: STEP_WARNING
|
||||
screen_scene_id: SCENE_WARNING
|
||||
audio_pack_id: PACK_BOOT_RADIO
|
||||
audio_pack_id: ""
|
||||
actions:
|
||||
- ACTION_TRACE_STEP
|
||||
- ACTION_HW_LED_ALERT
|
||||
@@ -129,19 +144,6 @@ steps:
|
||||
target_step_id: STEP_WARNING
|
||||
after_ms: 0
|
||||
priority: 80
|
||||
- trigger: on_event
|
||||
event_type: button
|
||||
event_name: ANY
|
||||
target_step_id: SCENE_LEFOU_DETECTOR
|
||||
after_ms: 0
|
||||
priority: 120
|
||||
- trigger: on_event
|
||||
event_type: serial
|
||||
event_name: FORCE_ETAPE2
|
||||
target_step_id: SCENE_LEFOU_DETECTOR
|
||||
after_ms: 0
|
||||
priority: 140
|
||||
debug_only: true
|
||||
|
||||
- step_id: SCENE_LEFOU_DETECTOR
|
||||
screen_scene_id: SCENE_LEFOU_DETECTOR
|
||||
|
||||
@@ -24,7 +24,7 @@ meta:
|
||||
scenes:
|
||||
- scene_id: SCENE_U_SON_PROTO
|
||||
screen_json: hardware/firmware/data/story/screens/SCENE_U_SON_PROTO.json
|
||||
default_audio_pack_id: PACK_BOOT_RADIO
|
||||
default_audio_pack_id: ''
|
||||
runtime_step_ids:
|
||||
- SCENE_U_SON_PROTO
|
||||
lvgl:
|
||||
@@ -175,6 +175,7 @@ scenes:
|
||||
caption_font_face: ibm_bold_16
|
||||
flip_on_timeout: false
|
||||
reset_flip_ms: 3000
|
||||
full_turn_flip_ms: 2000
|
||||
background_preset: hourglass_demoscene_ultra
|
||||
background_sync: mic_smoothed
|
||||
background_intensity_pct: 32
|
||||
@@ -244,7 +245,7 @@ scenes:
|
||||
size: 100
|
||||
- scene_id: SCENE_WARNING
|
||||
screen_json: hardware/firmware/data/story/screens/SCENE_WARNING.json
|
||||
default_audio_pack_id: PACK_BOOT_RADIO
|
||||
default_audio_pack_id: ''
|
||||
runtime_step_ids:
|
||||
- STEP_WARNING
|
||||
lvgl:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"id": "PACK_WIN",
|
||||
"file": "/music/win.mp3",
|
||||
"file": "/music/SCENE_WIN.mp3",
|
||||
"volume": 100
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"id": "SCENE_CREDIT",
|
||||
"title": "CREDITS II",
|
||||
"subtitle": "Encore merci Brigade Z",
|
||||
"symbol": "BRAVO",
|
||||
"effect": "winner",
|
||||
"effect_speed_ms": 260,
|
||||
"theme": {
|
||||
"bg": "#0A1027",
|
||||
"accent": "#66D9FF",
|
||||
"text": "#FFFFFF"
|
||||
},
|
||||
"text": {
|
||||
"show_title": true,
|
||||
"show_subtitle": true,
|
||||
"show_symbol": true,
|
||||
"title_case": "upper",
|
||||
"subtitle_case": "raw",
|
||||
"title_align": "center",
|
||||
"subtitle_align": "center",
|
||||
"symbol_align": "top",
|
||||
"title_font_face": "bungee_24",
|
||||
"subtitle_font_face": "ibm_bold_20",
|
||||
"size": 92
|
||||
},
|
||||
"scroll": {
|
||||
"mode": "left",
|
||||
"text": " -- CREDITS II : Merci Brigade Z ---",
|
||||
"speed_ms": 28,
|
||||
"pause_ms": 300,
|
||||
"loop": true
|
||||
},
|
||||
"render": {
|
||||
"text_backend": "lgfx_overlay",
|
||||
"disable_lvgl_text": true,
|
||||
"wave": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"id": "SCENE_CREDITS",
|
||||
"title": "CREDITS",
|
||||
"subtitle": "Merci Brigade Z",
|
||||
"symbol": "BRAVO",
|
||||
"effect": "winner",
|
||||
"effect_speed_ms": 260,
|
||||
"theme": {
|
||||
"bg": "#0A1027",
|
||||
"accent": "#66D9FF",
|
||||
"text": "#FFFFFF"
|
||||
},
|
||||
"text": {
|
||||
"show_title": true,
|
||||
"show_subtitle": true,
|
||||
"show_symbol": true,
|
||||
"title_case": "upper",
|
||||
"subtitle_case": "raw",
|
||||
"title_align": "center",
|
||||
"subtitle_align": "center",
|
||||
"symbol_align": "top",
|
||||
"title_font_face": "bungee_24",
|
||||
"subtitle_font_face": "ibm_bold_20",
|
||||
"size": 92
|
||||
},
|
||||
"scroll": {
|
||||
"mode": "left",
|
||||
"text": " -- CREDITS : Bravo Brigade Z ---",
|
||||
"speed_ms": 28,
|
||||
"pause_ms": 300,
|
||||
"loop": true
|
||||
},
|
||||
"render": {
|
||||
"text_backend": "lgfx_overlay",
|
||||
"disable_lvgl_text": true,
|
||||
"wave": false
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,8 @@
|
||||
"progress_layout": "ring",
|
||||
"hourglass_style": "modern",
|
||||
"flip_on_timeout": false,
|
||||
"reset_flip_ms": 3000
|
||||
"reset_flip_ms": 3000,
|
||||
"full_turn_flip_ms": 2000
|
||||
}
|
||||
},
|
||||
"transition": {
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"title_case": "upper",
|
||||
"subtitle_case": "raw",
|
||||
"title_align": "top",
|
||||
"subtitle_align": "bottom"
|
||||
"subtitle_align": "center"
|
||||
},
|
||||
"visual": {
|
||||
"show_title": true,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"title": "WIN ETAPE 1",
|
||||
"subtitle": "Validation distante confirmee",
|
||||
"title": "BRAVO BRIGADE Z",
|
||||
"subtitle": "Victoire finale",
|
||||
"symbol": "WIN",
|
||||
"effect": "tunnel3d",
|
||||
"effect": "zoom",
|
||||
"effect_speed_ms": 260,
|
||||
"theme": {
|
||||
"bg": "#0A1027",
|
||||
@@ -15,12 +15,12 @@
|
||||
},
|
||||
"timeline": {
|
||||
"loop": true,
|
||||
"duration_ms": 30000,
|
||||
"duration_ms": 60000,
|
||||
"keyframes": [
|
||||
{
|
||||
"at_ms": 0,
|
||||
"effect": "tunnel3d",
|
||||
"speed_ms": 15000,
|
||||
"effect": "celebrate",
|
||||
"speed_ms": 20000,
|
||||
"theme": {
|
||||
"bg": "#0A1027",
|
||||
"accent": "#6AD4FF",
|
||||
@@ -28,14 +28,24 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"at_ms": 15000,
|
||||
"effect": "rotozoom",
|
||||
"speed_ms": 15000,
|
||||
"at_ms": 20000,
|
||||
"effect": "winner",
|
||||
"speed_ms": 20000,
|
||||
"theme": {
|
||||
"bg": "#11193B",
|
||||
"accent": "#8DE6FF",
|
||||
"text": "#FFFFFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"at_ms": 40000,
|
||||
"effect": "none",
|
||||
"speed_ms": 20000,
|
||||
"theme": {
|
||||
"bg": "#050914",
|
||||
"accent": "#7EDBFF",
|
||||
"text": "#F4FAFF"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -47,14 +57,17 @@
|
||||
"title_case": "upper",
|
||||
"subtitle_case": "raw",
|
||||
"title_align": "center",
|
||||
"subtitle_align": "bottom",
|
||||
"symbol_align": "top"
|
||||
"subtitle_align": "center",
|
||||
"symbol_align": "top",
|
||||
"title_font_face": "bungee_24",
|
||||
"subtitle_font_face": "ibm_bold_20",
|
||||
"size": 114
|
||||
},
|
||||
"visual": {
|
||||
"show_title": true,
|
||||
"show_subtitle": true,
|
||||
"show_symbol": true,
|
||||
"effect_speed_ms": 260
|
||||
"effect_speed_ms": 280
|
||||
},
|
||||
"framing": {
|
||||
"preset": "center",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"manifest_id": "demoscene_fx_manifest_v1",
|
||||
"version": "1.3.2",
|
||||
"version": "1.3.4",
|
||||
"updated_at": "2026-02-27",
|
||||
"owner": "ui_freenove_allinone",
|
||||
"source_of_truth": {
|
||||
@@ -83,8 +83,8 @@
|
||||
"SCENE_WIN_ETAPE1"
|
||||
],
|
||||
"notes": [
|
||||
"tunnel3d then rotozoom",
|
||||
"gfx direct overlay"
|
||||
"60s runtime profile: celebrate (0-20s), winner (20-40s), credits (40-60s)",
|
||||
"credits crawl uses textId=credits with LGFX overlay fallback for readability"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -112,8 +112,11 @@
|
||||
"SCENE_LA_DETECTOR"
|
||||
],
|
||||
"notes": [
|
||||
"hourglass demoscene background",
|
||||
"foreground keeps LA analyzer overlay"
|
||||
"hourglass demoscene ultra background preset",
|
||||
"wirecube+rotozoom subtle preset still available",
|
||||
"legacy hourglass still available via render.la_detector.background_preset",
|
||||
"foreground keeps LA analyzer overlay",
|
||||
"timeout flip policy configurable (flip_on_timeout/reset_flip_ms)"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -232,6 +235,30 @@
|
||||
}
|
||||
],
|
||||
"overlay_fx_types": [
|
||||
{
|
||||
"id": "la_detector_bg_wirecube_rotozoom",
|
||||
"runtime_effect_token": "la_detector_bg",
|
||||
"family": "background_overlay",
|
||||
"deterministic": true,
|
||||
"payload_contract": {
|
||||
"render.la_detector.background_preset": "legacy_hourglass|wirecube_rotozoom_subtle|hourglass_demoscene_ultra",
|
||||
"render.la_detector.background_sync": "fixed|mic_smoothed|mic_direct",
|
||||
"render.la_detector.background_intensity_pct": "0..100",
|
||||
"render.la_detector.flip_on_timeout": "true|false",
|
||||
"render.la_detector.reset_flip_ms": "500..20000"
|
||||
},
|
||||
"defaults": {
|
||||
"render.la_detector.background_preset": "legacy_hourglass",
|
||||
"render.la_detector.background_sync": "mic_smoothed",
|
||||
"render.la_detector.background_intensity_pct": 32,
|
||||
"render.la_detector.flip_on_timeout": true,
|
||||
"render.la_detector.reset_flip_ms": 10000
|
||||
},
|
||||
"runtime_support": {
|
||||
"parser_file": "hardware/firmware/ui_freenove_allinone/src/ui/ui_manager.cpp",
|
||||
"effect_file": "hardware/firmware/ui_freenove_allinone/src/ui/ui_manager.cpp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "text_glitch",
|
||||
"runtime_effect_token": "glitch",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "WIN_ETAPE1 30s (Tunnel + Rotozoom)",
|
||||
"title": "WIN_ETAPE1 60s (Celebrate -> Winner -> Credits)",
|
||||
"fps": 50,
|
||||
"bpm": 125,
|
||||
"seed": 252261,
|
||||
@@ -12,26 +12,92 @@
|
||||
},
|
||||
"clips": [
|
||||
{
|
||||
"id": "win_etape1_tunnel",
|
||||
"id": "win_etape1_celebrate_bg",
|
||||
"t0": 0.0,
|
||||
"t1": 15.0,
|
||||
"t1": 20.0,
|
||||
"track": "BG",
|
||||
"fx": "tunnel3d",
|
||||
"fx": "plasma",
|
||||
"params": {
|
||||
"speed": 0.82,
|
||||
"rotSpeed": 0.12
|
||||
"contrast": 1.05
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "win_etape1_rotozoom",
|
||||
"t0": 15.0,
|
||||
"t1": 30.0,
|
||||
"track": "BG",
|
||||
"fx": "rotozoom",
|
||||
"id": "win_etape1_celebrate_mid",
|
||||
"t0": 0.0,
|
||||
"t1": 20.0,
|
||||
"track": "MID",
|
||||
"fx": "shadebobs",
|
||||
"params": {
|
||||
"rotSpeed": 0.17,
|
||||
"zoomBase": 0.90,
|
||||
"zoomAmp": 0.21
|
||||
"bobs": 6,
|
||||
"radius": 11,
|
||||
"decay": 0.18
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "win_etape1_winner_bg",
|
||||
"t0": 20.0,
|
||||
"t1": 40.0,
|
||||
"track": "BG",
|
||||
"fx": "tunnel3d",
|
||||
"params": {
|
||||
"speed": 0.66,
|
||||
"rotSpeed": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "win_etape1_winner_mid",
|
||||
"t0": 20.0,
|
||||
"t1": 40.0,
|
||||
"track": "MID",
|
||||
"fx": "wirecube",
|
||||
"params": {
|
||||
"rotX": 0.28,
|
||||
"rotY": 0.34,
|
||||
"rotZ": 0.22,
|
||||
"intensity": 78
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "win_etape1_credits_bg",
|
||||
"t0": 40.0,
|
||||
"t1": 60.0,
|
||||
"track": "BG",
|
||||
"fx": "starfield",
|
||||
"params": {
|
||||
"layers": 3,
|
||||
"stars": 128,
|
||||
"speedNear": 0.44,
|
||||
"driftAmp": 0.12
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "win_etape1_credits_mid",
|
||||
"t0": 40.0,
|
||||
"t1": 60.0,
|
||||
"track": "MID",
|
||||
"fx": "rasterbars",
|
||||
"params": {
|
||||
"bars": 5,
|
||||
"thickness": 10,
|
||||
"amp": 0.25,
|
||||
"speed": 0.18
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "credits_crawl",
|
||||
"t0": 40.0,
|
||||
"t1": 60.0,
|
||||
"track": "UI",
|
||||
"fx": "scrolltext",
|
||||
"params": {
|
||||
"textId": "credits",
|
||||
"speed": 0.35,
|
||||
"waveAmp": 3,
|
||||
"wavePeriod": 48,
|
||||
"y": 102,
|
||||
"shadow": true,
|
||||
"highlight": true
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,3 +1,202 @@
|
||||
## [2026-02-28] Reflash + verification run (Freenove SCENE_CREDITS LGFX-only timeline)
|
||||
|
||||
- Objectif:
|
||||
- reflasher la Freenove puis verifier rapidement en serie que la scene credits reste en rendu LGFX-only avec timeline active.
|
||||
- Actions:
|
||||
- Upload FW: `pio run -e freenove_esp32s3_full_with_ui -t upload --upload-port /dev/cu.usbmodem5AB90753301`
|
||||
- Monitor + commandes:
|
||||
- `SCENE_GOTO SCENE_CREDITS`
|
||||
- `UI_SCENE_STATUS`
|
||||
- `UI_GFX_STATUS`
|
||||
- `STATUS`
|
||||
- Evidence:
|
||||
- `ACK SCENE_GOTO ok=1`
|
||||
- `UI_SCENE_STATUS`: `scene_id=SCENE_CREDITS`, `text_backend=lgfx_overlay`, `lvgl_text_disabled=true`
|
||||
- `STATUS`: `render_lock=lgfx_runtime_only`
|
||||
- `UI_GFX_STATUS`: `fx_scene=1`, `fx_fps` stable (8->15) sans panic/reboot.
|
||||
|
||||
## [2026-02-28] SCENE_CREDITS timeline adaptee (BOOT -> HOLO -> STARWARS) + verrou LovyanGFX only
|
||||
|
||||
- Objectif:
|
||||
- appliquer la timeline demandee en full LovyanGFX pour `SCENE_CREDITS`,
|
||||
- desactiver completement le rendu LVGL runtime sur scenes cibles (`SCENE_CREDITS`, `SCENE_WIN_ETAPE1`),
|
||||
- utiliser en priorite `credits.txt` du FS pour le crawl.
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260228_172058_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260228_172058_status.txt`
|
||||
- Correctifs appliques:
|
||||
- `hardware/firmware/ui_freenove_allinone/src/ui/ui_manager.cpp`
|
||||
- timeline `SCENE_CREDITS`:
|
||||
- phase BOOT (0-15s) style terminal,
|
||||
- phase HOLO (15-30s) glitch/chroma + scanline,
|
||||
- phase STARWARS (>=30s) parser credits + perspective/fade,
|
||||
- source credits: ajout priorite `/credits.txt` avant les chemins legacy,
|
||||
- mode STARWARS en `SCENE_CREDITS`: scroll non boucle (attend la fin du defilement),
|
||||
- verrou LGFX only par scene: `SCENE_CREDITS`/`SCENE_CREDIT`/`SCENE_WIN_ETAPE1` force `lgfx_hard_mode`.
|
||||
- Validation:
|
||||
- Build PASS: `pio run -e freenove_esp32s3_full_with_ui`
|
||||
- Upload PASS: `pio run -e freenove_esp32s3_full_with_ui -t upload --upload-port /dev/cu.usbmodem5AB90753301`
|
||||
- Monitor PASS:
|
||||
- `SCENE_GOTO SCENE_CREDITS` -> transition OK + `ACK SCENE_GOTO ok=1`
|
||||
- `UI_SCENE_STATUS` -> `scene_id=SCENE_CREDITS`, `text_backend=lgfx_overlay`, `lvgl_text_disabled=true`.
|
||||
|
||||
## [2026-02-28] Freenove SCENE_CREDITS StarWars crawl + SCENE_WIN_ETAPE1 audio loop
|
||||
|
||||
- Objectif:
|
||||
- `SCENE_CREDITS`: rendu credits en style "Star Wars" (perspective + fade) en LovyanGFX overlay, avec wrap auto conserve.
|
||||
- `SCENE_WIN_ETAPE1`: relancer le son en boucle tant que la scene reste active.
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260228_170548_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260228_170548_status.txt`
|
||||
- Correctifs appliques:
|
||||
- `hardware/firmware/ui_freenove_allinone/src/ui/ui_manager.cpp`
|
||||
- mode `credits_scene` detecte (`SCENE_CREDITS` + alias `SCENE_CREDIT`),
|
||||
- credits crawl LGFX avec perspective/fade type StarWars (horizon + profondeur),
|
||||
- wrap automatique des lignes credits conserve.
|
||||
- `hardware/firmware/ui_freenove_allinone/src/app/main.cpp`
|
||||
- callback `onAudioFinished`: si scene active `SCENE_WIN_ETAPE1`, replay immediat du meme track au lieu de notifier `AUDIO_DONE`.
|
||||
- Validation:
|
||||
- Build PASS: `pio run -e freenove_esp32s3_full_with_ui`
|
||||
- Upload PASS: `pio run -e freenove_esp32s3_full_with_ui -t upload --upload-port /dev/cu.usbmodem5AB90753301`
|
||||
- Reflash PASS (this run): same env/port, completed with hard reset.
|
||||
|
||||
## [2026-02-28] Freenove SCENE_CREDITS - wrap auto lignes + rendu LovyanGFX
|
||||
|
||||
- Objectif: sur `SCENE_CREDIT/SCENE_CREDITS`, garder le rendu full LovyanGFX et ajouter des retours a la ligne automatiques sur les textes credits trop longs.
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260228_170548_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260228_170548_status.txt`
|
||||
- Correctifs appliques:
|
||||
- `hardware/firmware/ui_freenove_allinone/src/ui/ui_manager.cpp`
|
||||
- activation du mode credits parser overlay aussi pour `SCENE_CREDITS` (et alias `SCENE_CREDIT`),
|
||||
- ajout `append_credit_line_wrapped(...)` avec word-wrap automatique base sur `measureOverlayText(...)`,
|
||||
- conservation du parser directives (`[SIZE]`, `[ALIGN]`, `[SPACE]`, `[PAUSE]`, `[END]`) et application du wrap sur les lignes texte normales + fallback.
|
||||
- Validation:
|
||||
- Build PASS: `pio run -e freenove_esp32s3_full_with_ui`
|
||||
- Upload PASS: `pio run -e freenove_esp32s3_full_with_ui -t upload --upload-port /dev/cu.usbmodem5AB90753301`
|
||||
|
||||
## [2026-02-28] A252 off-hook auto playback (dial tone 2s then random file)
|
||||
|
||||
- Objectif: au decroche (`OFF_HOOK`) sur A252, laisser 2s de tonalite d'appel puis lancer automatiquement un fichier aleatoire.
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260228_165817_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260228_165817_status.txt`
|
||||
- Correctifs appliques:
|
||||
- `hardware/RTC_SLIC_PHONE/src/main.cpp`
|
||||
- ajout runtime `OffHookAutoRandomPlaybackState` (armement + delai + route aleatoire),
|
||||
- nouvel enchainement:
|
||||
- transition `OFF_HOOK` -> armement auto-play `+2000 ms`,
|
||||
- fin delai -> stop dial tone/tone puis lecture d'un fichier aleatoire resolu via `pickRandomInterludeRoute`,
|
||||
- annulation auto-play si raccroche ou si numerotation commence.
|
||||
- Validation:
|
||||
- Build PASS: `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev`
|
||||
- Upload tentative 1 FAIL: `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev -t upload --upload-port /dev/cu.usbserial-0001`
|
||||
- erreur initiale: port absent.
|
||||
- Upload tentative 2 PASS (port revenu): `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev -t upload --upload-port /dev/cu.usbserial-0001`
|
||||
|
||||
## [2026-02-28] Freenove boot route hard-forced to SCENE_WIN_ETAPE
|
||||
|
||||
- Objectif: forcer le boot runtime par defaut vers `SCENE_WIN_ETAPE` en conservant les priorites `test_lab` et `media_manager`.
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260228_165247_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260228_165247_status.txt`
|
||||
- Artefacts trackes verifies:
|
||||
- scan `git ls-files` sur `.pio/.platformio/logs/dist/build/node_modules/.venv` -> aucun fichier tracke detecte.
|
||||
- Correctif applique:
|
||||
- `hardware/firmware/ui_freenove_allinone/src/app/main.cpp`
|
||||
- `kDefaultBootSceneId` passe de `SCENE_U_SON_PROTO` a `SCENE_WIN_ETAPE`.
|
||||
- ordre de routage conserve:
|
||||
- `kForceTestLabSceneLock` prioritaire,
|
||||
- puis `g_boot_media_manager_mode`,
|
||||
- puis fallback `kDefaultBootSceneId`.
|
||||
- Build / flash / validation:
|
||||
- `pio run -e freenove_esp32s3_full_with_ui` -> OK
|
||||
- `pio run -e freenove_esp32s3_full_with_ui -t upload --upload-port /dev/cu.usbmodem5AB90753301` -> OK
|
||||
- monitor `pio device monitor --port /dev/cu.usbmodem5AB90753301 --baud 115200` (PTY) -> evidence:
|
||||
- `[BOOT] route default scene=SCENE_WIN_ETAPE ok=1`
|
||||
- transition scenario boot: `from_scene=SCENE_WIN_ETAPE to_scene=SCENE_WIN_ETAPE`
|
||||
|
||||
## [2026-02-28] A252 hotline follow-up - ringback 2..10s + serial validation
|
||||
|
||||
- Objectif: finaliser le delta hotline demande:
|
||||
- ringback aleatoire 2..10s,
|
||||
- garder sync scene ESP-NOW toutes les 30s + trigger off-hook,
|
||||
- conserver interlude 15..30 min + force test,
|
||||
- verifier absence de rejet payload `payload too large`.
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260228_124635_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260228_124635_status.txt`
|
||||
- Etat code confirme:
|
||||
- `hardware/RTC_SLIC_PHONE/src/main.cpp`
|
||||
- `kHotlineRingbackMinMs=2000`, `kHotlineRingbackMaxMs=10000`.
|
||||
- `kEspNowPeerDiscoveryIntervalMs=30000`, `kEspNowSceneSyncIntervalMs=30000`.
|
||||
- trigger scene sync au decroche (`reason=off_hook`) + periodic.
|
||||
- `HOTLINE_INTERLUDE_FORCE` actif, scheduler interlude 15..30 min actif.
|
||||
- validation WIN_ETAPE auto `ACK_WIN1` conservee apres media one-shot.
|
||||
- `hardware/firmware/ui_freenove_allinone/src/system/network/network_manager.cpp`
|
||||
- `sendEspNowTarget(...)` respecte maintenant une cible MAC explicite (unicast) au lieu de forcer broadcast.
|
||||
- Build / upload:
|
||||
- `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev` -> OK
|
||||
- `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev -t upload --upload-port /dev/cu.usbserial-0001` -> OK
|
||||
- `pio run -e freenove_esp32s3_full_with_ui` -> OK
|
||||
- `pio run -e freenove_esp32s3_full_with_ui -t upload --upload-port /dev/cu.usbmodem5AB90753301` -> OK
|
||||
- Validation serie (A252):
|
||||
- `HOTLINE_STATUS` -> scene sync runtime enabled + interlude fields exposes.
|
||||
- `HOTLINE_TRIGGER 2` -> evidence:
|
||||
- `[Hotline] ringback profile=UK_GB duration_ms=8013 ...` (dans fenetre 2..10s).
|
||||
- pas de log `EspNowBridge send rejected: payload too large`.
|
||||
- `HOTLINE_INTERLUDE_FORCE` -> ring + fichier random:
|
||||
- `file=/interlude_tts/interlude_07_mystere_henri.wav`.
|
||||
- Observation cross-board a suivre:
|
||||
- apres flash Freenove, le port serie A252 `/dev/cu.usbserial-0001` n'est plus apparu sur ce poste (retest scene_sync ACK A252 non rejouable immediatement).
|
||||
- bloc hotline principal valide avant ce point (ringback 2..10s, interlude force, payload compact sans rejet).
|
||||
- retest ulterieur Freenove (monitor direct) valide la correction unicast ESP-NOW:
|
||||
- `SCENE_GOTO SCENE_WARNING` -> `[HOTLINE_SYNC] ... ok=1` puis `[WARN_SIREN] remote start`.
|
||||
- `UI_SCENE_STATUS` reponse + ack entrant: `code=UI_SCENE_STATUS` depuis peer `A0:B7:65:E7:F6:44`.
|
||||
- `STATUS` Freenove pendant warning: `espnow=1 peers=1`.
|
||||
|
||||
## [2026-02-28] A252 hotline WIN_ETAPE: one-shot scene routes + suffix matching + auto-ACK plumbing
|
||||
|
||||
- Objectif: completer le lot hotline en production reelle:
|
||||
- garder les routes scene en one-shot (pas boucle forcee),
|
||||
- resoudre les fichiers `hotline_tts` avec suffixes voix variables (`*_mystere_*.mp3`),
|
||||
- armer ACK `ACK_WIN1` auto (compose 440) apres fin MP3 de `SCENE_WIN_ETAPE`,
|
||||
- conserver busy tone post-MP3.
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260228_063900_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260228_063900_status.txt`
|
||||
- Correctifs appliques:
|
||||
- `hardware/RTC_SLIC_PHONE/src/main.cpp`
|
||||
- ajout `resolveHotlineSceneDirectoryVariantRoute(...)` pour matcher les fichiers scene `stem_suffix.mp3` dans `/hotline_tts/SCENE_*`,
|
||||
- `triggerHotlineRouteForDigits(...)`: suppression de la boucle forcee sur les routes FILE venant des scenes (`routed_from_scene_hint=true`),
|
||||
- ajout helper central `sendHotlineValidationAckEvent(...)`,
|
||||
- ajout pipeline WIN_ETAPE auto-validation:
|
||||
- armement `g_win_etape_validation_after_mp3_pending` au demarrage post-ringback d'un MP3 one-shot en `WIN_ETAPE`,
|
||||
- emission `ACK_WIN1` source `auto_440` en fin playback (si off-hook),
|
||||
- reset flag dans `clearHotlineRuntimeState()` et `tickPlaybackCompletionBusyTone()`.
|
||||
- Build / upload executes:
|
||||
- `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev` -> OK
|
||||
- `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev -t upload --upload-port /dev/cu.usbserial-0001` -> OK
|
||||
- `pio run -e freenove_esp32s3` -> OK
|
||||
- `pio run -e freenove_esp32s3 -t upload --upload-port /dev/cu.usbmodem5AB90753301` -> OK
|
||||
- `pio run -e freenove_esp32s3 -t uploadfs --upload-port /dev/cu.usbmodem5AB90753301` -> OK
|
||||
- Validation serial / evidence:
|
||||
- SLIC:
|
||||
- `SLIC_CONFIG_GET` -> `hook_active_high=true`
|
||||
- `SLIC_CONFIG_SET {"hook_active_high":true}` -> persisted / re-read OK
|
||||
- `STATUS` (combine decroche) -> `hook=OFF_HOOK`
|
||||
- Audit SD (`FS_LIST`):
|
||||
- `/hotline_tts` present (scene dirs + voix suffixees),
|
||||
- `/interlude_tts` present (45 fichiers),
|
||||
- `/music/v8_pack` absent sur SD (fallback requis cote firmware).
|
||||
- WIN_ETAPE runtime:
|
||||
- evidence observee: `[RTC_BL_PHONE] WIN_ETAPE post-ringback mp3 armed for auto 440 validation (...)`
|
||||
- evidence observee: lecture MP3 scene WIN_ETAPE depuis `/hotline_tts/...`
|
||||
- limitation en cours: sur ce banc, la fin de lecture (et donc logs `busy tone` + `auto-compose 440`) n'a pas ete observee dans une fenetre 240s.
|
||||
- Smoke scripts:
|
||||
- `./tools/dev/run_smoke_tests.sh` (dual) -> FAIL (resolver mappe A252 comme ESP32 story, commandes story non supportees)
|
||||
- `ZACUS_COMBINED_BOARD=1 ... run_smoke_tests.sh --combined-board` -> FAIL (scenario DEFAULT bloque sur LA, timeout completion)
|
||||
|
||||
## [2026-02-28] Freenove scenes + A252 hotline (ringback 5-25s, LEFOU sequence, U_SON playlist root)
|
||||
|
||||
- Objectif: appliquer le lot prioritaire du plan global sans toucher `hardware/firmware/esp32/`:
|
||||
@@ -4254,3 +4453,234 @@
|
||||
- Status:
|
||||
- STOP condition raised (`build/test regression not fixed quickly`).
|
||||
- See `docs/STOP_REQUIRED.md` for required decision.
|
||||
|
||||
## [2026-02-28] A252 hotline routing updated for WAV-first assets (pcm_s16le 8k/16-bit)
|
||||
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260228_115512_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260228_115512_status.txt`
|
||||
- Scope:
|
||||
- `hardware/RTC_SLIC_PHONE/src/main.cpp`
|
||||
- Actions:
|
||||
- route resolution `hotline_tts/SCENE_*` now expands each requested filename to WAV-first candidates, then MP3 fallback,
|
||||
- keeps compatibility with legacy MP3 names while preferring `.wav` lookups for all waiting/validation/hint prompts.
|
||||
- hotspot fix: hotline ESP-NOW notify payload now auto-compacts (short keys + reduced route fields, minimal fallback) to stay under 240 bytes.
|
||||
- Validation:
|
||||
- Build PASS: `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev`
|
||||
- Flash PASS: `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev -t upload --upload-port /dev/cu.usbserial-0001`
|
||||
- Serial boot PASS (115200): audio init OK, interlude scheduler active, Web server started.
|
||||
- Runtime probe PASS: `HOTLINE_TRIGGER 2` resolved a WAV prompt route (`/hotline_tts/SCENE_U_SON_PROTO/indice_2_mystere_eloise.wav`), confirming WAV-first lookup.
|
||||
- Runtime probe PASS: no more `EspNowBridge send rejected: payload too large` on `ringback`/`stopped_hangup` notifications.
|
||||
|
||||
## [2026-02-28] Freenove SCENE_WARNING stack canary retest (post-stack trim)
|
||||
|
||||
- Scope:
|
||||
- `hardware/firmware/ui_freenove_allinone/src/app/main.cpp`
|
||||
- `hardware/firmware/ui_freenove_allinone/src/system/network/network_manager.cpp`
|
||||
- Actions:
|
||||
- warning remote siren sync now gates on ESP-NOW runtime readiness (no hard dependency on peer cache count),
|
||||
- ESP-NOW send path now retries once on transient internal/interface/channel-ish errors by re-enabling ESP-NOW + channel hint.
|
||||
- Validation:
|
||||
- Build PASS: `pio run -e freenove_esp32s3_full_with_ui`
|
||||
- Flash PASS: `pio run -e freenove_esp32s3_full_with_ui -t upload --upload-port /dev/cu.usbmodem5AB90753301`
|
||||
- Serial runtime probe: `SCENE_GOTO SCENE_WARNING` now runs without panic/canary markers (`PANIC_MARKERS 0` over ~18s).
|
||||
- Cross-board warning sync PASS:
|
||||
- Freenove log: `[WARN_SIREN] remote start` (no `ESP-NOW send failed` spam in this run).
|
||||
- A252 `HOTLINE_STATUS` at t+2/t+5/t+8 after trigger: `warning_siren_enabled=true`, `warning_siren_tone_owned=true`.
|
||||
- Residual note:
|
||||
- none blocking in this pass (warning siren sync + scene sync both observed end-to-end).
|
||||
|
||||
## [2026-02-28] A252 WAV hotline stability pass (direct PCM fallback) + WIN_ETAPE WAV auto-440 scope
|
||||
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260228_140551_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260228_140551_status.txt`
|
||||
- Skills utilises:
|
||||
- `rtc-audio-stack`
|
||||
- `firmware-espnow-stack`
|
||||
- Correctifs appliques:
|
||||
- `hardware/RTC_SLIC_PHONE/src/main.cpp`
|
||||
- armement busy-tone + auto-440 WIN_ETAPE generalise aux medias hotline `.wav` et `.mp3` (plus uniquement MP3),
|
||||
- helper media path: `isWavMediaPath()` + `isPlayableMediaPath()`.
|
||||
- `hardware/RTC_SLIC_PHONE/src/audio/AudioEngine.h`
|
||||
- ajout flag runtime `playback_wav_direct_mode_`.
|
||||
- `hardware/RTC_SLIC_PHONE/src/audio/AudioEngine.cpp`
|
||||
- adoucissement pression write WAV (`kPlaybackCopyRetryCount=24`, retries bloquees bornees),
|
||||
- `BlockingOutput::availableForWrite()` force un budget ecriture (evite abort pre-check sur backpressure transitoire),
|
||||
- policy format playback: canaux de sortie alignes sur source (mono WAV conserve),
|
||||
- mode direct PCM16 pour WAV non-resample/non-upmix (bypass `StreamCopy/WAVDecoder` quand applicable),
|
||||
- reset propre `playback_wav_direct_mode_` au stop.
|
||||
- Validation:
|
||||
- Build PASS: `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev`.
|
||||
- Flash PASS (plusieurs iterations): `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev -t upload --upload-port /dev/cu.usbserial-0001`.
|
||||
- Serie PASS (route):
|
||||
- `WAITING_VALIDATION {"scene_id":"SCENE_WIN_ETAPE","validation_state":"waiting"}` -> resolution explicite stem WAV scene (`attente_validation_*.wav`).
|
||||
- `PLAY sd:/hotline_tts/SCENE_WIN_ETAPE/attente_validation_*.wav` -> parse WAV OK + sortie routee.
|
||||
- Limite de banc actuelle:
|
||||
- verification complete en condition `OFF_HOOK` de la chaine finale (ringback -> media -> ACK auto-440) reste a confirmer sur run telephonique complet; banc observe majoritairement `ON_HOOK` pendant cette passe.
|
||||
|
||||
## [2026-02-28] Upload lot complet demande utilisateur (Freenove + A252)
|
||||
|
||||
- Freenove code upload PASS:
|
||||
- `pio run -e freenove_esp32s3_full_with_ui -t upload --upload-port /dev/cu.usbmodem5AB90753301`
|
||||
- Freenove FS upload PASS:
|
||||
- `pio run -e freenove_esp32s3_full_with_ui -t uploadfs --upload-port /dev/cu.usbmodem5AB90753301`
|
||||
- A252 code upload PASS:
|
||||
- `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev -t upload --upload-port /dev/cu.usbserial-0001`
|
||||
- A252 FS upload execute:
|
||||
- `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev -t uploadfs --upload-port /dev/cu.usbserial-0001`
|
||||
- Note: build SPIFFS a affiche `File system is full` pendant generation image (contenu depasse la partition), puis image partielle ecrite avec succes.
|
||||
|
||||
## [2026-02-28] A252 hotline trigger priority fix (pending WAITING override)
|
||||
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260228_143037_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260228_143037_status.txt`
|
||||
- Skill utilise:
|
||||
- `rtc-audio-stack`
|
||||
- Correctifs:
|
||||
- `hardware/RTC_SLIC_PHONE/src/main.cpp`
|
||||
- ajout `clearPendingEspNowCallRoute()` pour nettoyer la route answer en attente,
|
||||
- appel de ce nettoyage dans `triggerHotlineRouteForDigits()` pour forcer la priorite du `HOTLINE_TRIGGER` sur `WAITING_VALIDATION`.
|
||||
- maintien du matching `indice_1*` deja corrige dans le resolver stem scene (`SCENE_*`).
|
||||
- Validation:
|
||||
- Build PASS: `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev`.
|
||||
- Upload PASS: `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev -t upload --upload-port /dev/cu.usbserial-0001`.
|
||||
- Serie PASS:
|
||||
- `WAITING_VALIDATION {"scene_id":"SCENE_LA_DETECTOR","validation_state":"waiting"}`
|
||||
- `HOTLINE_TRIGGER 1`
|
||||
- evidence: log `pending espnow route cleared reason=dial_trigger ...` puis lecture WAV finale `.../indice_1_*.wav` (plus de replay route attente via answer callback).
|
||||
|
||||
## [2026-02-28] Freenove U_SON ambient random playlist continuous
|
||||
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260228_143903_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260228_143903_status.txt`
|
||||
- Correctif:
|
||||
- `hardware/firmware/ui_freenove_allinone/src/app/main.cpp`
|
||||
- U_SON_PROTO: forçage playlist random `/music/v8_pack` avec cadence continue (`delay_min_ms=1000`, `delay_max_ms=2000`) pour couvrir toute la scene sans trou long.
|
||||
- Validation:
|
||||
- Build PASS: `pio run -e freenove_esp32s3_full_with_ui`.
|
||||
- Upload Freenove: FAIL (port occupe) sur `/dev/cu.usbmodem5AB90753301`.
|
||||
|
||||
## [2026-02-28] A252 volume normal (suppression forcage max)
|
||||
|
||||
- Correctifs:
|
||||
- `hardware/RTC_SLIC_PHONE/src/main.cpp`
|
||||
- ajout constante `kA252CodecNormalVolumePercent=70`,
|
||||
- remplacement du forcage volume 100 -> 70 dans `ensureA252AudioDefaults`,
|
||||
- remplacement du forcage `VOLUME_SET` 100 -> 70,
|
||||
- maintien gain logiciel neutre (`[AudioEngine] playback boost set to 1.00x + software 1.00x`).
|
||||
- `hardware/RTC_SLIC_PHONE/src/config/A252ConfigStore.h`
|
||||
- volume par defaut passe de 100 -> 70.
|
||||
- Validation:
|
||||
- Build PASS: `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev`.
|
||||
- Upload PASS: `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev -t upload --upload-port /dev/cu.usbserial-0001`.
|
||||
- Serie PASS:
|
||||
- `VOLUME_GET` -> `{"volume":70}`.
|
||||
|
||||
## [2026-02-28] A252 rollback volume (retour son comme avant)
|
||||
|
||||
- Demande utilisateur: restaurer le comportement sonore precedent.
|
||||
- Correctifs:
|
||||
- `hardware/RTC_SLIC_PHONE/src/main.cpp`
|
||||
- retour forçage volume A252 a `100` (`kA252CodecMaxVolumePercent`),
|
||||
- `ensureA252AudioDefaults()` repasse en cible `100`,
|
||||
- `VOLUME_SET` repasse en forçage `100`.
|
||||
- `hardware/RTC_SLIC_PHONE/src/config/A252ConfigStore.h`
|
||||
- valeur par defaut `volume = 100`.
|
||||
- Validation:
|
||||
- Build PASS: `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev`.
|
||||
- Upload PASS: `pio run -d hardware/RTC_SLIC_PHONE -e esp32dev -t upload --upload-port /dev/cu.usbserial-0001`.
|
||||
- Serie PASS: `VOLUME_GET` -> `{"volume":100}`.
|
||||
|
||||
## [2026-02-28 17:44] Freenove FX all-scenes + scroll text
|
||||
- Scope: SCENE_* forced to LGFX/FX runtime (LVGL text disabled in scene runtime path).
|
||||
- Alias parity: SCENE_CREDIT handled as credits timeline/preset path.
|
||||
- Scroll policy: fallback scroll text enabled for every scene (subtitle/title, fallback message).
|
||||
- Build gate: pio run -e freenove_esp32s3_full_with_ui => SUCCESS.
|
||||
- Flash: pio run -e freenove_esp32s3_full_with_ui -t upload --upload-port /dev/cu.usbmodem5AB90753301 => SUCCESS.
|
||||
|
||||
## [2026-02-28 17:51] Credits: StarWars immediate + no LVGL text scroll
|
||||
- Scene credits path now enters StarWars crawl immediately (no boot/holo pre-phases for SCENE_CREDITS/SCENE_CREDIT).
|
||||
- Credits elapsed base adjusted to start from scene time 0 to avoid delayed/wrapped crawl.
|
||||
- Serial evidence after flash:
|
||||
- `ACK SCENE_GOTO ok=1` for `SCENE_CREDITS`
|
||||
- `UI_SCENE_STATUS`: `scene_id=SCENE_CREDITS`, `text_backend=lgfx_overlay`, `lvgl_text_disabled=true`
|
||||
- Build PASS: `pio run -e freenove_esp32s3_full_with_ui`
|
||||
- Upload PASS: `pio run -e freenove_esp32s3_full_with_ui -t upload --upload-port /dev/cu.usbmodem5AB90753301`
|
||||
|
||||
## [2026-02-28 18:00] Credits scroll direction audit + StarWars crawl correction
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260228_175454_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260228_175454_status.txt`
|
||||
- Audit code:
|
||||
- sens vertical credits explicite en `bottom -> top` via `kCreditsScrollDirectionY=-1`.
|
||||
- projection Y StarWars passee en quadratique (compression vers horizon) pour eviter un simple scroll lineaire.
|
||||
- scroller horizontal FX desactive pour `SCENE_CREDITS`/`SCENE_CREDIT` (texte StarWars uniquement).
|
||||
- Fichiers modifies:
|
||||
- `hardware/firmware/ui_freenove_allinone/src/ui/ui_manager.cpp`
|
||||
- `hardware/firmware/ui_freenove_allinone/src/ui/ui_manager_effects.cpp`
|
||||
- Validation:
|
||||
- Build PASS: `pio run -e freenove_esp32s3_full_with_ui`
|
||||
- Upload PASS: `pio run -e freenove_esp32s3_full_with_ui -t upload --upload-port /dev/cu.usbmodem5AB90753301`
|
||||
- Serial PASS:
|
||||
- `ACK SCENE_GOTO ok=1` on `SCENE_CREDITS`
|
||||
- `UI_SCENE_STATUS`: `scene_id=SCENE_CREDITS`, `text_backend=lgfx_overlay`, `lvgl_text_disabled=true`
|
||||
|
||||
## [2026-02-28 18:38] Credits StarWars text visibility hardening (LGFX overlay)
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260228_183054_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260228_183054_status.txt`
|
||||
- Correctifs:
|
||||
- `hardware/firmware/ui_freenove_allinone/src/ui/ui_manager.cpp`
|
||||
- `sceneStatusSnapshot()` renvoie des compteurs overlay live (`overlay_draw_ok/fail/startwrite/skip`) pour debug reel.
|
||||
- Credits StarWars: bascule des lignes en fonts built-in LGFX pour robustesse d'affichage.
|
||||
- Ajout fallback: si aucune ligne du crawl n'est visible sur la frame, affichage d'une ligne credits de secours (centre bas) pour eviter ecran texte vide.
|
||||
- Validation:
|
||||
- Build PASS: `pio run -e freenove_esp32s3_full_with_ui`
|
||||
- Upload PASS: `pio run -e freenove_esp32s3_full_with_ui -t upload --upload-port /dev/cu.usbmodem5AB90753301`
|
||||
- Serie PASS:
|
||||
- `ACK SCENE_GOTO ok=1` sur `SCENE_CREDIT`
|
||||
- `UI_SCENE_STATUS` montre `scene_id=SCENE_CREDITS`, `text_backend=lgfx_overlay`
|
||||
- compteurs overlay progressent (`overlay_draw_ok: 9 -> 65`, `overlay_draw_fail: 0`)
|
||||
|
||||
## [2026-02-28 19:42] Credits StarWars effet renforce (perspective + inclinaison visuelle)
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260228_193417_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260228_193417_status.txt`
|
||||
- Correctifs:
|
||||
- `hardware/firmware/ui_freenove_allinone/src/ui/ui_manager.cpp`
|
||||
- horizon remonte (`height/5`) pour accentuer la fuite perspective.
|
||||
- couleur crawl forcee en jaune StarWars (`0xFFD34A`) en mode credits.
|
||||
- projection verticale passe en cubique (compression plus marquee vers l'horizon).
|
||||
- squeeze horizontal renforce + couloir top plus etroit.
|
||||
- ajout d'un tilt vertical par glyph (bords legerement releves) pour un rendu plus "crawl cinema".
|
||||
- Validation:
|
||||
- Build PASS: `pio run -e freenove_esp32s3_full_with_ui`
|
||||
- Upload PASS: `pio run -e freenove_esp32s3_full_with_ui -t upload --upload-port /dev/cu.usbmodem5AB90753301`
|
||||
- Serie PASS:
|
||||
- `ACK SCENE_GOTO ok=1` sur `SCENE_CREDIT`
|
||||
- `UI_SCENE_STATUS`: `scene_id=SCENE_CREDITS`
|
||||
- overlay actif (`overlay_draw_ok: 1 -> 38`, `overlay_draw_fail: 0`)
|
||||
|
||||
## [2026-02-28 20:14] Boot credits direct + audio SCENE_WIN force loop
|
||||
- Checkpoint securite:
|
||||
- `/tmp/zacus_checkpoint/20260228_195154_wip.patch`
|
||||
- `/tmp/zacus_checkpoint/20260228_195154_status.txt`
|
||||
- Correctifs:
|
||||
- `hardware/firmware/ui_freenove_allinone/src/app/main.cpp`
|
||||
- boot story par defaut route sur `SCENE_CREDITS`.
|
||||
- helper credits ajoute (`isCreditsSceneId`, `playCreditsWinTrack`, `isCreditsWinTrackPath`).
|
||||
- `ACTION_QUEUE_SONAR` en scene credits joue prioritairement `SCENE_WIN.mp3`.
|
||||
- garde credits audio: si piste non credits (ex: boot_radio), stop + force `SCENE_WIN.mp3`.
|
||||
- boucle credits conservee entre `SCENE_CREDITS` et `SCENE_CREDIT` avec relance piste credits.
|
||||
- Validation:
|
||||
- Build PASS: `pio run -e freenove_esp32s3_full_with_ui`
|
||||
- Upload firmware PASS (avant deconnexion USB): `pio run -e freenove_esp32s3_full_with_ui -t upload --upload-port /dev/cu.usbmodem5AB90753301`
|
||||
- UploadFS PASS: `pio run -e freenove_esp32s3_full_with_ui -t uploadfs --upload-port /dev/cu.usbmodem5AB90753301`
|
||||
- Serie PASS (evidence):
|
||||
- `[BOOT] route default scene=SCENE_CREDITS ok=1`
|
||||
- `[MAIN] credits boot audio scene=SCENE_CREDITS track=/music/SCENE_WIN.mp3 source=forced prev=/music/boot_radio.mp3`
|
||||
- `AUDIO_STATUS ... track=/music/SCENE_WIN.mp3 ...`
|
||||
- Dernier upload TENTATIVE FAIL: port `/dev/cu.usbmodem5AB90753301` disparu (USB non detecte au moment de la commande).
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# STOP REQUIRED
|
||||
|
||||
## Trigger condition (2026-02-28)
|
||||
- Build/test regression not fixed quickly (agent contract stop condition reached).
|
||||
|
||||
## What was done just before stop
|
||||
- Flashed A252 with `pio run -e esp32dev -t upload --upload-port /dev/cu.usbserial-0001`.
|
||||
- Ran serial verification command to force MP3 playback:
|
||||
- `PLAY sd:/hotline_tts/SCENE_U_SON_PROTO/attente_validation_mystere_denise.mp3`
|
||||
- Firmware confirmed MP3 decode start:
|
||||
- `[AudioEngine] mp3 header parsed sr=22050 ch=1 bits=16 bitrate=64000 ...`
|
||||
- Immediately after `OK PLAY`, board crashed with:
|
||||
- `Guru Meditation Error: Core 1 panic'ed (LoadProhibited)`
|
||||
- reboot loop followed.
|
||||
|
||||
## Why execution is stopped
|
||||
- A runtime regression is now reproducible on A252 during MP3 playback using the new decoder path.
|
||||
- Contract requires immediate stop until user decision on next action.
|
||||
|
||||
## Required user decision
|
||||
- Approve focused crash triage/fix for this MP3 playback panic, or rollback the MP3 decoder change.
|
||||
|
||||
## Trigger condition
|
||||
- Deletion of more than 10 files in one run (agent contract stop condition reached).
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# SCENARIO BRIEF — DEFAULT
|
||||
# SCENARIO BRIEF — None
|
||||
|
||||
## Stats
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Organisation des fichiers TTS pour la hotline
|
||||
|
||||
Chaque scène dispose de ses fichiers TTS (text-to-speech) pour les différents états et situations.
|
||||
|
||||
Structure recommandée :
|
||||
|
||||
hotline_tts/
|
||||
├── SCENE_U_SON_PROTO/
|
||||
|
||||
│ ├── indice_1.mp3
|
||||
│ ├── indice_2.mp3
|
||||
│ └── indice_3.mp3
|
||||
├── SCENE_LA_DETECTOR/
|
||||
│ ├── indice_1.mp3
|
||||
│ ├── indice_2.mp3
|
||||
│ └── indice_3.mp3
|
||||
├── SCENE_WIN_ETAPE/
|
||||
│ ├── attente_validation.mp3
|
||||
│ ├── validation_ok.mp3
|
||||
│ ├── validation_ko.mp3
|
||||
├── SCENE_WARNING/
|
||||
│ ├── indice_1.mp3
|
||||
│ ├── indice_2.mp3
|
||||
│ └── indice_3.mp3
|
||||
├── SCENE_CREDITS/
|
||||
│ ├── bravo_1.mp3
|
||||
│ ├── bravo_1.mp3
|
||||
│ ├── bravo_1.mp3
|
||||
├── SCENE_WIN_ETAPE1/
|
||||
│ ├── bravo_1.mp3
|
||||
│ ├── bravo_1.mp3
|
||||
│ ├── bravo_1.mp3
|
||||
├── SCENE_WIN_ETAPE2/
|
||||
│ ├── bravo_1.mp3
|
||||
│ ├── bravo_1.mp3
|
||||
│ ├── bravo_1.mp3
|
||||
├── SCENE_QR_DETECTOR/
|
||||
│ ├── indice_1.mp3
|
||||
│ ├── indice_2.mp3
|
||||
│ └── indice_3.mp3
|
||||
├── SCENE_LEFOU_DETECTOR/
|
||||
│ ├── indice_1.mp3
|
||||
│ ├── indice_2.mp3
|
||||
│ └── indice_3.mp3
|
||||
├── SCENE_POLICE_CHASE_ARCADE/
|
||||
│ ├── warning_1.mp3
|
||||
│ ├── warning_2.mp3
|
||||
│ └── warning_3.mp3
|
||||
|
||||
Pour chaque scène, prévoir :
|
||||
- attente_validation.mp3
|
||||
- validation_ok.mp3
|
||||
- validation_ko.mp3
|
||||
- indice_1.mp3, indice_2.mp3, indice_3.mp3 (si applicable)
|
||||
|
||||
Exemple de nommage :
|
||||
SCENE_LA_DETECTOR/validation_ok.mp3
|
||||
SCENE_WIN_ETAPE/indice_2.mp3
|
||||
|
||||
Tu peux adapter selon les besoins spécifiques de chaque scène.
|
||||
|
||||
Astuce : Ajoute un README.txt dans chaque dossier pour décrire le contenu et l’usage des fichiers.
|
||||
@@ -0,0 +1,11 @@
|
||||
SCENE_CREDITS
|
||||
La scène des crédits marque la fin de l’aventure, un moment de célébration et de reconnaissance.
|
||||
Les messages audio remercient les participants, les créateurs et tous ceux qui ont contribué au projet.
|
||||
La narration retrace les moments forts, les défis relevés et les victoires remportées.
|
||||
Chaque ligne est une ode à la créativité, à la collaboration et à la passion du jeu.
|
||||
Les bravos résonnent, saluant l’engagement et la persévérance des joueurs.
|
||||
La scène invite à la réflexion, à la gratitude et à l’envie de recommencer l’aventure.
|
||||
Les validations rappellent que chaque étape franchie est une victoire collective.
|
||||
Les indices évoquent les secrets du jeu, les anecdotes et les clins d’œil cachés.
|
||||
La scène clôture l’expérience sur une note positive, festive et inspirante.
|
||||
Ici, le jeu continue dans les souvenirs et les rêves des joueurs.
|
||||
@@ -0,0 +1,10 @@
|
||||
Crédits :
|
||||
La scène des crédits s’ouvre sur un effet LovyanGFX Star Wars.
|
||||
Le texte défile verticalement, mettant en valeur chaque participant.
|
||||
La police Montserrat 28 rend hommage à la brigade Z.
|
||||
Le fichier credits.txt du FS est utilisé pour afficher tous les noms.
|
||||
L’ambiance est solennelle, chaque nom est mis en lumière.
|
||||
Le scroll est lent, permettant à chacun de lire et d’apprécier le travail accompli.
|
||||
La scène finale rend hommage à tous les héros du projet.
|
||||
La fête se termine sur une note de reconnaissance et de gratitude.
|
||||
Bravo à tous, la brigade Z a marqué l’histoire du projet Zacus.
|
||||
@@ -0,0 +1,11 @@
|
||||
SCENE_LA_DETECTOR
|
||||
Dans cette scène, tu incarnes un détecteur d’indices, prêt à scruter chaque recoin du jeu.
|
||||
L’ambiance est mystérieuse, ponctuée de sons énigmatiques et de messages codés.
|
||||
Chaque indice audio t’invite à observer, à écouter, à déduire.
|
||||
La scène met l’accent sur la curiosité, la logique et l’esprit d’enquête.
|
||||
Les validations rythment la progression, confirmant ou infirmant tes hypothèses.
|
||||
L’attente de validation est un moment de suspense, où chaque détail peut faire la différence.
|
||||
Les indices sont variés : certains sont évidents, d’autres cachés dans la narration.
|
||||
Le système encourage la prise d’initiative, la réflexion et la persévérance.
|
||||
Chaque réussite est saluée par un message sonore valorisant, chaque échec par un conseil constructif.
|
||||
La scène est conçue pour stimuler l’intelligence et l’imagination du joueur.
|
||||
@@ -0,0 +1,10 @@
|
||||
En attente de validation :
|
||||
Dans la peau du détecteur, tu observes chaque recoin du jeu.
|
||||
L’ambiance est chargée de mystère, chaque son est porteur d’un secret.
|
||||
Tu attends le verdict, scrutant les signaux et les messages codés.
|
||||
La curiosité te pousse à analyser chaque détail, à ne rien laisser au hasard.
|
||||
L’attente est un moment de suspense, où la logique et l’intuition s’entremêlent.
|
||||
Le détecteur analyse ta réponse, prêt à révéler la vérité.
|
||||
Reste attentif, car le moindre indice peut faire basculer la situation.
|
||||
La scène valorise la réflexion, la persévérance et l’esprit d’enquête.
|
||||
Prépare-toi à accueillir la validation comme la confirmation de ton talent d’observateur.
|
||||
@@ -0,0 +1,11 @@
|
||||
Bravo !
|
||||
Tu as su interpréter les signaux du détecteur avec brio.
|
||||
Ta logique et ta curiosité t’ont permis de déjouer les énigmes de la scène.
|
||||
Le système te félicite par un message sonore valorisant, preuve de ton esprit d’enquête.
|
||||
Chaque réussite confirme la justesse de tes hypothèses et la finesse de ton observation.
|
||||
L’ambiance mystérieuse s’ouvre à toi, révélant de nouveaux indices à explorer.
|
||||
Tu as su analyser les signaux, écouter les messages codés et déduire la solution.
|
||||
La réussite est une invitation à poursuivre l’aventure, à affiner ta méthode.
|
||||
Prépare-toi à relever de nouveaux défis, car chaque victoire t’enrichit.
|
||||
Le détecteur t’encourage à rester curieux, à ne jamais cesser d’apprendre.
|
||||
La scène est le terrain de jeu de ton intelligence et de ton imagination.
|
||||
@@ -0,0 +1,11 @@
|
||||
Indice 1 :
|
||||
Dans cette scène, le sablier attire ton regard, son écoulement lent te livre un premier indice.
|
||||
Observe attentivement la façon dont le sable s’arrête, chaque détail compte pour la suite.
|
||||
Le mouvement du sablier est précis, il te guide vers la prochaine étape de l’enquête.
|
||||
La logique et la curiosité sont tes alliées pour comprendre ce mécanisme subtil.
|
||||
L’ambiance mystérieuse t’encourage à ne rien négliger, chaque pixel peut cacher une information.
|
||||
Le système valorise l’observation, la patience et la capacité à déduire.
|
||||
Ce premier indice te pousse à analyser le rythme, à anticiper les changements.
|
||||
Prépare-toi à explorer plus loin, car le sablier est le début d’une série de révélations.
|
||||
L’enquête commence ici, chaque grain de sable est porteur de sens.
|
||||
Continue à scruter, à réfléchir et à avancer dans la scène du détecteur.
|
||||
@@ -0,0 +1,11 @@
|
||||
Indice 2 :
|
||||
Le bargraph bleu s’anime à chaque activité, ses variations sont riches d’enseignements.
|
||||
Analyse la rémanence, la longueur du signal te donne une indication sur l’intensité.
|
||||
Comme sur un autoradio, la vivacité du bargraph révèle l’activité du détecteur.
|
||||
L’ambiance visuelle renforce l’aspect dynamique, chaque pic est une information à décrypter.
|
||||
La logique et la persévérance sont tes alliées pour comprendre ce langage lumineux.
|
||||
Le système encourage l’analyse, la prise d’initiative et la réflexion.
|
||||
Ce deuxième indice te pousse à observer les variations, à anticiper les réactions.
|
||||
Prépare-toi à explorer plus loin, car le bargraph est le reflet de l’activité cachée.
|
||||
L’enquête se poursuit, chaque variation est une piste à suivre.
|
||||
Continue à observer, à analyser et à avancer dans la scène du détecteur.
|
||||
@@ -0,0 +1,11 @@
|
||||
Indice 3 :
|
||||
La waveforme dynamique s’anime sur l’écran, ses variations sont le reflet du signal capté.
|
||||
Déchiffre les pics et les creux, chaque forme est porteuse d’un message caché.
|
||||
L’amplitude et la rapidité des changements te donnent des indices sur la nature du son.
|
||||
Comme une animation de spectre audio, la waveforme réagit à l’intensité du micro.
|
||||
L’ambiance visuelle est réaliste, chaque fluctuation est une énigme à résoudre.
|
||||
La curiosité et la concentration sont tes alliées pour percer ce mystère.
|
||||
Le système encourage l’analyse, la prise d’initiative et la persévérance.
|
||||
Ce troisième indice te pousse à explorer les dynamiques, à ne pas négliger les détails.
|
||||
Prépare-toi à déchiffrer le message, car la waveforme est la clé de l’aventure.
|
||||
Continue à observer, à analyser et à avancer dans la scène du détecteur.
|
||||
@@ -0,0 +1,11 @@
|
||||
Validation échouée :
|
||||
Le détecteur n’a pas réagi comme prévu, mais l’enquête ne s’arrête pas là.
|
||||
Chaque échec est une opportunité d’affiner ta méthode, d’observer autrement.
|
||||
Le système t’invite à réévaluer les indices, à chercher ce qui t’a échappé.
|
||||
La curiosité et la logique sont tes meilleures armes pour surmonter l’obstacle.
|
||||
L’ambiance mystérieuse t’encourage à persévérer, à ne jamais abandonner.
|
||||
Analyse les signaux, écoute les messages codés, déduis une nouvelle solution.
|
||||
Le suspense reste entier, chaque détail peut faire la différence.
|
||||
Prépare-toi à tenter à nouveau, car la réussite est à portée de réflexion.
|
||||
Le détecteur valorise la prise d’initiative et la capacité à apprendre de ses erreurs.
|
||||
L’échec est le début d’une nouvelle piste vers la vérité.
|
||||
@@ -0,0 +1,11 @@
|
||||
Validation réussie :
|
||||
Félicitations, tu as percé le secret du détecteur d’indices.
|
||||
Ta logique et ta curiosité t’ont permis de déjouer les pièges de la scène.
|
||||
Le système te salue par un message sonore valorisant, preuve de ton esprit d’enquête.
|
||||
Chaque validation confirme la justesse de tes hypothèses et la finesse de ton observation.
|
||||
L’ambiance mystérieuse s’ouvre à toi, révélant de nouveaux indices à explorer.
|
||||
Tu as su analyser les signaux, écouter les messages codés et déduire la solution.
|
||||
La réussite est une invitation à poursuivre l’aventure, à affiner ta méthode.
|
||||
Prépare-toi à relever de nouveaux défis, car chaque victoire t’enrichit.
|
||||
Le détecteur t’encourage à rester curieux, à ne jamais cesser d’apprendre.
|
||||
La scène est le terrain de jeu de ton intelligence et de ton imagination.
|
||||
@@ -0,0 +1,11 @@
|
||||
Attention :
|
||||
Le détecteur signale une anomalie, l’enquête prend une tournure inattendue.
|
||||
Les signaux audio se modifient, chaque détail devient crucial.
|
||||
Reste sur tes gardes, car la moindre erreur peut compromettre la mission.
|
||||
Le système t’invite à ajuster ta démarche, à observer autrement.
|
||||
L’ambiance mystérieuse s’intensifie, les indices se font plus subtils.
|
||||
La curiosité et la logique sont tes alliées pour anticiper le danger.
|
||||
Analyse les variations, expérimente de nouvelles hypothèses.
|
||||
Le détecteur valorise la capacité à réagir face à l’imprévu.
|
||||
Prépare-toi à affronter les mystères qui se cachent derrière chaque signal.
|
||||
L’enquête continue, chaque anomalie est une piste à explorer.
|
||||
@@ -0,0 +1,11 @@
|
||||
Alerte :
|
||||
Un signal inattendu perturbe la lecture, l’enquête se complique.
|
||||
L’ambiance se charge de tension, chaque son devient un indice à décrypter.
|
||||
Reste vigilant, car le moindre détail peut révéler une anomalie.
|
||||
Le système t’encourage à analyser la situation, à adapter ta stratégie.
|
||||
La patience et la réflexion sont essentielles pour comprendre le message caché.
|
||||
Observe les variations, expérimente pour percer le mystère.
|
||||
Le détecteur valorise la capacité à anticiper et à réagir rapidement.
|
||||
Prépare-toi à affronter l’inattendu, car chaque alerte est une opportunité d’apprendre.
|
||||
L’enquête continue, chaque signal est une piste à explorer.
|
||||
Continue à observer, à écouter et à déduire.
|
||||
@@ -0,0 +1,11 @@
|
||||
Danger :
|
||||
Le détecteur montre des signes de défaillance, l’enquête est en péril.
|
||||
Les signaux deviennent chaotiques, chaque indice est porteur d’un avertissement.
|
||||
Reste sur tes gardes, car le danger peut surgir à tout instant.
|
||||
Le système t’invite à prendre les mesures nécessaires pour poursuivre l’aventure.
|
||||
L’ambiance est électrique, les indices se multiplient et se croisent.
|
||||
La vigilance et la rapidité sont tes meilleures armes pour surmonter l’obstacle.
|
||||
Analyse les signaux, adapte ta stratégie, expérimente sans relâche.
|
||||
Le détecteur valorise la capacité à gérer le stress et à rebondir face à l’imprévu.
|
||||
Prépare-toi à affronter la tempête, car chaque danger cache une opportunité de progresser.
|
||||
L’enquête continue, chaque défaillance est une nouvelle piste à explorer.
|
||||
@@ -0,0 +1,11 @@
|
||||
SCENE_LEFOU_DETECTOR
|
||||
Bienvenue dans la scène du détecteur « Le Fou », où l’imprévu règne en maître.
|
||||
Ici, le joueur doit faire preuve d’audace, d’humour et d’un brin de folie pour avancer.
|
||||
Les indices sont parfois absurdes, les validations pleines de surprises.
|
||||
L’attente de validation devient un jeu de patience, ponctué de messages décalés et de clins d’œil.
|
||||
Chaque réussite est saluée par une narration originale, chaque échec par une pirouette sonore.
|
||||
La scène encourage la créativité, l’improvisation et l’esprit d’aventure.
|
||||
Les indices audio sont autant de pistes farfelues à explorer, invitant le joueur à sortir des sentiers battus.
|
||||
Le système valorise l’originalité et la prise de risque, récompensant les tentatives audacieuses.
|
||||
Ici, la logique laisse parfois place à l’imagination, pour une expérience ludique et inattendue.
|
||||
Le Fou n’est jamais là où on l’attend : il faut savoir le suivre pour découvrir tous ses secrets.
|
||||
@@ -0,0 +1,11 @@
|
||||
Indice 1 :
|
||||
Dans cette scène sombre, le détecteur capte une première note inquiétante.
|
||||
Observe la fréquence, elle pourrait être la clé de la séquence à valider.
|
||||
L’ambiance est tendue, chaque son est porteur d’un message caché.
|
||||
La logique et la concentration sont tes alliées pour décrypter ce signal.
|
||||
Le système valorise la précision, chaque détail compte pour la réussite.
|
||||
Ce premier indice te pousse à analyser la séquence, à anticiper les changements.
|
||||
Prépare-toi à explorer plus loin, car la validation dépend de l’ordre des notes.
|
||||
L’enquête commence ici, chaque fréquence est porteuse de sens.
|
||||
Continue à écouter, à réfléchir et à avancer dans la scène du fou détecteur.
|
||||
La réussite dépend de ta capacité à reconnaître la séquence exacte.
|
||||
@@ -0,0 +1,11 @@
|
||||
Indice 2 :
|
||||
Le détecteur capte une nouvelle note, la tension monte.
|
||||
Observe la fréquence, elle fait partie de la séquence à valider.
|
||||
L’ambiance est sombre, chaque son est porteur d’un message caché.
|
||||
La logique et la concentration sont tes alliées pour décrypter ce signal.
|
||||
Le système valorise la précision, chaque détail compte pour la réussite.
|
||||
Ce deuxième indice te pousse à analyser la séquence, à anticiper les changements.
|
||||
Prépare-toi à explorer plus loin, car la validation dépend de l’ordre des notes.
|
||||
L’enquête se poursuit, chaque fréquence est porteuse de sens.
|
||||
Continue à écouter, à réfléchir et à avancer dans la scène du fou détecteur.
|
||||
La réussite dépend de ta capacité à reconnaître la séquence exacte.
|
||||
@@ -0,0 +1,11 @@
|
||||
Indice 3 :
|
||||
Le détecteur capte la dernière note de la séquence, la tension est à son comble.
|
||||
Observe la fréquence, elle complète la séquence à valider.
|
||||
L’ambiance est sombre, chaque son est porteur d’un message caché.
|
||||
La logique et la concentration sont tes alliées pour décrypter ce signal.
|
||||
Le système valorise la précision, chaque détail compte pour la réussite.
|
||||
Ce troisième indice te pousse à analyser la séquence, à anticiper les changements.
|
||||
Prépare-toi à valider la scène, car la réussite dépend de l’ordre des notes.
|
||||
L’enquête touche à sa fin, chaque fréquence est porteuse de sens.
|
||||
Continue à écouter, à réfléchir et à avancer dans la scène du fou détecteur.
|
||||
La réussite dépend de ta capacité à reconnaître la séquence exacte.
|
||||
@@ -0,0 +1,11 @@
|
||||
SCENE_POLICE_CHASE_ARCADE
|
||||
Bienvenue dans la scène POLICE_CHASE_ARCADE, où l’action et la tension atteignent leur paroxysme.
|
||||
Le joueur se retrouve plongé dans une course-poursuite effrénée, où chaque choix peut faire basculer le destin.
|
||||
Les sirènes résonnent, les obstacles se multiplient, et la moindre erreur peut coûter cher.
|
||||
La validation des actions est cruciale : elle détermine la réussite ou l’échec de la fuite.
|
||||
L’attente de validation est un moment de suspense intense, où chaque seconde compte pour échapper à la police.
|
||||
Les indices audio guident le joueur, lui suggérant des stratégies et des astuces pour semer ses poursuivants.
|
||||
Le système valorise la rapidité, l’anticipation et la capacité à garder son sang-froid sous pression.
|
||||
Chaque réussite est célébrée par une narration dynamique, chaque échec par un message d’encouragement à recommencer.
|
||||
La scène encourage la prise de risque, l’audace et l’esprit d’aventure.
|
||||
Ici, la victoire se mérite : il faut être malin, rapide et déterminé pour triompher dans la course-poursuite.
|
||||
@@ -0,0 +1,11 @@
|
||||
Warning 1 :
|
||||
La scène POLICE CHASE ARCADE démarre sur une alerte sonore intense.
|
||||
Le gyrophare s’illumine, la sirène retentit, l’ambiance est électrique.
|
||||
Chaque signal audio synchronise les effets visuels, créant une urgence palpable.
|
||||
La rapidité d’exécution est essentielle pour éviter les obstacles.
|
||||
Le système valorise la réactivité, chaque bruit est porteur d’un avertissement.
|
||||
Ce premier warning te pousse à anticiper les dangers, à rester concentré.
|
||||
Prépare-toi à affronter la course-poursuite, chaque instant compte.
|
||||
L’expérience est immersive, chaque signal est une opportunité d’apprendre.
|
||||
Continue à écouter, à observer et à agir avec discernement.
|
||||
Le danger est partout, il faut rester attentif pour avancer.
|
||||
@@ -0,0 +1,11 @@
|
||||
Warning 2 :
|
||||
La sirène de police alterne, le rythme s’accélère, la tension monte.
|
||||
Les effets visuels et sonores se synchronisent pour renforcer l’urgence.
|
||||
La vigilance est essentielle pour anticiper les obstacles et réagir à temps.
|
||||
Le système t’invite à analyser les signaux, à ne pas négliger les détails.
|
||||
Ce deuxième warning te pousse à observer les rythmes, à anticiper les changements.
|
||||
Prépare-toi à affronter l’imprévu, car chaque signal est une opportunité d’apprendre.
|
||||
La scène valorise la capacité à réagir rapidement et à garder son sang-froid.
|
||||
Continue à écouter, à observer et à agir avec discernement.
|
||||
Le danger est partout, il faut rester attentif pour avancer.
|
||||
L’expérience arcade est un test de réactivité et d’intelligence.
|
||||
@@ -0,0 +1,11 @@
|
||||
Warning 3 :
|
||||
La scène atteint son paroxysme, la sirène et le gyrophare s’intensifient.
|
||||
Les signaux audio et visuels se multiplient, chaque instant est critique.
|
||||
La rapidité et la précision sont tes meilleures armes pour éviter l’échec.
|
||||
Le système t’invite à prendre les mesures nécessaires pour surmonter l’obstacle.
|
||||
L’ambiance est électrique, les dangers se succèdent sans répit.
|
||||
Ce troisième warning te pousse à analyser les indices, à adapter ta stratégie.
|
||||
Prépare-toi à affronter la tempête, chaque danger cache une opportunité de progresser.
|
||||
La scène valorise la capacité à gérer le stress et à rebondir face à l’imprévu.
|
||||
Continue à écouter, à analyser et à agir avec discernement.
|
||||
Le danger est partout, il faut rester attentif pour avancer.
|
||||
@@ -0,0 +1,11 @@
|
||||
SCENE_QR_DETECTOR
|
||||
Dans cette scène, le joueur se retrouve face à un défi technologique : la détection de QR codes.
|
||||
L’environnement est truffé de symboles, de messages cachés et d’indices visuels à décrypter.
|
||||
Chaque scan est une aventure : il faut être rapide, précis et attentif aux détails.
|
||||
Les validations rythment la progression, confirmant la réussite ou invitant à recommencer.
|
||||
L’attente de validation est un moment de tension, où chaque seconde compte.
|
||||
Les indices audio guident le joueur, lui suggérant des pistes et des astuces pour réussir.
|
||||
La scène valorise l’observation, la logique et la capacité à résoudre des énigmes.
|
||||
Les échecs sont des opportunités d’apprentissage, les réussites des moments de satisfaction intense.
|
||||
Le système encourage la persévérance et la curiosité, récompensant chaque effort.
|
||||
Ici, la technologie et l’ingéniosité se rencontrent pour offrir une expérience unique.
|
||||
@@ -0,0 +1,11 @@
|
||||
Indice 1 :
|
||||
Pour réussir la détection, aligne le QR code dans le réticule de visée.
|
||||
L’affichage LovyanGFX optimise la lecture, chaque détail compte.
|
||||
La scène utilise une bibliothèque QR optimisée pour microcontrôleur.
|
||||
Reste attentif à la position et à la luminosité pour une détection rapide.
|
||||
Le système analyse l’image capturée, chaque pixel peut être porteur d’information.
|
||||
La patience et la précision sont tes alliées pour réussir.
|
||||
Observe le réticule, ajuste l’angle si nécessaire.
|
||||
La détection fiable dépend de la qualité du QR et de l’environnement.
|
||||
Prépare-toi à valider la scène, chaque essai rapproche du succès.
|
||||
La réussite est à portée de main, reste concentré sur le centre du viseur.
|
||||
@@ -0,0 +1,11 @@
|
||||
Indice 2 :
|
||||
La scène QR_DETECTOR utilise une bibliothèque optimisée pour la reconnaissance.
|
||||
La caméra capture l’image, le système traite chaque pixel pour détecter le code.
|
||||
Reste attentif à la luminosité, elle influence la rapidité de la détection.
|
||||
Le réticule de visée t’aide à aligner correctement le QR code.
|
||||
La patience et la précision sont essentielles pour réussir.
|
||||
Observe les variations d’éclairage, ajuste la position si nécessaire.
|
||||
Le système gère les erreurs pour offrir une expérience robuste.
|
||||
Chaque essai permet d’améliorer la détection, ne te décourage pas.
|
||||
La réussite dépend de la qualité du QR et de l’environnement.
|
||||
Prépare-toi à valider la scène, chaque détail compte pour le succès.
|
||||
@@ -0,0 +1,11 @@
|
||||
Indice 3 :
|
||||
La détection de QR code est sensible aux conditions d’éclairage.
|
||||
Utilise le réticule pour bien centrer le code, ajuste la caméra si besoin.
|
||||
Le système optimise les ressources pour garantir une expérience fluide.
|
||||
La bibliothèque QR gère les cas d’erreur, offrant une robustesse accrue.
|
||||
Reste attentif à la qualité du QR, chaque pixel compte.
|
||||
La patience et la précision sont tes alliées pour réussir.
|
||||
Observe les variations, ajuste la position pour améliorer la détection.
|
||||
Le système teste différentes conditions pour garantir la fiabilité.
|
||||
Chaque essai rapproche du succès, ne te décourage pas.
|
||||
La réussite est à portée de main, reste concentré sur le centre du viseur.
|
||||
@@ -0,0 +1,11 @@
|
||||
SCENE_U_SON_PROTO
|
||||
Plongé dans l’univers sonore du proto, le joueur explore une scène où chaque bruit, chaque vibration, peut révéler un secret.
|
||||
L’ambiance est immersive : les sons se mêlent aux indices, créant une atmosphère intrigante et captivante.
|
||||
La validation des actions dépend de l’écoute attentive et de la capacité à interpréter les signaux audio.
|
||||
L’attente de validation devient une expérience sensorielle, où la patience et la concentration sont récompensées.
|
||||
Les indices audio sont subtils, parfois cachés dans des fréquences ou des rythmes inhabituels.
|
||||
Le système encourage l’expérimentation, la curiosité et l’ouverture d’esprit.
|
||||
Chaque réussite est saluée par une séquence sonore unique, chaque échec par un message d’encouragement.
|
||||
La scène valorise la découverte, l’analyse et la capacité à s’adapter à un environnement changeant.
|
||||
Ici, le son est le guide principal : il faut savoir écouter pour avancer et percer les mystères du proto.
|
||||
L’aventure sonore est une invitation à explorer, à ressentir et à comprendre le jeu autrement.
|
||||
@@ -0,0 +1,11 @@
|
||||
En attente de validation :
|
||||
Plongé dans l’univers sonore du proto, tu attends le verdict du système.
|
||||
Chaque vibration, chaque souffle audio, te rapproche de la révélation.
|
||||
L’ambiance est dense, les sons se mêlent à l’incertitude.
|
||||
La patience devient une arme, la concentration une clé.
|
||||
Tu scrutes les fréquences, cherchant le signal qui confirmera ton intuition.
|
||||
Le système analyse ta réponse, prêt à récompenser l’écoute attentive.
|
||||
L’attente est une épreuve sensorielle, où chaque détail compte.
|
||||
Reste concentré, car le moindre bruit peut être porteur d’un indice.
|
||||
L’univers du proto t’invite à explorer, à ressentir, à comprendre.
|
||||
Prépare-toi à accueillir la validation comme une victoire sur le silence.
|
||||
@@ -0,0 +1,11 @@
|
||||
Bravo !
|
||||
Tu as déjoué les pièges du professeur Zacus dans l’univers sonore du proto.
|
||||
Ta persévérance et ton écoute attentive t’ont permis de franchir chaque étape.
|
||||
Le système te salue par une séquence sonore unique, symbole de ta réussite.
|
||||
Chaque bruit, chaque vibration, devient une célébration de ton talent.
|
||||
L’ambiance s’illumine, les sons se transforment en messages de félicitations.
|
||||
Tu as su interpréter les indices cachés, explorer les fréquences et les rythmes.
|
||||
La Force du son est avec toi, guide tes pas vers de nouveaux mystères.
|
||||
Prépare-toi à poursuivre l’aventure, car chaque victoire t’ouvre de nouveaux horizons.
|
||||
Le proto valorise la découverte, l’analyse et la capacité à s’adapter.
|
||||
Continue à écouter, à ressentir et à comprendre le jeu autrement.
|
||||
@@ -0,0 +1,11 @@
|
||||
Indice 1 :
|
||||
Plongé dans l’univers sonore du proto, tu remarques une fréquence qui se détache du reste.
|
||||
Ce spectre inhabituel attire ton attention, il semble rythmer la scène d’une façon étrange.
|
||||
Écoute attentivement, car ce détail pourrait t’indiquer la voie à suivre.
|
||||
L’ambiance est immersive, chaque bruit devient porteur d’un message caché.
|
||||
La patience et la curiosité sont tes alliées pour décrypter ce signal.
|
||||
Le système encourage l’expérimentation, n’hésite pas à explorer différentes pistes.
|
||||
Ce premier indice te pousse à analyser les sons, à chercher ce qui sort de l’ordinaire.
|
||||
Observe les variations, les ruptures dans le rythme, elles sont souvent révélatrices.
|
||||
Le proto valorise la découverte, chaque indice est une invitation à aller plus loin.
|
||||
Prépare-toi à percer le mystère, car le son est ton guide principal.
|
||||
@@ -0,0 +1,11 @@
|
||||
Indice 2 :
|
||||
Un motif rythmique se répète dans l’univers du proto, il semble porteur d’un message.
|
||||
Analyse sa structure, car chaque variation peut révéler une information précieuse.
|
||||
L’ambiance est intrigante, les sons se mêlent aux indices pour créer une atmosphère unique.
|
||||
La concentration est essentielle pour décrypter ce motif et en comprendre la signification.
|
||||
Le système t’invite à écouter avec attention, à ne pas négliger les détails.
|
||||
Ce deuxième indice te pousse à explorer les rythmes, à chercher des correspondances.
|
||||
Observe les répétitions, les ruptures, elles sont souvent révélatrices d’un code caché.
|
||||
Le proto valorise l’analyse, chaque indice est une étape vers la résolution du mystère.
|
||||
Prépare-toi à découvrir le message, car le son est la clé de l’énigme.
|
||||
Continue à écouter, à ressentir et à analyser l’univers du proto.
|
||||
@@ -0,0 +1,11 @@
|
||||
Indice 3 :
|
||||
La waveforme dynamique du proto semble cacher un message secret.
|
||||
Déchiffre ses pics et ses creux, car chaque variation est porteuse d’un indice.
|
||||
L’ambiance est captivante, les sons se transforment en énigmes à résoudre.
|
||||
La curiosité et la concentration sont tes alliées pour percer ce mystère.
|
||||
Le système t’encourage à analyser les formes, à chercher des correspondances.
|
||||
Ce troisième indice te pousse à explorer les dynamiques, à ne pas négliger les détails.
|
||||
Observe les fluctuations, les changements soudains, ils sont souvent révélateurs d’un code.
|
||||
Le proto valorise la découverte, chaque indice est une étape vers la compréhension.
|
||||
Prépare-toi à déchiffrer le message, car le son est la clé de l’aventure.
|
||||
Continue à écouter, à analyser et à explorer l’univers du proto.
|
||||
@@ -0,0 +1,11 @@
|
||||
Validation échouée :
|
||||
La séquence n’a pas abouti, mais l’aventure sonore continue.
|
||||
Chaque échec est une invitation à écouter plus attentivement, à explorer de nouvelles pistes.
|
||||
Le système t’encourage à analyser les indices cachés dans les fréquences et les rythmes.
|
||||
Ne te décourage pas, car chaque erreur t’enseigne une nouvelle façon d’aborder le proto.
|
||||
L’ambiance reste intrigante, les sons se transforment en messages d’encouragement.
|
||||
La patience et la concentration sont tes alliées pour surmonter l’obstacle.
|
||||
Réévalue ta stratégie, expérimente, ose l’inattendu.
|
||||
Le silence n’est jamais définitif, il cache souvent la clé de la réussite.
|
||||
Prépare-toi à tenter une nouvelle approche, car le proto récompense la persévérance.
|
||||
L’échec n’est qu’une étape vers la découverte des mystères du son.
|
||||
@@ -0,0 +1,11 @@
|
||||
Validation réussie :
|
||||
Bravo, tu as franchi l’étape avec brio dans l’univers sonore du proto.
|
||||
Ta capacité à écouter et à interpréter les signaux t’a permis de percer le mystère.
|
||||
La séquence sonore qui accompagne ta réussite est unique, elle célèbre ton sens de l’analyse.
|
||||
Chaque bruit, chaque vibration, devient une récompense pour ta persévérance.
|
||||
L’ambiance s’illumine, le système te félicite pour ta curiosité et ton ouverture d’esprit.
|
||||
Tu as su déchiffrer les indices cachés dans les fréquences et les rythmes inhabituels.
|
||||
La validation est une victoire sur le silence, une preuve de ton talent d’explorateur.
|
||||
Prépare-toi à poursuivre l’aventure, car chaque réussite t’ouvre de nouveaux horizons.
|
||||
Le proto t’invite à aller plus loin, à découvrir encore plus de secrets.
|
||||
La Force du son est avec toi, continue à écouter pour avancer.
|
||||
@@ -0,0 +1,11 @@
|
||||
Attention :
|
||||
Un paramètre critique vient de changer dans l’univers sonore du proto.
|
||||
Les signaux audio se modifient, créant une atmosphère d’incertitude.
|
||||
Reste attentif, car chaque bruit peut annoncer un bouleversement.
|
||||
Le système t’invite à surveiller les fréquences et à adapter ta stratégie.
|
||||
L’ambiance devient plus dense, les sons se font plus pressants.
|
||||
La curiosité et la vigilance sont tes alliées pour anticiper le danger.
|
||||
Analyse les variations, expérimente de nouvelles approches.
|
||||
Le proto valorise la capacité à réagir face à l’imprévu.
|
||||
Prépare-toi à affronter les mystères qui se cachent derrière chaque signal.
|
||||
Le son est ton guide, il te mènera vers la solution.
|
||||
@@ -0,0 +1,11 @@
|
||||
Alerte :
|
||||
Un bruit suspect se fait entendre, perturbant l’équilibre du proto.
|
||||
L’ambiance se charge de tension, chaque vibration devient un indice.
|
||||
Reste vigilant, car le moindre son peut révéler une anomalie.
|
||||
Le système t’encourage à analyser la situation, à ne rien laisser au hasard.
|
||||
La patience et la concentration sont essentielles pour comprendre le message caché.
|
||||
Observe les variations, expérimente pour percer le mystère.
|
||||
Le proto valorise la capacité à anticiper et à réagir rapidement.
|
||||
Prépare-toi à affronter l’inattendu, car chaque alerte est une opportunité d’apprendre.
|
||||
Le son est ton allié, il te guide vers la résolution du problème.
|
||||
Continue à écouter, à ressentir et à explorer l’univers du proto.
|
||||
@@ -0,0 +1,11 @@
|
||||
Danger :
|
||||
Le système montre des signes d’instabilité, l’univers sonore du proto est en alerte.
|
||||
Les sons deviennent chaotiques, chaque vibration est porteuse d’un avertissement.
|
||||
Reste sur tes gardes, car le danger peut surgir à tout instant.
|
||||
Le système t’invite à prendre les mesures nécessaires pour éviter l’échec.
|
||||
L’ambiance est électrique, les signaux se multiplient et se croisent.
|
||||
La vigilance et la rapidité sont tes meilleures armes pour surmonter l’obstacle.
|
||||
Analyse les indices, adapte ta stratégie, expérimente sans relâche.
|
||||
Le proto valorise la capacité à gérer le stress et à rebondir face à l’imprévu.
|
||||
Prépare-toi à affronter la tempête, car chaque danger cache une opportunité de progresser.
|
||||
Le son est ton guide, il t’aidera à traverser les épreuves.
|
||||
@@ -0,0 +1,11 @@
|
||||
SCENE_WARNING
|
||||
La scène WARNING plonge le joueur dans une atmosphère de tension et d’alerte.
|
||||
Des signaux sonores et visuels avertissent d’un danger imminent ou d’une anomalie à résoudre.
|
||||
Chaque validation est cruciale : elle peut signifier la résolution d’un problème ou l’apparition d’un nouvel obstacle.
|
||||
L’attente de validation est chargée d’adrénaline, chaque seconde compte pour éviter l’échec.
|
||||
Les indices audio sont des avertissements, des conseils pour anticiper et réagir rapidement.
|
||||
Le système valorise la vigilance, la rapidité d’exécution et la capacité à garder son sang-froid.
|
||||
Les réussites sont célébrées par des messages rassurants, les échecs par des alertes incitant à la prudence.
|
||||
La scène encourage l’analyse des situations, la prise de décision et la gestion du stress.
|
||||
Ici, le danger est partout : il faut rester attentif, écouter et agir avec discernement.
|
||||
L’expérience WARNING est un test de réactivité et d’intelligence face à l’imprévu.
|
||||
@@ -0,0 +1,10 @@
|
||||
En attente de validation :
|
||||
Plongé dans la scène WARNING, tu ressens la tension monter.
|
||||
Les signaux sonores et visuels t’avertissent d’un danger imminent.
|
||||
Chaque seconde d’attente est chargée d’adrénaline, chaque bruit peut être décisif.
|
||||
Le système de sécurité vérifie ta réponse, prêt à déclencher l’alerte.
|
||||
Reste attentif, car le moindre signal peut signifier la réussite ou l’échec.
|
||||
L’attente est une épreuve de sang-froid, où la vigilance est primordiale.
|
||||
Analyse les indices, anticipe les obstacles, prépare-toi à réagir vite.
|
||||
La scène valorise la rapidité, la gestion du stress et la prise de décision.
|
||||
Prépare-toi à accueillir la validation comme une victoire sur le danger.
|
||||
@@ -0,0 +1,10 @@
|
||||
Bravo !
|
||||
Tu as surmonté l’épreuve d’alerte avec sang-froid et rapidité.
|
||||
Le système te félicite par un message rassurant, preuve de ta capacité à gérer le stress.
|
||||
Chaque réussite est une victoire sur le danger, une preuve de ta réactivité.
|
||||
L’ambiance de tension s’apaise, laissant place à la satisfaction d’avoir réagi à temps.
|
||||
Tu as su analyser les signaux d’alerte, anticiper les obstacles et prendre la bonne décision.
|
||||
La réussite t’encourage à rester attentif, car le danger peut surgir à tout moment.
|
||||
Prépare-toi à affronter de nouveaux défis, car chaque victoire te rend plus fort.
|
||||
La scène WARNING est un test de réactivité et d’intelligence face à l’imprévu.
|
||||
Continue à écouter, à analyser et à agir avec discernement.
|
||||
@@ -0,0 +1,11 @@
|
||||
Indice 1 :
|
||||
Un signal intermittent perturbe la séquence, la tension monte dans la scène WARNING.
|
||||
Repère le rythme, chaque variation est porteuse d’un avertissement.
|
||||
L’ambiance sonore et visuelle te plonge dans une atmosphère d’urgence.
|
||||
La vigilance est essentielle pour anticiper le danger et réagir à temps.
|
||||
Le système t’invite à analyser les signaux, à ne pas négliger les détails.
|
||||
Ce premier indice te pousse à observer les rythmes, à anticiper les changements.
|
||||
Prépare-toi à affronter l’imprévu, car chaque signal est une opportunité d’apprendre.
|
||||
La scène WARNING valorise la capacité à réagir rapidement et à garder son sang-froid.
|
||||
Continue à écouter, à observer et à agir avec discernement.
|
||||
Le danger est partout, il faut rester attentif pour avancer.
|
||||
@@ -0,0 +1,11 @@
|
||||
Indice 2 :
|
||||
La couleur rouge s’intensifie à chaque alerte, signalant une situation critique.
|
||||
Surveille son évolution, car chaque nuance est porteuse d’un message d’avertissement.
|
||||
L’ambiance visuelle renforce l’aspect d’urgence, chaque flash est une information à décrypter.
|
||||
La vigilance et la rapidité sont tes alliées pour réagir à temps.
|
||||
Le système t’encourage à analyser les variations, à ne rien laisser au hasard.
|
||||
Ce deuxième indice te pousse à observer les couleurs, à anticiper les réactions.
|
||||
Prépare-toi à affronter l’imprévu, car chaque alerte est une opportunité d’apprendre.
|
||||
La scène WARNING valorise la capacité à anticiper et à réagir rapidement.
|
||||
Continue à observer, à analyser et à agir avec discernement.
|
||||
Le danger est partout, il faut rester attentif pour avancer.
|
||||
@@ -0,0 +1,11 @@
|
||||
Indice 3 :
|
||||
Un bruit sourd accompagne chaque pic de tension, l’ambiance devient électrique.
|
||||
Écoute attentivement, car chaque variation sonore est porteuse d’un indice.
|
||||
La scène WARNING te plonge dans une atmosphère d’urgence, chaque son est un avertissement.
|
||||
La vigilance et la concentration sont essentielles pour localiser la source du danger.
|
||||
Le système t’invite à analyser les bruits, à ne pas négliger les détails.
|
||||
Ce troisième indice te pousse à explorer les dynamiques, à anticiper les réactions.
|
||||
Prépare-toi à affronter l’imprévu, car chaque bruit est une opportunité d’apprendre.
|
||||
La scène WARNING valorise la capacité à réagir rapidement et à garder son sang-froid.
|
||||
Continue à écouter, à analyser et à agir avec discernement.
|
||||
Le danger est partout, il faut rester attentif pour avancer.
|
||||
@@ -0,0 +1,11 @@
|
||||
Validation échouée :
|
||||
Le système a détecté une erreur, mais la scène WARNING n’est pas terminée.
|
||||
Chaque échec est une alerte, un signal pour réagir différemment.
|
||||
L’ambiance de tension t’incite à analyser les indices, à anticiper les dangers.
|
||||
Ne te laisse pas submerger par le stress, utilise-le pour affiner ta stratégie.
|
||||
Le système t’encourage à rester vigilant, à ne jamais baisser la garde.
|
||||
Réévalue ta décision, écoute les signaux d’alerte, prépare une nouvelle réponse.
|
||||
La prudence et la rapidité sont tes alliées pour surmonter l’obstacle.
|
||||
Prépare-toi à tenter une nouvelle approche, car chaque échec t’enseigne la réactivité.
|
||||
La scène WARNING valorise la capacité à apprendre de ses erreurs et à rebondir.
|
||||
L’échec est le début d’une nouvelle chance face à l’imprévu.
|
||||
@@ -0,0 +1,11 @@
|
||||
Validation réussie :
|
||||
Bravo, tu as su éviter le piège dans la scène WARNING.
|
||||
Ta vigilance et ta rapidité d’exécution t’ont permis de surmonter l’épreuve.
|
||||
Le système te félicite par un message rassurant, preuve de ta capacité à gérer le stress.
|
||||
Chaque validation est une victoire sur le danger, une preuve de ton sang-froid.
|
||||
L’ambiance de tension s’apaise, laissant place à la satisfaction d’avoir réagi à temps.
|
||||
Tu as su analyser les signaux d’alerte, anticiper les obstacles et prendre la bonne décision.
|
||||
La réussite t’encourage à rester attentif, car le danger peut surgir à tout moment.
|
||||
Prépare-toi à affronter de nouveaux défis, car chaque victoire te rend plus fort.
|
||||
La scène WARNING est un test de réactivité et d’intelligence face à l’imprévu.
|
||||
Continue à écouter, à analyser et à agir avec discernement.
|
||||
@@ -0,0 +1,11 @@
|
||||
Attention :
|
||||
Un signal d’alerte vient d’être déclenché, la tension monte dans la scène WARNING.
|
||||
Les indicateurs s’affolent, chaque bruit devient un avertissement.
|
||||
Reste attentif, car le danger peut surgir à tout moment.
|
||||
Le système t’invite à surveiller les signaux et à adapter ta stratégie.
|
||||
L’ambiance devient plus électrique, les sons se font plus pressants.
|
||||
La vigilance et la rapidité sont tes alliées pour anticiper le danger.
|
||||
Analyse les variations, expérimente de nouvelles approches.
|
||||
La scène WARNING valorise la capacité à réagir face à l’imprévu.
|
||||
Prépare-toi à affronter les obstacles qui se cachent derrière chaque signal.
|
||||
Le son est ton guide, il te mènera vers la solution.
|
||||
@@ -0,0 +1,11 @@
|
||||
Alerte :
|
||||
Un bruit suspect perturbe la séquence, la tension est à son comble.
|
||||
Les sons deviennent plus sombres, chaque vibration est porteuse d’un avertissement.
|
||||
Reste vigilant, car le moindre signal peut révéler une anomalie.
|
||||
Le système t’encourage à analyser la situation, à ne rien laisser au hasard.
|
||||
La patience et la concentration sont essentielles pour comprendre le message caché.
|
||||
Observe les variations, expérimente pour percer le mystère.
|
||||
La scène WARNING valorise la capacité à anticiper et à réagir rapidement.
|
||||
Prépare-toi à affronter l’inattendu, car chaque alerte est une opportunité d’apprendre.
|
||||
Le son est ton allié, il te guide vers la résolution du problème.
|
||||
Continue à écouter, à ressentir et à explorer l’univers de l’alerte.
|
||||
@@ -0,0 +1,11 @@
|
||||
Danger :
|
||||
Le système montre des signes d’instabilité, la scène WARNING est en alerte maximale.
|
||||
Les sons deviennent chaotiques, chaque vibration est porteuse d’un avertissement.
|
||||
Reste sur tes gardes, car le danger peut surgir à tout instant.
|
||||
Le système t’invite à prendre les mesures nécessaires pour éviter l’échec.
|
||||
L’ambiance est électrique, les signaux se multiplient et se croisent.
|
||||
La vigilance et la rapidité sont tes meilleures armes pour surmonter l’obstacle.
|
||||
Analyse les indices, adapte ta stratégie, expérimente sans relâche.
|
||||
La scène WARNING valorise la capacité à gérer le stress et à rebondir face à l’imprévu.
|
||||
Prépare-toi à affronter la tempête, car chaque danger cache une opportunité de progresser.
|
||||
Le son est ton guide, il t’aidera à traverser les épreuves.
|
||||
@@ -0,0 +1,11 @@
|
||||
SCENE_WIN_ETAPE
|
||||
Bienvenue dans la scène « Victoire d’étape ».
|
||||
Après avoir surmonté les épreuves, tu arrives à un moment clé : la validation de ton avancée.
|
||||
Ici, chaque détail compte : la tension monte, les indices se recoupent, et la réussite n’est jamais garantie.
|
||||
Le système vérifie ta progression, analyse tes réponses et t’encourage à persévérer.
|
||||
Si la validation est positive, une vague de félicitations t’envahit, accompagnée d’un message sonore motivant.
|
||||
En cas d’échec, ne te décourage pas : chaque erreur est une leçon, chaque tentative te rapproche du but.
|
||||
L’attente de validation est l’occasion de réfléchir, de revoir tes indices et de préparer ta prochaine action.
|
||||
Les indices audio te guident subtilement, distillant des conseils et des encouragements.
|
||||
La scène célèbre la progression, valorise l’effort et prépare le joueur à la suite de l’aventure.
|
||||
Ici, la victoire n’est pas seulement un résultat : c’est une étape vers le triomphe final.
|
||||
@@ -0,0 +1,11 @@
|
||||
Attente validation :
|
||||
La scène de victoire approche, mais il te faut encore patienter.
|
||||
Le téléphone sonne, l’ambiance est chargée d’espoir et de suspense.
|
||||
Le système attend la validation de la HOT-LINE, chaque seconde compte.
|
||||
Un message s’affiche à l’écran, t’informant de la situation.
|
||||
La connexion esp-now se rafraîchit, garantissant la fiabilité du processus.
|
||||
Reste attentif, car la validation peut arriver à tout moment.
|
||||
L’attente est une épreuve, mais elle prépare la célébration à venir.
|
||||
Le système veille à la fluidité et à la réactivité de l’expérience.
|
||||
Prépare-toi à vivre la scène de victoire, chaque instant d’attente renforce l’émotion.
|
||||
La victoire n’est plus très loin, garde confiance et reste prêt à décrocher le téléphone.
|
||||
@@ -0,0 +1,11 @@
|
||||
Validation échouée :
|
||||
La HOT-LINE n’a pas confirmé la victoire, le suspense continue.
|
||||
Le téléphone sonne encore, l’attente se prolonge.
|
||||
Un message d’encouragement s’affiche à l’écran, t’invitant à persévérer.
|
||||
Le système vérifie la connexion esp-now, prêt à relancer le processus.
|
||||
L’ambiance reste tendue, mais l’espoir n’est pas perdu.
|
||||
Chaque tentative rapproche de la réussite, il faut garder confiance.
|
||||
La scène de victoire attend encore sa validation, ne baisse pas les bras.
|
||||
Le système veille à la stabilité et à la réactivité de l’expérience.
|
||||
Prépare-toi à retenter ta chance, la victoire est à portée de main.
|
||||
L’échec n’est qu’une étape, la célébration viendra bientôt.
|
||||
@@ -0,0 +1,11 @@
|
||||
Validation réussie :
|
||||
La HOT-LINE a confirmé ta victoire, la scène s’illumine.
|
||||
Le téléphone cesse de sonner, un message de félicitations s’affiche.
|
||||
Le système passe à la scène suivante, tout est prêt pour la célébration.
|
||||
La connexion esp-now a été rafraîchie, garantissant la fluidité du processus.
|
||||
L’ambiance est festive, les effets visuels et sonores marquent l’instant.
|
||||
Tu as franchi toutes les étapes, la brigade Z te salue.
|
||||
La victoire est totale, chaque participant est mis à l’honneur.
|
||||
Prépare-toi à vivre la scène finale, la fête commence.
|
||||
La réussite est le fruit de ta persévérance et de ta réactivité.
|
||||
Bravo, la scène de victoire est à toi, savoure ce moment unique.
|
||||
@@ -0,0 +1,11 @@
|
||||
SCENE_WIN_ETAPE1
|
||||
La scène WIN_ETAPE1 marque la première grande victoire du joueur dans son parcours.
|
||||
Après de nombreux efforts, tu franchis une étape décisive, saluée par des messages de félicitations et d’encouragement.
|
||||
La validation de cette étape est un moment fort, où la satisfaction se mêle à l’excitation de poursuivre l’aventure.
|
||||
L’attente de validation est ponctuée de conseils, d’indices et de rappels sur les réussites passées.
|
||||
Les indices audio t’aident à consolider tes acquis et à préparer la prochaine phase du jeu.
|
||||
Le système valorise la persévérance, la stratégie et la capacité à apprendre de ses erreurs.
|
||||
Chaque réussite est célébrée par une narration positive, chaque échec par un encouragement à recommencer.
|
||||
La scène invite à la réflexion, à la préparation et à l’anticipation des défis à venir.
|
||||
Ici, la victoire est le fruit d’un travail acharné et d’une volonté sans faille.
|
||||
WIN_ETAPE1 est le tremplin vers de nouveaux succès et de nouvelles découvertes.
|
||||
@@ -0,0 +1,10 @@
|
||||
Bravo Brigade Z !
|
||||
La scène de victoire s’ouvre sur un effet de zoom spectaculaire.
|
||||
Le texte s’affiche en grand, vibrant au rythme de la célébration.
|
||||
L’arrière-plan 3D et les boingballs animent l’écran, créant une ambiance festive.
|
||||
Le scroll en bas dédicace Franck Saillant, hommage à la brigade.
|
||||
Les feux d’artifice illuminent la scène, le texte défile au centre.
|
||||
La démo 90s ajoute une touche rétro, renforçant l’émotion du moment.
|
||||
La brigade Z est mise à l’honneur, chaque participant est célébré.
|
||||
La fête dure 30 secondes, laissant le temps de savourer la victoire.
|
||||
Prépare-toi à lire les crédits, la scène finale rend hommage à tous les héros du projet.
|
||||
@@ -0,0 +1,11 @@
|
||||
SCENE_WIN_ETAPE2
|
||||
La scène WIN_ETAPE2 célèbre la deuxième grande étape franchie par le joueur.
|
||||
L’ambiance est festive, les messages audio résonnent comme des applaudissements pour le parcours accompli.
|
||||
La validation de cette étape confirme la progression et ouvre la voie vers la victoire finale.
|
||||
L’attente de validation est l’occasion de savourer le chemin parcouru et de se projeter vers la suite.
|
||||
Les indices audio rappellent les moments forts, les stratégies gagnantes et les erreurs surmontées.
|
||||
Le système encourage la confiance en soi, la prise d’initiative et la gestion des nouveaux défis.
|
||||
Chaque réussite est saluée par une narration enthousiaste, chaque échec par un message motivant.
|
||||
La scène invite à la célébration, à la reconnaissance des efforts et à la préparation de la dernière ligne droite.
|
||||
Ici, la victoire est collective : chaque étape franchie rapproche tous les joueurs du but ultime.
|
||||
WIN_ETAPE2 est le symbole de la ténacité et de l’esprit d’équipe.
|
||||
@@ -0,0 +1,10 @@
|
||||
Bravo Brigade Z !
|
||||
La scène de victoire finale s’ouvre sur un effet de zoom et dézoom impressionnant.
|
||||
L’arrière-plan 3D et les boingballs animent l’écran, créant une ambiance festive et dynamique.
|
||||
Le texte défile en bas, dédicace à Franck Saillant, hommage à la brigade.
|
||||
Les feux d’artifice illuminent la scène, le texte scroll au centre.
|
||||
La démo 90s ajoute une touche rétro, renforçant l’émotion du moment.
|
||||
La brigade Z est mise à l’honneur, chaque participant est célébré.
|
||||
La fête dure 30 secondes, laissant le temps de savourer la victoire.
|
||||
Prépare-toi à lire les crédits, la scène finale rend hommage à tous les héros du projet.
|
||||
La victoire est totale, chaque instant est à savourer.
|
||||
@@ -0,0 +1,11 @@
|
||||
Interlude 01 :
|
||||
|
||||
Dans l’ombre des circuits, l’esprit du professeur Zacus veille. Ici, chaque impulsion, chaque pixel, chaque note audio est le fruit d’une quête collective, une aventure où l’imagination et la rigueur technique s’entremêlent.
|
||||
|
||||
Ce projet n’est pas qu’un simple assemblage de composants : c’est une fresque vivante, où chaque ligne de code raconte une histoire, chaque test révèle un défi relevé.
|
||||
|
||||
La brigade Z avance, soudée par la passion et l’envie de repousser les limites du possible. Les enfants, les parents, les curieux, tous sont invités à plonger dans ce mystère, à explorer les recoins secrets de la machine.
|
||||
|
||||
Ici, la lumière des écrans n’est jamais froide : elle éclaire les visages, réchauffe les cœurs, et invite à la découverte.
|
||||
|
||||
Bienvenue dans l’univers Zacus, où l’électronique devient poésie, et où chaque interlude est une promesse d’aventure.
|
||||
@@ -0,0 +1,13 @@
|
||||
Interlude 02 :
|
||||
|
||||
Le mystère du professeur Zacus s’épaissit à chaque étape. Derrière chaque scène, une énigme, un clin d’œil, une surprise attend le joueur attentif.
|
||||
|
||||
Les voix synthétiques résonnent, portées par des algorithmes soigneusement orchestrés. Les couleurs dansent sur l’écran, les sons s’entrelacent, et l’histoire prend vie.
|
||||
|
||||
Ici, la technologie n’est pas un obstacle : elle est le terrain de jeu, le support de l’imaginaire. Les enfants deviennent explorateurs, les parents complices, les agents des créateurs.
|
||||
|
||||
Chaque interlude est une pause, un souffle, une invitation à regarder autrement, à écouter ce que la machine murmure.
|
||||
|
||||
Dans ce laboratoire, l’erreur est une alliée, le bug une opportunité, et la réussite une fête partagée.
|
||||
|
||||
L’aventure continue, portée par l’esprit d’équipe et la magie du code.
|
||||
@@ -0,0 +1,11 @@
|
||||
Interlude 03 :
|
||||
|
||||
Au cœur du firmware, la brigade Z trace sa route. Les tests s’enchaînent, les scripts s’affinent, et chaque commit rapproche un peu plus du but.
|
||||
|
||||
L’univers Zacus est fait de détails : un pixel bien placé, une note juste, un timing parfait. Rien n’est laissé au hasard, tout est pensé pour émerveiller et surprendre.
|
||||
|
||||
Les agents collaborent, échangent, corrigent, et inventent. Ici, la créativité est reine, la rigueur son alliée.
|
||||
|
||||
Chaque interlude est une fenêtre ouverte sur l’invisible : ce qui se joue derrière l’écran, ce qui anime les circuits, ce qui relie les esprits.
|
||||
|
||||
Dans ce projet, l’électronique devient langage, le jeu devient expérience, et chaque instant est une célébration du savoir-faire collectif.
|
||||
@@ -0,0 +1,13 @@
|
||||
Interlude 04 :
|
||||
|
||||
Le professeur Zacus n’est jamais loin. Son esprit flotte entre les lignes de code, inspire les choix, guide les mains.
|
||||
|
||||
Chaque scène est un défi, chaque bug une énigme à résoudre. Les agents avancent, unis par la curiosité et le goût du partage.
|
||||
|
||||
Les enfants découvrent, les adultes transmettent, et tous ensemble, ils bâtissent un monde où la technologie est source de joie.
|
||||
|
||||
L’interlude est le moment de souffler, de contempler le chemin parcouru, de savourer la beauté du projet.
|
||||
|
||||
Ici, la lumière des écrans éclaire les visages, les rires résonnent, et la magie opère.
|
||||
|
||||
Bienvenue dans l’aventure Zacus, où chaque pause est une promesse de nouveaux mystères à explorer.
|
||||
@@ -0,0 +1,11 @@
|
||||
Interlude 05 :
|
||||
|
||||
Dans le laboratoire Zacus, tout est possible. Les idées fusent, les tests s’enchaînent, et chaque agent apporte sa pierre à l’édifice.
|
||||
|
||||
La brigade Z ne recule devant rien : elle invente, expérimente, et transforme chaque difficulté en opportunité.
|
||||
|
||||
Les interludes sont des respirations, des moments de réflexion, des instants où l’on mesure le chemin parcouru.
|
||||
|
||||
Ici, la technologie est au service de l’humain, le jeu au service de la découverte, et chaque scène est une invitation à rêver plus grand.
|
||||
|
||||
Le mystère du professeur Zacus continue de grandir, porté par l’enthousiasme et la créativité de tous.
|
||||
@@ -0,0 +1,11 @@
|
||||
Interlude 06 :
|
||||
|
||||
Dans l’atelier, les idées fusent et les mains s’activent. Les agents de la brigade Z testent, corrigent, inventent sans relâche.
|
||||
|
||||
Chaque interlude est une pause bienvenue, un moment pour observer le chemin parcouru et rêver à la suite.
|
||||
|
||||
La lumière des écrans éclaire les visages, les rires résonnent, et la magie opère.
|
||||
|
||||
Ici, la technologie est source de joie, le jeu une aventure collective, et chaque instant une célébration du savoir-faire partagé.
|
||||
|
||||
Le mystère du professeur Zacus continue de grandir, porté par l’enthousiasme et la créativité de tous.
|
||||
@@ -0,0 +1,9 @@
|
||||
Interlude 07 :
|
||||
|
||||
La nuit tombe sur le laboratoire, mais l’aventure ne s’arrête jamais. Les agents veillent, surveillent les signaux, et préparent la prochaine étape.
|
||||
|
||||
Chaque interlude est une lumière dans l’obscurité, un repère pour ne pas perdre le fil.
|
||||
|
||||
Les enfants rêvent, les adultes inventent, et la brigade Z avance, portée par la curiosité et la passion.
|
||||
|
||||
Ici, chaque instant compte, chaque détail a son importance, et le mystère du professeur Zacus s’épaissit encore.
|
||||
@@ -0,0 +1,9 @@
|
||||
Interlude 08 :
|
||||
|
||||
Un souffle d’aventure traverse le laboratoire. Les agents s’interrogent, cherchent, et trouvent des solutions inattendues.
|
||||
|
||||
Chaque interlude est une invitation à explorer, à repousser les limites, à inventer de nouveaux chemins.
|
||||
|
||||
La brigade Z ne s’arrête jamais, elle avance, apprend, et partage ses découvertes.
|
||||
|
||||
Ici, la technologie est un terrain de jeu, le mystère une source d’inspiration, et chaque pause un moment de réflexion.
|
||||
@@ -0,0 +1,9 @@
|
||||
Interlude 09 :
|
||||
|
||||
Le professeur Zacus observe, silencieux, les progrès de la brigade Z. Les agents s’entraident, relèvent les défis, et célèbrent chaque victoire.
|
||||
|
||||
Chaque interlude est une étape, un jalon sur le chemin de l’aventure.
|
||||
|
||||
Les enfants découvrent, les adultes transmettent, et tous ensemble, ils bâtissent un monde où la technologie est source de joie et de partage.
|
||||
|
||||
Le mystère continue, porté par l’énergie et la créativité de chacun.
|
||||
@@ -0,0 +1,9 @@
|
||||
Interlude 10 :
|
||||
|
||||
Dans le silence du laboratoire, les idées prennent forme. Les agents de la brigade Z imaginent, testent, et corrigent sans relâche.
|
||||
|
||||
Chaque interlude est une respiration, un moment pour se recentrer et repartir de plus belle.
|
||||
|
||||
La lumière des écrans éclaire les visages, les rires résonnent, et la magie opère.
|
||||
|
||||
Ici, la technologie est source de joie, le jeu une aventure collective, et chaque instant une célébration du savoir-faire partagé.
|
||||
@@ -0,0 +1,9 @@
|
||||
Interlude 11 :
|
||||
|
||||
Le matin se lève sur le laboratoire, apportant avec lui de nouvelles idées et de nouveaux défis. Les agents de la brigade Z se réunissent, prêts à affronter la journée.
|
||||
|
||||
Chaque interlude est une promesse, un moment pour se préparer à l’inattendu.
|
||||
|
||||
Les enfants s’émerveillent, les adultes encouragent, et tous avancent ensemble vers l’inconnu.
|
||||
|
||||
Ici, la technologie est une aventure, le mystère une source de motivation, et chaque instant une occasion d’apprendre.
|
||||
@@ -0,0 +1,9 @@
|
||||
Interlude 12 :
|
||||
|
||||
Un bruit de pas résonne dans le couloir, annonçant le début d’une nouvelle expérience. Les agents s’activent, ajustent les réglages, et lancent les tests.
|
||||
|
||||
Chaque interlude est une transition, un passage vers une nouvelle étape de l’aventure.
|
||||
|
||||
La brigade Z ne recule devant rien, elle explore, invente, et partage ses découvertes.
|
||||
|
||||
Ici, la technologie est un langage, le jeu une histoire, et chaque pause un moment de complicité.
|
||||
@@ -0,0 +1,9 @@
|
||||
Interlude 13 :
|
||||
|
||||
Le professeur Zacus sourit, fier du chemin parcouru par la brigade Z. Les agents relèvent les défis, surmontent les obstacles, et célèbrent chaque réussite.
|
||||
|
||||
Chaque interlude est une célébration, un instant pour savourer le travail accompli.
|
||||
|
||||
Les enfants apprennent, les adultes transmettent, et tous ensemble, ils bâtissent un monde où la technologie est source de joie et de partage.
|
||||
|
||||
Le mystère continue, porté par l’énergie et la créativité de chacun.
|
||||
@@ -0,0 +1,9 @@
|
||||
Interlude 14 :
|
||||
|
||||
La nuit tombe, mais le laboratoire reste éveillé. Les agents de la brigade Z veillent, surveillent les signaux, et préparent la prochaine étape.
|
||||
|
||||
Chaque interlude est une lumière dans l’obscurité, un repère pour ne pas perdre le fil.
|
||||
|
||||
Les enfants rêvent, les adultes inventent, et la brigade Z avance, portée par la curiosité et la passion.
|
||||
|
||||
Ici, chaque instant compte, chaque détail a son importance, et le mystère du professeur Zacus s’épaissit encore.
|
||||
@@ -0,0 +1,9 @@
|
||||
Interlude 15 :
|
||||
|
||||
Le cycle s’achève, mais l’aventure continue. Les agents de la brigade Z se préparent à de nouveaux défis, portés par l’enthousiasme et la créativité.
|
||||
|
||||
Chaque interlude est une porte ouverte sur l’avenir, un moment pour rêver plus grand.
|
||||
|
||||
Les enfants imaginent, les adultes encouragent, et tous avancent ensemble vers de nouveaux mystères.
|
||||
|
||||
Ici, la technologie est une aventure sans fin, le jeu une source d’inspiration, et chaque instant une célébration du savoir-faire collectif.
|
||||
@@ -26,10 +26,13 @@ Référence rôles: [docs/roles_agents.md](docs/roles_agents.md)
|
||||
- `3 -> /hotline/menu_dtmf_long.wav` (SD)
|
||||
- Numérotation impulsion active combiné décroché.
|
||||
- Après sélection d’un numéro valide:
|
||||
- lecture WAV,
|
||||
- tentative de lecture MP3 via table explicite `scene + validation_state + digit`,
|
||||
- fallback heuristique par stems si entrée explicite absente,
|
||||
- fallback automatique vers mapping WAV `1/2/3` si aucun MP3 contextualisé n’est trouvé,
|
||||
- pause 3s,
|
||||
- boucle jusqu’au raccroché.
|
||||
- `WAITING_VALIDATION` déclenche la sonnerie + prompt SD `enter_code_5.wav` au décroché.
|
||||
- `WAITING_VALIDATION` déclenche la sonnerie + prompt SD contextualisé (fallback `enter_code_5__fr-fr-deniseneural.mp3` / `enter_code_5.wav`).
|
||||
- `WAITING_VALIDATION {"scene_id":"SCENE_WIN_ETAPE","step_id":"RTC_ESP_ETAPE1","validation_state":"waiting"}` met à jour le contexte scène/état avant routage MP3.
|
||||
- Lecture MP3 SD activée (decodeur Helix) pour les prompts scène hotline.
|
||||
- Raccroché détecté rapidement (~300 ms).
|
||||
- Pas de sonnerie automatique au boot (ring déclenché par événement runtime uniquement).
|
||||
@@ -37,8 +40,20 @@ Référence rôles: [docs/roles_agents.md](docs/roles_agents.md)
|
||||
## Audio scène hotline (SD)
|
||||
|
||||
- `SCENE <scene_id>` conserve l’état scène et tente une lecture SD mappée (`/hotline/scene_*`).
|
||||
- `SCENE {"id":"<scene_id>","step_id":"<step_id>","validation_state":"waiting|granted|refused|none"}` met à jour le contexte hotline complet.
|
||||
- Commande dédiée: `HOTLINE_SCENE_PLAY <scene_id>` pour forcer la lecture scène.
|
||||
- Mapping voix par défaut: suffixe `__fr-fr-deniseneural.mp3`.
|
||||
- Mapping scène explicite validé:
|
||||
- `SCENE_U_SON_PROTO -> fiches-hotline_2`
|
||||
- `SCENE_LA_DETECTOR -> scene_la_detector_2`
|
||||
- `SCENE_WIN_ETAPE`, `SCENE_WIN_ETAPE1`, `SCENE_WIN_ETAPE2`, `SCENE_CREDITS -> scene_win_2`
|
||||
- `SCENE_WARNING -> scene_broken_2`
|
||||
- `SCENE_QR_DETECTOR -> scene_camera_scan_2`
|
||||
- `SCENE_LEFOU_DETECTOR`, `SCENE_POLICE_CHASE_ARCADE -> scene_search_2`
|
||||
- `HOTLINE_STATUS` expose aussi:
|
||||
- `route_lookup_key` (clé `scene|state|digit`),
|
||||
- `route_resolution` (`explicit_table:*`, `heuristic_stems`, `dial_map`, etc.),
|
||||
- `route_target` (fichier/tone effectivement routé).
|
||||
|
||||
## ESP-NOW (actuel)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -19,6 +19,7 @@ lib_deps =
|
||||
ESP32Async/ESPAsyncWebServer@^3.6.0
|
||||
https://github.com/pschatzmann/arduino-audio-tools.git
|
||||
https://github.com/pschatzmann/arduino-libhelix.git
|
||||
earlephilhower/ESP8266Audio@^1.9.7
|
||||
https://github.com/bitluni/OsciDisplay.git
|
||||
lib_ignore =
|
||||
ESPAsyncTCP
|
||||
@@ -61,6 +62,7 @@ lib_deps =
|
||||
throwtheswitch/Unity@^2.6.1
|
||||
https://github.com/luisllamasbinaburo/Arduino-List.git#master
|
||||
https://github.com/pschatzmann/arduino-audio-tools.git
|
||||
earlephilhower/ESP8266Audio@^1.9.7
|
||||
|
||||
[env:esp32-s3-devkitc-1]
|
||||
board = esp32-s3-devkitc-1
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
#include <AudioFileSourceFS.h>
|
||||
#include <AudioGeneratorMP3.h>
|
||||
#include <AudioOutput.h>
|
||||
#include "audio/AudioEngine.h"
|
||||
|
||||
#include <FFat.h>
|
||||
#include <SD.h>
|
||||
#include <SD_MMC.h>
|
||||
#include <SPI.h>
|
||||
#include <esp_dsp.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -37,23 +41,83 @@ constexpr TickType_t kI2sWriteTimeoutMs = 30;
|
||||
constexpr uint8_t kToneWriteRetryCount = 10U;
|
||||
constexpr TickType_t kI2sReadTimeoutMs = 2;
|
||||
constexpr size_t kPlaybackCopyBytes = 256U;
|
||||
constexpr size_t kPlaybackCopyBytesMp3 = 1024U;
|
||||
constexpr uint8_t kPlaybackCopyRetryCount = 8U;
|
||||
constexpr uint8_t kPlaybackCopyRetryCount = 24U;
|
||||
constexpr uint8_t kPlaybackCopyRetryDelayMs = 1U;
|
||||
constexpr uint16_t kBlockingOutputMaxRetries = 2000U;
|
||||
// Keep retries bounded to avoid long loop stalls if the sink is wedged.
|
||||
constexpr uint16_t kBlockingOutputMaxRetries = 120U;
|
||||
constexpr uint8_t kBlockingOutputRetryDelayMs = 1U;
|
||||
// Keep playback gain neutral; loudness is driven by ES8388 hardware volume.
|
||||
constexpr float kPlaybackBoostLinear = 1.0f;
|
||||
// Keep software gain neutral to avoid cumulative clipping with volume boosts.
|
||||
constexpr float kPlaybackSoftwareGain = 1.0f;
|
||||
// Hotline runtime lock: keep loudness auto processing disabled to preserve deterministic playback.
|
||||
constexpr bool kHardDisableAutoLoudnessProcessing = true;
|
||||
constexpr int16_t kAdcRawMax = 4095;
|
||||
constexpr int16_t kAdcMidScale = kAdcRawMax / 2;
|
||||
constexpr size_t kWavHeaderProbeMaxBytes = 262144U;
|
||||
constexpr size_t kMp3HeaderProbeMaxBytes = 8192U;
|
||||
constexpr uint32_t kStableRatesHz[] = {8000U, 16000U, 22050U, 32000U, 44100U, 48000U};
|
||||
constexpr uint32_t kStorageMountRetryIntervalMs = 3000U;
|
||||
// Keep 24 kHz in the stable set: most hotline TTS MP3 prompts are encoded at 24 kHz.
|
||||
// Avoiding needless 24k -> 22.05k resampling reduces write pressure and artifacts.
|
||||
constexpr uint32_t kStableRatesHz[] = {8000U, 16000U, 22050U, 24000U, 32000U, 44100U, 48000U};
|
||||
constexpr float kDbToLinearRef = 20.0f;
|
||||
constexpr float kMinRmsLinear = 1.0e-5f;
|
||||
|
||||
class AudioToolsMp3OutputBridge final : public ::AudioOutput {
|
||||
public:
|
||||
AudioToolsMp3OutputBridge() {
|
||||
channels = 2U;
|
||||
gainF2P6 = static_cast<uint8_t>(1U << 6U);
|
||||
}
|
||||
|
||||
void setSink(Print* sink) {
|
||||
sink_ = sink;
|
||||
}
|
||||
|
||||
bool begin() override {
|
||||
return sink_ != nullptr;
|
||||
}
|
||||
|
||||
bool stop() override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SetRate(int hz) override {
|
||||
return ::AudioOutput::SetRate(hz);
|
||||
}
|
||||
|
||||
bool SetChannels(int chan) override {
|
||||
if (!::AudioOutput::SetChannels(chan)) {
|
||||
return false;
|
||||
}
|
||||
if (channels < 1U) {
|
||||
channels = 1U;
|
||||
} else if (channels > 2U) {
|
||||
channels = 2U;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConsumeSample(int16_t sample[2]) override {
|
||||
if (sink_ == nullptr || sample == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (channels <= 1U) {
|
||||
const int16_t mono = sample[::AudioOutput::LEFTCHANNEL];
|
||||
const size_t written = sink_->write(reinterpret_cast<const uint8_t*>(&mono), sizeof(mono));
|
||||
return written == sizeof(mono);
|
||||
}
|
||||
|
||||
const int16_t stereo[2] = {sample[::AudioOutput::LEFTCHANNEL], sample[::AudioOutput::RIGHTCHANNEL]};
|
||||
const size_t written = sink_->write(reinterpret_cast<const uint8_t*>(stereo), sizeof(stereo));
|
||||
return written == sizeof(stereo);
|
||||
}
|
||||
|
||||
private:
|
||||
Print* sink_ = nullptr;
|
||||
};
|
||||
|
||||
int16_t clampInt16(float value) {
|
||||
if (value > static_cast<float>(std::numeric_limits<int16_t>::max())) {
|
||||
return std::numeric_limits<int16_t>::max();
|
||||
@@ -177,6 +241,10 @@ float linearToDb(float value) {
|
||||
float dbToLinear(float db) {
|
||||
return std::pow(10.0f, db / kDbToLinearRef);
|
||||
}
|
||||
|
||||
bool wavAutoLoudnessEnabled(const AudioConfig& cfg) {
|
||||
return !kHardDisableAutoLoudnessProcessing && cfg.wav_auto_normalize_limiter;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void AudioEngine::BlockingOutput::setOutput(Print* out) {
|
||||
@@ -212,7 +280,9 @@ size_t AudioEngine::BlockingOutput::write(const uint8_t* data, size_t len) {
|
||||
}
|
||||
|
||||
int AudioEngine::BlockingOutput::availableForWrite() {
|
||||
return (out_ == nullptr) ? 0 : out_->availableForWrite();
|
||||
// Advertise writable capacity and let write() block/retry on the real sink.
|
||||
// This avoids StreamCopy aborting early on transient I2S backpressure spikes.
|
||||
return (out_ == nullptr) ? 0 : 4096;
|
||||
}
|
||||
|
||||
AudioConfig defaultAudioConfigForProfile(BoardProfile profile) {
|
||||
@@ -231,7 +301,7 @@ AudioConfig defaultAudioConfigForProfile(BoardProfile profile) {
|
||||
cfg.ws_pin = A1S_I2S_LRCK;
|
||||
cfg.data_out_pin = A1S_I2S_DOUT;
|
||||
cfg.data_in_pin = A1S_I2S_DIN;
|
||||
cfg.enable_capture = true;
|
||||
cfg.enable_capture = false;
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
@@ -242,25 +312,19 @@ AudioEngine::AudioEngine()
|
||||
capture_clients_mask_(0),
|
||||
playing_(false),
|
||||
features_(getFeatureMatrix(detectBoardProfile())) {
|
||||
playback_blocking_output_.setOutput(&playback_volume_stream_);
|
||||
playback_gain_stream_.setOutput(i2s_stream_);
|
||||
playback_blocking_output_.setOutput(&i2s_stream_);
|
||||
playback_gain_stream_.setOutput(playback_blocking_output_);
|
||||
playback_volume_stream_.setOutput(playback_gain_stream_);
|
||||
playback_channel_converter_stream_.setOutput(playback_blocking_output_);
|
||||
playback_channel_converter_stream_.setOutput(playback_volume_stream_);
|
||||
playback_resample_stream_.setOutput(playback_channel_converter_stream_);
|
||||
wav_stream_.setOutput(playback_volume_stream_);
|
||||
wav_stream_.setDecoder(&wav_decoder_);
|
||||
wav_copy_.setCheckAvailable(false);
|
||||
wav_copy_.setCheckAvailableForWrite(true);
|
||||
wav_copy_.setCheckAvailableForWrite(false);
|
||||
wav_copy_.setMinCopySize(sizeof(int16_t));
|
||||
wav_copy_.setRetry(kPlaybackCopyRetryCount);
|
||||
wav_copy_.setRetryDelay(kPlaybackCopyRetryDelayMs);
|
||||
mp3_stream_.setOutput(playback_volume_stream_);
|
||||
mp3_stream_.setDecoder(&mp3_decoder_);
|
||||
mp3_copy_.setCheckAvailable(false);
|
||||
mp3_copy_.setCheckAvailableForWrite(true);
|
||||
mp3_copy_.setMinCopySize(sizeof(int16_t));
|
||||
mp3_copy_.setRetry(kPlaybackCopyRetryCount);
|
||||
mp3_copy_.setRetryDelay(kPlaybackCopyRetryDelayMs);
|
||||
mp3_output_ = new AudioToolsMp3OutputBridge();
|
||||
}
|
||||
|
||||
AudioEngine::~AudioEngine() {
|
||||
@@ -290,23 +354,71 @@ void AudioEngine::unlockI2s() const {
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioEngine::lockPlaybackState(TickType_t timeout_ticks) const {
|
||||
if (playback_state_mutex_ == nullptr) {
|
||||
return true;
|
||||
}
|
||||
return xSemaphoreTake(playback_state_mutex_, timeout_ticks) == pdTRUE;
|
||||
}
|
||||
|
||||
void AudioEngine::unlockPlaybackState() const {
|
||||
if (playback_state_mutex_ != nullptr) {
|
||||
xSemaphoreGive(playback_state_mutex_);
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioEngine::ensureSdMounted() {
|
||||
if (sd_mount_attempted_) {
|
||||
return sd_ready_;
|
||||
static uint32_t last_sd_attempt_ms = 0U;
|
||||
if (sd_ready_ && sd_fs_ != nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint32_t now = millis();
|
||||
if (sd_mount_attempted_ && static_cast<uint32_t>(now - last_sd_attempt_ms) < kStorageMountRetryIntervalMs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sd_mount_attempted_ = true;
|
||||
sd_ready_ = SD_MMC.begin();
|
||||
if (!sd_ready_) {
|
||||
Serial.println("[AudioEngine] SD_MMC begin failed");
|
||||
last_sd_attempt_ms = now;
|
||||
if (SD_MMC.begin()) {
|
||||
sd_ready_ = true;
|
||||
sd_fs_ = &SD_MMC;
|
||||
return true;
|
||||
}
|
||||
return sd_ready_;
|
||||
|
||||
Serial.println("[AudioEngine] SD_MMC begin failed, trying SD SPI fallback");
|
||||
static bool sd_spi_bus_started = false;
|
||||
if (!sd_spi_bus_started) {
|
||||
SPI.begin(A1S_SD_SCK, A1S_SD_MISO, A1S_SD_MOSI, A1S_SD_CS);
|
||||
sd_spi_bus_started = true;
|
||||
}
|
||||
|
||||
if (SD.begin(A1S_SD_CS, SPI, 10000000U)) {
|
||||
sd_ready_ = true;
|
||||
sd_fs_ = &SD;
|
||||
Serial.println("[AudioEngine] SD mounted via SPI fallback");
|
||||
return true;
|
||||
}
|
||||
|
||||
sd_ready_ = false;
|
||||
sd_fs_ = nullptr;
|
||||
Serial.println("[AudioEngine] SD SPI fallback init failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioEngine::ensureLittleFsMounted() {
|
||||
if (littlefs_mount_attempted_) {
|
||||
return littlefs_ready_;
|
||||
static uint32_t last_littlefs_attempt_ms = 0U;
|
||||
if (littlefs_ready_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint32_t now = millis();
|
||||
if (littlefs_mount_attempted_ && static_cast<uint32_t>(now - last_littlefs_attempt_ms) < kStorageMountRetryIntervalMs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
littlefs_mount_attempted_ = true;
|
||||
last_littlefs_attempt_ms = now;
|
||||
#ifdef USB_MSC_BOOT_ENABLE
|
||||
littlefs_ready_ = FFat.begin(true, "/usbmsc", 10, "usbmsc");
|
||||
#else
|
||||
@@ -353,7 +465,10 @@ bool AudioEngine::openPlaybackFileForSource(const char* path, MediaSource source
|
||||
if (!ensureSdMounted()) {
|
||||
return false;
|
||||
}
|
||||
return try_open(MediaSource::SD, SD_MMC);
|
||||
if (sd_fs_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return try_open(MediaSource::SD, *sd_fs_);
|
||||
}
|
||||
|
||||
if (source == MediaSource::LITTLEFS) {
|
||||
@@ -363,7 +478,7 @@ bool AudioEngine::openPlaybackFileForSource(const char* path, MediaSource source
|
||||
return try_open(MediaSource::LITTLEFS, FFat);
|
||||
}
|
||||
|
||||
if (ensureSdMounted() && try_open(MediaSource::SD, SD_MMC)) {
|
||||
if (ensureSdMounted() && sd_fs_ != nullptr && try_open(MediaSource::SD, *sd_fs_)) {
|
||||
return true;
|
||||
}
|
||||
if (ensureLittleFsMounted() && try_open(MediaSource::LITTLEFS, FFat)) {
|
||||
@@ -372,17 +487,29 @@ bool AudioEngine::openPlaybackFileForSource(const char* path, MediaSource source
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioEngine::stopPlaybackFile() {
|
||||
void AudioEngine::stopPlaybackFileUnlocked() {
|
||||
wav_copy_.end();
|
||||
wav_stream_.end();
|
||||
mp3_copy_.end();
|
||||
mp3_stream_.end();
|
||||
if (mp3_decoder_ != nullptr) {
|
||||
mp3_decoder_->stop();
|
||||
delete mp3_decoder_;
|
||||
mp3_decoder_ = nullptr;
|
||||
}
|
||||
if (mp3_source_ != nullptr) {
|
||||
mp3_source_->close();
|
||||
delete mp3_source_;
|
||||
mp3_source_ = nullptr;
|
||||
}
|
||||
if (mp3_output_ != nullptr) {
|
||||
static_cast<AudioToolsMp3OutputBridge*>(mp3_output_)->setSink(nullptr);
|
||||
}
|
||||
mp3_pcm_sink_ = nullptr;
|
||||
mp3_source_last_pos_ = 0U;
|
||||
playback_resample_stream_.end();
|
||||
playback_channel_converter_stream_.end();
|
||||
playback_channel_converter_stream_.setOutput(playback_blocking_output_);
|
||||
playback_channel_converter_stream_.setOutput(playback_volume_stream_);
|
||||
playback_resample_stream_.setOutput(playback_channel_converter_stream_);
|
||||
wav_stream_.setOutput(playback_volume_stream_);
|
||||
mp3_stream_.setOutput(playback_volume_stream_);
|
||||
playback_loudness_gain_db_ = 0.0f;
|
||||
playback_volume_stream_.setVolume(kPlaybackBoostLinear);
|
||||
restorePlaybackAudioInfo();
|
||||
@@ -392,6 +519,8 @@ void AudioEngine::stopPlaybackFile() {
|
||||
playback_path_ = "";
|
||||
playback_data_remaining_ = 0;
|
||||
playback_data_offset_ = 0;
|
||||
playback_wav_direct_mode_ = false;
|
||||
playback_mp3_bitrate_bps_ = 0U;
|
||||
playback_input_channels_ = 0;
|
||||
playback_input_audio_info_.clear();
|
||||
playback_resampler_active_ = false;
|
||||
@@ -404,6 +533,14 @@ void AudioEngine::stopPlaybackFile() {
|
||||
playing_ = false;
|
||||
}
|
||||
|
||||
void AudioEngine::stopPlaybackFile() {
|
||||
if (!lockPlaybackState(pdMS_TO_TICKS(50))) {
|
||||
return;
|
||||
}
|
||||
stopPlaybackFileUnlocked();
|
||||
unlockPlaybackState();
|
||||
}
|
||||
|
||||
bool AudioEngine::prepareWavPlayback(File& file, const char* path) {
|
||||
if (!file) {
|
||||
return false;
|
||||
@@ -465,7 +602,7 @@ bool AudioEngine::prepareWavPlayback(File& file, const char* path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
playback_loudness_auto_ = _config.wav_auto_normalize_limiter;
|
||||
playback_loudness_auto_ = wavAutoLoudnessEnabled(_config);
|
||||
playback_limiter_active_ = false;
|
||||
playback_loudness_gain_db_ = 0.0f;
|
||||
if (playback_loudness_auto_) {
|
||||
@@ -629,6 +766,7 @@ bool AudioEngine::prepareMp3Playback(File& file, const char* path) {
|
||||
playback_last_error_ = "";
|
||||
playback_data_offset_ = 0U;
|
||||
playback_data_remaining_ = static_cast<uint32_t>(file.size());
|
||||
playback_mp3_bitrate_bps_ = 0U;
|
||||
playback_loudness_auto_ = false;
|
||||
playback_limiter_active_ = false;
|
||||
playback_loudness_gain_db_ = 0.0f;
|
||||
@@ -638,7 +776,8 @@ bool AudioEngine::prepareMp3Playback(File& file, const char* path) {
|
||||
playback_volume_stream_.setVolume(kPlaybackBoostLinear);
|
||||
|
||||
audio_tools::AudioInfo mp3_info{};
|
||||
if (!readMp3HeaderInfo(file, mp3_info, nullptr) || !isPlaybackAudioInfoSupported(mp3_info)) {
|
||||
uint32_t mp3_bitrate_bps = 0U;
|
||||
if (!readMp3HeaderInfo(file, mp3_info, &mp3_bitrate_bps) || !isPlaybackAudioInfoSupported(mp3_info)) {
|
||||
playback_input_audio_info_ = default_playback_audio_info_;
|
||||
playback_resampler_active_ = false;
|
||||
playback_channel_upmix_active_ = false;
|
||||
@@ -652,6 +791,7 @@ bool AudioEngine::prepareMp3Playback(File& file, const char* path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
playback_mp3_bitrate_bps_ = mp3_bitrate_bps;
|
||||
playback_input_audio_info_ = mp3_info;
|
||||
const audio_tools::AudioInfo resolved_output = resolvePlaybackFormat(mp3_info);
|
||||
playback_resampler_active_ = (resolved_output.sample_rate != mp3_info.sample_rate);
|
||||
@@ -667,10 +807,11 @@ bool AudioEngine::prepareMp3Playback(File& file, const char* path) {
|
||||
if (!file.seek(0U)) {
|
||||
return false;
|
||||
}
|
||||
Serial.printf("[AudioEngine] mp3 header parsed sr=%u ch=%u bits=%u path=%s out_sr=%u out_ch=%u resampler=%s upmix=%s\n",
|
||||
Serial.printf("[AudioEngine] mp3 header parsed sr=%u ch=%u bits=%u bitrate=%u path=%s out_sr=%u out_ch=%u resampler=%s upmix=%s\n",
|
||||
static_cast<unsigned>(mp3_info.sample_rate),
|
||||
static_cast<unsigned>(mp3_info.channels),
|
||||
static_cast<unsigned>(mp3_info.bits_per_sample),
|
||||
static_cast<unsigned>(playback_mp3_bitrate_bps_),
|
||||
path == nullptr ? "(null)" : path,
|
||||
static_cast<unsigned>(resolved_output.sample_rate),
|
||||
static_cast<unsigned>(resolved_output.channels),
|
||||
@@ -846,15 +987,15 @@ audio_tools::AudioInfo AudioEngine::resolvePlaybackFormat(const audio_tools::Aud
|
||||
}
|
||||
playback_rate_fallback_ = fallback_rate_hz;
|
||||
output.bits_per_sample = 16U;
|
||||
// Force stereo output for hotline playback so mono prompts are centered on both channels.
|
||||
output.channels = 2U;
|
||||
// Keep source channel count when possible: mono WAV prompts are native on A252.
|
||||
output.channels = (input.channels == 0U) ? 1U : std::min<uint16_t>(input.channels, 2U);
|
||||
return output;
|
||||
}
|
||||
|
||||
bool AudioEngine::configureWavPlaybackPipeline(const audio_tools::AudioInfo& input, const audio_tools::AudioInfo& output) {
|
||||
playback_resample_stream_.end();
|
||||
playback_channel_converter_stream_.end();
|
||||
playback_channel_converter_stream_.setOutput(playback_blocking_output_);
|
||||
playback_channel_converter_stream_.setOutput(playback_volume_stream_);
|
||||
playback_resample_stream_.setOutput(playback_channel_converter_stream_);
|
||||
wav_stream_.setOutput(playback_volume_stream_);
|
||||
|
||||
@@ -900,9 +1041,9 @@ bool AudioEngine::configureWavPlaybackPipeline(const audio_tools::AudioInfo& inp
|
||||
bool AudioEngine::configureMp3PlaybackPipeline(const audio_tools::AudioInfo& input, const audio_tools::AudioInfo& output) {
|
||||
playback_resample_stream_.end();
|
||||
playback_channel_converter_stream_.end();
|
||||
playback_channel_converter_stream_.setOutput(playback_blocking_output_);
|
||||
playback_channel_converter_stream_.setOutput(playback_volume_stream_);
|
||||
playback_resample_stream_.setOutput(playback_channel_converter_stream_);
|
||||
mp3_stream_.setOutput(playback_volume_stream_);
|
||||
mp3_pcm_sink_ = &playback_volume_stream_;
|
||||
|
||||
const bool channel_convert_active = (output.channels != input.channels);
|
||||
|
||||
@@ -912,7 +1053,7 @@ bool AudioEngine::configureMp3PlaybackPipeline(const audio_tools::AudioInfo& inp
|
||||
} else {
|
||||
playback_resample_stream_.setOutput(playback_volume_stream_);
|
||||
}
|
||||
mp3_stream_.setOutput(playback_resample_stream_);
|
||||
mp3_pcm_sink_ = &playback_resample_stream_;
|
||||
if (!playback_resample_stream_.begin(input, static_cast<int>(output.sample_rate))) {
|
||||
Serial.printf("[AudioEngine] mp3 resampler begin failed in_sr=%u out_sr=%u\n",
|
||||
static_cast<unsigned>(input.sample_rate),
|
||||
@@ -921,9 +1062,9 @@ bool AudioEngine::configureMp3PlaybackPipeline(const audio_tools::AudioInfo& inp
|
||||
}
|
||||
} else {
|
||||
if (channel_convert_active) {
|
||||
mp3_stream_.setOutput(playback_channel_converter_stream_);
|
||||
mp3_pcm_sink_ = &playback_channel_converter_stream_;
|
||||
} else {
|
||||
mp3_stream_.setOutput(playback_volume_stream_);
|
||||
mp3_pcm_sink_ = &playback_volume_stream_;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -940,7 +1081,11 @@ bool AudioEngine::configureMp3PlaybackPipeline(const audio_tools::AudioInfo& inp
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
if (mp3_output_ != nullptr) {
|
||||
static_cast<AudioToolsMp3OutputBridge*>(mp3_output_)->setSink(mp3_pcm_sink_);
|
||||
}
|
||||
|
||||
return mp3_pcm_sink_ != nullptr;
|
||||
}
|
||||
|
||||
void AudioEngine::applyPlaybackAudioInfo(const audio_tools::AudioInfo& info) {
|
||||
@@ -1021,7 +1166,7 @@ float AudioEngine::analyzeWavLoudnessGainDb(
|
||||
uint32_t data_size,
|
||||
bool& out_limiter_active) const {
|
||||
out_limiter_active = false;
|
||||
if (!_config.wav_auto_normalize_limiter || input.channels == 0U || data_offset == 0U || data_size == 0U) {
|
||||
if (!wavAutoLoudnessEnabled(_config) || input.channels == 0U || data_offset == 0U || data_size == 0U) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
@@ -1344,6 +1489,10 @@ int16_t AudioEngine::processAdcSample(int16_t raw_sample) {
|
||||
bool AudioEngine::begin(const AudioConfig& config) {
|
||||
end();
|
||||
_config = config;
|
||||
if (kHardDisableAutoLoudnessProcessing && _config.wav_auto_normalize_limiter) {
|
||||
Serial.println("[AudioEngine] wav auto loudness requested but hard-disabled by firmware policy");
|
||||
}
|
||||
_config.wav_auto_normalize_limiter = wavAutoLoudnessEnabled(_config);
|
||||
adc_capture_pin_ = config.capture_adc_pin;
|
||||
use_adc_capture_ = (adc_capture_pin_ >= 0);
|
||||
const int max_gpio = (detectBoardProfile() == BoardProfile::ESP32_S3) ? 48 : 39;
|
||||
@@ -1433,6 +1582,7 @@ bool AudioEngine::begin(const AudioConfig& config) {
|
||||
playback_loudness_gain_db_ = 0.0f;
|
||||
playback_limiter_active_ = false;
|
||||
playback_rate_fallback_ = 0U;
|
||||
playback_mp3_bitrate_bps_ = 0U;
|
||||
playback_copy_source_bytes_ = 0U;
|
||||
playback_copy_accepted_bytes_ = 0U;
|
||||
playback_copy_loss_bytes_ = 0U;
|
||||
@@ -1466,6 +1616,12 @@ bool AudioEngine::begin(const AudioConfig& config) {
|
||||
Serial.println("[AudioEngine] i2s mutex unavailable");
|
||||
}
|
||||
}
|
||||
if (playback_state_mutex_ == nullptr) {
|
||||
playback_state_mutex_ = xSemaphoreCreateMutex();
|
||||
if (playback_state_mutex_ == nullptr) {
|
||||
Serial.println("[AudioEngine] playback state mutex unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
driver_installed_ = true;
|
||||
portENTER_CRITICAL(&capture_lock_);
|
||||
@@ -1511,8 +1667,16 @@ void AudioEngine::end() {
|
||||
vSemaphoreDelete(i2s_io_mutex_);
|
||||
i2s_io_mutex_ = nullptr;
|
||||
}
|
||||
if (playback_state_mutex_ != nullptr) {
|
||||
vSemaphoreDelete(playback_state_mutex_);
|
||||
playback_state_mutex_ = nullptr;
|
||||
}
|
||||
i2s_stream_.end();
|
||||
playback_volume_stream_.end();
|
||||
if (mp3_output_ != nullptr) {
|
||||
delete static_cast<AudioToolsMp3OutputBridge*>(mp3_output_);
|
||||
mp3_output_ = nullptr;
|
||||
}
|
||||
driver_installed_ = false;
|
||||
}
|
||||
|
||||
@@ -1584,7 +1748,12 @@ bool AudioEngine::playFileFromSource(const char* path, MediaSource source) {
|
||||
}
|
||||
|
||||
stopTone();
|
||||
stopPlaybackFile();
|
||||
|
||||
if (!lockPlaybackState(pdMS_TO_TICKS(200))) {
|
||||
playback_last_error_ = "playback_lock_timeout";
|
||||
return false;
|
||||
}
|
||||
stopPlaybackFileUnlocked();
|
||||
playback_last_error_ = "";
|
||||
|
||||
fs::FS* mounted_fs = nullptr;
|
||||
@@ -1592,6 +1761,7 @@ bool AudioEngine::playFileFromSource(const char* path, MediaSource source) {
|
||||
if (!openPlaybackFileForSource(path, source, mounted_fs, selected_source) || mounted_fs == nullptr || !playback_file_) {
|
||||
Serial.printf("[AudioEngine] playback file not found source=%s path=%s\n", mediaSourceToString(source), path);
|
||||
playback_last_error_ = "file_not_found";
|
||||
unlockPlaybackState();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1599,38 +1769,80 @@ bool AudioEngine::playFileFromSource(const char* path, MediaSource source) {
|
||||
if (use_mp3_decoder) {
|
||||
if (!prepareMp3Playback(playback_file_, path)) {
|
||||
playback_last_error_ = "mp3_prepare_failed";
|
||||
stopPlaybackFile();
|
||||
stopPlaybackFileUnlocked();
|
||||
unlockPlaybackState();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mp3_stream_.begin()) {
|
||||
playback_last_error_ = "decoder_begin_failed";
|
||||
stopPlaybackFile();
|
||||
Serial.printf("[AudioEngine] mp3 decoder begin failed: %s\n", path);
|
||||
if (playback_file_) {
|
||||
playback_file_.close();
|
||||
}
|
||||
if (mp3_output_ == nullptr) {
|
||||
mp3_output_ = new AudioToolsMp3OutputBridge();
|
||||
}
|
||||
if (mp3_output_ == nullptr) {
|
||||
playback_last_error_ = "decoder_output_alloc_failed";
|
||||
stopPlaybackFileUnlocked();
|
||||
unlockPlaybackState();
|
||||
return false;
|
||||
}
|
||||
|
||||
static_cast<AudioToolsMp3OutputBridge*>(mp3_output_)->setSink(mp3_pcm_sink_);
|
||||
mp3_source_ = new AudioFileSourceFS(*mounted_fs, path);
|
||||
if (mp3_source_ == nullptr || !mp3_source_->isOpen()) {
|
||||
playback_last_error_ = "source_open_failed";
|
||||
stopPlaybackFileUnlocked();
|
||||
Serial.printf("[AudioEngine] mp3 source open failed: %s\n", path);
|
||||
unlockPlaybackState();
|
||||
return false;
|
||||
}
|
||||
|
||||
mp3_decoder_ = new AudioGeneratorMP3();
|
||||
auto* output_bridge = static_cast<AudioToolsMp3OutputBridge*>(mp3_output_);
|
||||
if (mp3_decoder_ == nullptr || output_bridge == nullptr || !mp3_decoder_->begin(mp3_source_, output_bridge)) {
|
||||
playback_last_error_ = "decoder_begin_failed";
|
||||
stopPlaybackFileUnlocked();
|
||||
Serial.printf("[AudioEngine] mp3 decoder begin failed: %s\n", path);
|
||||
unlockPlaybackState();
|
||||
return false;
|
||||
}
|
||||
|
||||
playback_volume_stream_.setVolume(kPlaybackBoostLinear);
|
||||
mp3_copy_.begin(mp3_stream_, playback_file_);
|
||||
mp3_source_last_pos_ = mp3_source_ != nullptr ? mp3_source_->getPos() : 0U;
|
||||
playback_codec_ = PlaybackCodec::MP3;
|
||||
} else {
|
||||
if (!prepareWavPlayback(playback_file_, path)) {
|
||||
playback_last_error_ = "wav_prepare_failed";
|
||||
stopPlaybackFile();
|
||||
stopPlaybackFileUnlocked();
|
||||
unlockPlaybackState();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!wav_stream_.begin()) {
|
||||
playback_last_error_ = "decoder_begin_failed";
|
||||
stopPlaybackFile();
|
||||
Serial.printf("[AudioEngine] wav decoder begin failed: %s\n", path);
|
||||
return false;
|
||||
}
|
||||
// Re-assert runtime volume after decoder init because stream reconfiguration
|
||||
// may reset volume state on some runs.
|
||||
const float gain_linear = dbToLinear(playback_loudness_gain_db_);
|
||||
const float requested_volume = clampFloat(kPlaybackBoostLinear * gain_linear, 0.05f, 4.0f);
|
||||
playback_volume_stream_.setVolume(requested_volume);
|
||||
playback_wav_direct_mode_ =
|
||||
(playback_input_audio_info_.bits_per_sample == 16U) &&
|
||||
!playback_resampler_active_ &&
|
||||
!playback_channel_upmix_active_ &&
|
||||
(playback_data_offset_ > 0U);
|
||||
if (playback_wav_direct_mode_) {
|
||||
if (!playback_file_.seek(playback_data_offset_)) {
|
||||
playback_last_error_ = "wav_seek_data_failed";
|
||||
stopPlaybackFileUnlocked();
|
||||
unlockPlaybackState();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!wav_stream_.begin()) {
|
||||
playback_last_error_ = "decoder_begin_failed";
|
||||
stopPlaybackFileUnlocked();
|
||||
Serial.printf("[AudioEngine] wav decoder begin failed: %s\n", path);
|
||||
unlockPlaybackState();
|
||||
return false;
|
||||
}
|
||||
wav_copy_.begin(wav_stream_, playback_file_);
|
||||
}
|
||||
playback_codec_ = PlaybackCodec::WAV;
|
||||
}
|
||||
|
||||
@@ -1644,6 +1856,7 @@ bool AudioEngine::playFileFromSource(const char* path, MediaSource source) {
|
||||
use_mp3_decoder ? "mp3" : "wav",
|
||||
mediaSourceToString(selected_source),
|
||||
path);
|
||||
unlockPlaybackState();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2081,7 +2294,7 @@ uint32_t AudioEngine::toneWriteMissCount() const {
|
||||
}
|
||||
|
||||
bool AudioEngine::supportsFullDuplex() const {
|
||||
return features_.has_full_duplex_i2s;
|
||||
return features_.has_full_duplex_i2s && _config.enable_capture;
|
||||
}
|
||||
|
||||
void AudioEngine::clearToneStateIfIdle() {
|
||||
@@ -2224,7 +2437,7 @@ bool AudioEngine::probePlaybackFileFromSource(const char* path, MediaSource sour
|
||||
|
||||
bool limiter_active = false;
|
||||
float gain_db = 0.0f;
|
||||
const bool loudness_auto = _config.wav_auto_normalize_limiter;
|
||||
const bool loudness_auto = wavAutoLoudnessEnabled(_config);
|
||||
if (loudness_auto) {
|
||||
gain_db = analyzeWavLoudnessGainDb(playback_file_, wav_info, data_offset, data_size, limiter_active);
|
||||
}
|
||||
@@ -2268,29 +2481,108 @@ void AudioEngine::updateToneJitter(uint32_t now_ms) {
|
||||
}
|
||||
|
||||
bool AudioEngine::streamPlaybackChunk() {
|
||||
if (!playback_file_) {
|
||||
stopPlaybackFile();
|
||||
if (!lockPlaybackState(0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (playback_codec_ == PlaybackCodec::MP3) {
|
||||
if (mp3_decoder_ == nullptr || mp3_source_ == nullptr) {
|
||||
stopPlaybackFileUnlocked();
|
||||
unlockPlaybackState();
|
||||
return false;
|
||||
}
|
||||
} else if (!playback_file_) {
|
||||
stopPlaybackFileUnlocked();
|
||||
unlockPlaybackState();
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t now_ms = millis();
|
||||
if (playback_next_chunk_ms_ != 0U &&
|
||||
static_cast<int32_t>(now_ms - playback_next_chunk_ms_) < 0) {
|
||||
unlockPlaybackState();
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t total_source_advanced = 0U;
|
||||
size_t total_copied = 0U;
|
||||
const size_t copy_bytes = (playback_codec_ == PlaybackCodec::MP3) ? kPlaybackCopyBytesMp3 : kPlaybackCopyBytes;
|
||||
if (playback_codec_ == PlaybackCodec::MP3) {
|
||||
const uint32_t pos_before = mp3_source_->getPos();
|
||||
const bool decoder_running = mp3_decoder_->loop();
|
||||
const uint32_t pos_after = mp3_source_->getPos();
|
||||
total_source_advanced = (pos_after >= pos_before) ? (pos_after - pos_before) : 0U;
|
||||
total_copied = total_source_advanced;
|
||||
mp3_source_last_pos_ = pos_after;
|
||||
if (!decoder_running || !mp3_decoder_->isRunning()) {
|
||||
stopPlaybackFileUnlocked();
|
||||
unlockPlaybackState();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (playback_wav_direct_mode_) {
|
||||
uint8_t pcm_buf[kPlaybackCopyBytes];
|
||||
size_t wanted = kPlaybackCopyBytes;
|
||||
if (playback_data_remaining_ > 0U) {
|
||||
wanted = std::min<size_t>(wanted, playback_data_remaining_);
|
||||
}
|
||||
const size_t align = sizeof(int16_t);
|
||||
wanted = (wanted / align) * align;
|
||||
if (wanted == 0U) {
|
||||
stopPlaybackFileUnlocked();
|
||||
unlockPlaybackState();
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t pos_before = playback_file_.position();
|
||||
if (playback_codec_ == PlaybackCodec::MP3) {
|
||||
total_copied = mp3_copy_.copyBytes(copy_bytes);
|
||||
} else {
|
||||
total_copied = wav_copy_.copyBytes(copy_bytes);
|
||||
const size_t bytes_read = playback_file_.read(pcm_buf, wanted);
|
||||
if (bytes_read == 0U) {
|
||||
stopPlaybackFileUnlocked();
|
||||
unlockPlaybackState();
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t aligned_read = (bytes_read / align) * align;
|
||||
const size_t sample_count = aligned_read / sizeof(int16_t);
|
||||
const size_t samples_written =
|
||||
writePlaybackFrame(reinterpret_cast<const int16_t*>(pcm_buf), sample_count);
|
||||
total_copied = samples_written * sizeof(int16_t);
|
||||
total_source_advanced = total_copied;
|
||||
|
||||
if (total_copied < aligned_read) {
|
||||
const size_t rewind = aligned_read - total_copied;
|
||||
if (rewind > 0U) {
|
||||
const size_t safe_pos = playback_file_.position();
|
||||
if (safe_pos >= rewind) {
|
||||
playback_file_.seek(safe_pos - rewind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (playback_data_remaining_ > 0U) {
|
||||
const uint32_t consumed =
|
||||
static_cast<uint32_t>(std::min<size_t>(total_source_advanced, playback_data_remaining_));
|
||||
playback_data_remaining_ -= consumed;
|
||||
if (playback_data_remaining_ == 0U) {
|
||||
stopPlaybackFileUnlocked();
|
||||
unlockPlaybackState();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const size_t pos_after = playback_file_.position();
|
||||
if (pos_after <= pos_before || !playback_file_.available()) {
|
||||
stopPlaybackFileUnlocked();
|
||||
unlockPlaybackState();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const size_t copy_bytes = kPlaybackCopyBytes;
|
||||
const size_t pos_before = playback_file_.position();
|
||||
total_copied = wav_copy_.copyBytes(copy_bytes);
|
||||
const size_t pos_after = playback_file_.position();
|
||||
total_source_advanced = (pos_after >= pos_before) ? (pos_after - pos_before) : 0U;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t progress_bytes = (total_source_advanced > 0U) ? total_source_advanced : total_copied;
|
||||
if (progress_bytes > 0U) {
|
||||
@@ -2300,6 +2592,16 @@ bool AudioEngine::streamPlaybackChunk() {
|
||||
|
||||
if (progress_bytes > 0U) {
|
||||
uint32_t next_delay_ms = 1U;
|
||||
if (playback_codec_ == PlaybackCodec::MP3 && playback_mp3_bitrate_bps_ > 0U) {
|
||||
const uint64_t bits = static_cast<uint64_t>(progress_bytes) * 8ULL;
|
||||
const uint64_t chunk_ms_u64 =
|
||||
std::max<uint64_t>(1ULL, (bits * 1000ULL) / static_cast<uint64_t>(playback_mp3_bitrate_bps_));
|
||||
const uint32_t chunk_ms =
|
||||
(chunk_ms_u64 > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()))
|
||||
? std::numeric_limits<uint32_t>::max()
|
||||
: static_cast<uint32_t>(chunk_ms_u64);
|
||||
next_delay_ms = std::max<uint32_t>(1U, chunk_ms / 2U);
|
||||
} else {
|
||||
const uint32_t bytes_per_sample = std::max<uint32_t>(1U, playback_input_audio_info_.bits_per_sample / 8U);
|
||||
const uint32_t channels = std::max<uint32_t>(1U, playback_input_audio_info_.channels);
|
||||
const uint32_t bytes_per_frame = bytes_per_sample * channels;
|
||||
@@ -2308,19 +2610,30 @@ bool AudioEngine::streamPlaybackChunk() {
|
||||
const uint32_t frames = static_cast<uint32_t>(progress_bytes / bytes_per_frame);
|
||||
if (frames > 0U) {
|
||||
const uint32_t chunk_ms = std::max<uint32_t>(1U, (frames * 1000U) / input_rate);
|
||||
next_delay_ms = std::max<uint32_t>(1U, chunk_ms / 2U);
|
||||
// WAV flow is already light; pacing at real-time avoids saturating I2S buffers.
|
||||
next_delay_ms = chunk_ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
playback_next_chunk_ms_ = now_ms + next_delay_ms;
|
||||
unlockPlaybackState();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (playback_codec_ == PlaybackCodec::MP3) {
|
||||
playback_next_chunk_ms_ = now_ms + 1U;
|
||||
unlockPlaybackState();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!playback_file_.available()) {
|
||||
stopPlaybackFile();
|
||||
stopPlaybackFileUnlocked();
|
||||
unlockPlaybackState();
|
||||
return false;
|
||||
}
|
||||
|
||||
playback_next_chunk_ms_ = now_ms + 1U;
|
||||
unlockPlaybackState();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user