diff --git a/docs/ui/screen_bypass.svg b/docs/ui/screen_bypass.svg
new file mode 100644
index 0000000..6d3591b
--- /dev/null
+++ b/docs/ui/screen_bypass.svg
@@ -0,0 +1,8 @@
+
diff --git a/docs/ui/screen_feedback.svg b/docs/ui/screen_feedback.svg
new file mode 100644
index 0000000..2a5c60b
--- /dev/null
+++ b/docs/ui/screen_feedback.svg
@@ -0,0 +1,15 @@
+
diff --git a/docs/ui/screen_mix.svg b/docs/ui/screen_mix.svg
new file mode 100644
index 0000000..e1e6615
--- /dev/null
+++ b/docs/ui/screen_mix.svg
@@ -0,0 +1,15 @@
+
diff --git a/targets/embedded_esp32s3/main/app_main.cpp b/targets/embedded_esp32s3/main/app_main.cpp
index 7957090..c1f12b9 100644
--- a/targets/embedded_esp32s3/main/app_main.cpp
+++ b/targets/embedded_esp32s3/main/app_main.cpp
@@ -130,16 +130,10 @@ void audioCallback(void* userData, const int32_t* inInterleaved, int32_t* outInt
}
void uiTick(void* userData) {
- auto* app = static_cast(userData);
- static uint32_t tick = 0;
- ++tick;
-
- AudioParams p;
- p.mix = 0.30f + 0.15f * ((tick / 120) % 2);
- p.feedback = 0.40f;
- p.toneHz = 6500.0f;
- p.smearAmount = 0.20f;
- app->params.publish(p);
+ // Opcional: A UI task já trata seus inputs e manda os parâmetros pro bridge.
+ // O uiTick agora poderia ser usado apenas para side-effects ou heartbeats,
+ // mas o loop principal da UI task gerencia o estado através de UiTft::updateState().
+ // Removendo a lógica dummy que alterava os parâmetros.
}
} // namespace
@@ -190,6 +184,7 @@ extern "C" void app_main(void) {
uiCfg.refreshPeriodMs = board::ui::kRefreshPeriodMs;
uiCfg.core = board::ui::kTaskCore;
uiCfg.priority = board::ui::kTaskPriority;
+ uiCfg.paramBridge = &app.params;
if (!app.ui.start(uiCfg, uiTick, &app)) {
ESP_LOGW(kTag, "UI não iniciou; áudio segue ativo");
diff --git a/targets/embedded_esp32s3/main/ui_tft.cpp b/targets/embedded_esp32s3/main/ui_tft.cpp
index 98ceac5..e7c23e0 100644
--- a/targets/embedded_esp32s3/main/ui_tft.cpp
+++ b/targets/embedded_esp32s3/main/ui_tft.cpp
@@ -3,6 +3,11 @@
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
+#include "driver/gpio.h"
+#include "board_config.h"
+
+#include
+#include
namespace orbit::embedded {
namespace {
@@ -22,6 +27,21 @@ bool UiTft::start(const Config& config, UiTickCallback callback, void* userData)
callback_ = callback;
userData_ = userData;
+ // Configuração dos GPIOs para o Encoder e Botão Bypass
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << board::encoder::kA) |
+ (1ULL << board::encoder::kB) |
+ (1ULL << board::encoder::kSwitch) |
+ (1ULL << board::controls::kBypassButton);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&io_conf);
+
+ // Incializa o estado do encoder
+ lastEncoderA_ = gpio_get_level(static_cast(board::encoder::kA));
+
if (!stoppedSignal_) {
stoppedSignal_ = xSemaphoreCreateBinary();
if (!stoppedSignal_) {
@@ -72,14 +92,139 @@ void UiTft::taskEntry(void* ctx) {
static_cast(ctx)->taskLoop();
}
+void UiTft::processInputs() {
+ uint32_t now = xTaskGetTickCount() * portTICK_PERIOD_MS;
+
+ // Encoder - Leitura baseada em polling de quadratura
+ int a = gpio_get_level(static_cast(board::encoder::kA));
+ int b = gpio_get_level(static_cast(board::encoder::kB));
+
+ if (a != lastEncoderA_) {
+ if (a == 0) { // Na borda de descida de A
+ if (b == 1) {
+ encoderPos_++; // CW
+ } else {
+ encoderPos_--; // CCW
+ }
+ }
+ lastEncoderA_ = a;
+ }
+
+ // Encoder Switch (Debounced)
+ bool switchState = gpio_get_level(static_cast(board::encoder::kSwitch));
+ if (switchState != lastSwitchState_) {
+ if (now - switchDebounceTime_ > 50) { // 50ms debounce
+ if (switchState == 0) { // Pressionado (pull-up)
+ switchPressed_ = true;
+ }
+ lastSwitchState_ = switchState;
+ switchDebounceTime_ = now;
+ }
+ }
+
+ // Bypass Button (Debounced)
+ bool bypassState = gpio_get_level(static_cast(board::controls::kBypassButton));
+ if (bypassState != lastBypassState_) {
+ if (now - bypassDebounceTime_ > 50) { // 50ms debounce
+ if (bypassState == 0) { // Pressionado (pull-up)
+ bypassPressed_ = true;
+ }
+ lastBypassState_ = bypassState;
+ bypassDebounceTime_ = now;
+ }
+ }
+}
+
+void UiTft::updateState() {
+ bool paramsUpdated = false;
+
+ // Trata o botão Bypass
+ if (bypassPressed_) {
+ isBypassed_ = !isBypassed_;
+ bypassPressed_ = false;
+
+ // Em caso de bypass, podemos mudar o readMode ou zerar o mix.
+ // Aqui assumimos zerar o mix como forma de bypass.
+ if (isBypassed_) {
+ preBypassMix_ = currentParams_.mix;
+ currentParams_.mix = 0.0f;
+ } else {
+ currentParams_.mix = preBypassMix_;
+ }
+ paramsUpdated = true;
+ }
+
+ // Trata o botão do Encoder (Muda a página)
+ if (switchPressed_) {
+ int nextPage = static_cast(currentPage_) + 1;
+ if (nextPage >= static_cast(Page::Count)) {
+ nextPage = 0;
+ }
+ currentPage_ = static_cast(nextPage);
+ switchPressed_ = false;
+ }
+
+ // Trata a rotação do Encoder (Muda os valores da página atual)
+ if (encoderPos_ != 0) {
+ if (!isBypassed_) { // Ignora rotações no bypass, ou desabilita bypass?
+ switch (currentPage_) {
+ case Page::Mix:
+ currentParams_.mix = std::clamp(currentParams_.mix + (encoderPos_ * 0.05f), 0.0f, 1.0f);
+ break;
+ case Page::Feedback:
+ currentParams_.feedback = std::clamp(currentParams_.feedback + (encoderPos_ * 0.05f), 0.0f, 1.0f);
+ break;
+ case Page::Time:
+ // 24000.0f is kMaxDelaySamples. It would be better to share it, but for now we clamp against it.
+ currentParams_.offsetSamples = std::clamp(currentParams_.offsetSamples + (encoderPos_ * 1000.0f), 0.0f, 24000.0f);
+ break;
+ case Page::Tone:
+ currentParams_.toneHz = std::clamp(currentParams_.toneHz + (encoderPos_ * 500.0f), 200.0f, 20000.0f);
+ break;
+ default:
+ break;
+ }
+ paramsUpdated = true;
+ }
+ encoderPos_ = 0;
+ }
+
+ // Publica se houve mudança real e o ParameterBridge estiver configurado
+ if (paramsUpdated && config_.paramBridge) {
+ config_.paramBridge->publish(currentParams_);
+ }
+}
+
+void UiTft::drawDisplay() {
+ // Stub: Simular o desenho do display
+ // Em uma implementação real usaria SPI para enviar os dados da tela
+}
+
+
void UiTft::taskLoop() {
const TickType_t periodTicks = pdMS_TO_TICKS(config_.refreshPeriodMs);
TickType_t lastWake = xTaskGetTickCount();
+ // Sincroniza parâmetros iniciais
+ if (config_.paramBridge) {
+ // Se o paramBridge tivesse método getter, a gente puxava.
+ // Vamos inicializar com alguns valores padrão conhecidos,
+ // alinhados com o estado default da struct AudioParams
+ currentParams_.mix = 0.35f;
+ currentParams_.feedback = 0.35f;
+ currentParams_.offsetSamples = 1200.0f;
+ currentParams_.toneHz = 8000.0f;
+ }
+
while (running_) {
+ processInputs();
+ updateState();
+ drawDisplay();
+
if (callback_) {
callback_(userData_);
}
+
vTaskDelayUntil(&lastWake, periodTicks);
}
diff --git a/targets/embedded_esp32s3/main/ui_tft.h b/targets/embedded_esp32s3/main/ui_tft.h
index dff7740..50e849b 100644
--- a/targets/embedded_esp32s3/main/ui_tft.h
+++ b/targets/embedded_esp32s3/main/ui_tft.h
@@ -5,6 +5,7 @@
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
+#include "parameter_bridge.h"
namespace orbit::embedded {
@@ -15,6 +16,7 @@ class UiTft {
uint32_t stackWords = 4096;
uint32_t priority = 3;
int core = 1;
+ ParameterBridge* paramBridge = nullptr;
};
using UiTickCallback = void (*)(void* userData);
@@ -29,12 +31,42 @@ class UiTft {
static void taskEntry(void* ctx);
void taskLoop();
+ void processInputs();
+ void updateState();
+ void drawDisplay();
+
Config config_{};
UiTickCallback callback_ = nullptr;
void* userData_ = nullptr;
TaskHandle_t taskHandle_ = nullptr;
SemaphoreHandle_t stoppedSignal_ = nullptr;
bool running_ = false;
+
+ // Encoder State
+ int lastEncoderA_ = 1;
+ int encoderPos_ = 0;
+ bool switchPressed_ = false;
+ bool lastSwitchState_ = true;
+ uint32_t switchDebounceTime_ = 0;
+
+ // Bypass State
+ bool bypassPressed_ = false;
+ bool isBypassed_ = false;
+ bool lastBypassState_ = true;
+ uint32_t bypassDebounceTime_ = 0;
+ float preBypassMix_ = 0.35f;
+
+ // UI Parameters
+ enum class Page {
+ Mix = 0,
+ Feedback,
+ Time,
+ Tone,
+ Count
+ };
+ Page currentPage_ = Page::Mix;
+
+ AudioParams currentParams_;
};
} // namespace orbit::embedded