Skip to content

embedded_esp32s3: add pluggable audio sources and internal test tone#37

Open
ovelhaaa wants to merge 2 commits into
mainfrom
codex/plan-implementation-for-esp32s3-interface-vsl18e
Open

embedded_esp32s3: add pluggable audio sources and internal test tone#37
ovelhaaa wants to merge 2 commits into
mainfrom
codex/plan-implementation-for-esp32s3-interface-vsl18e

Conversation

@ovelhaaa

@ovelhaaa ovelhaaa commented Apr 15, 2026

Copy link
Copy Markdown
Owner

Motivation

  • Introduce an audio source abstraction so the DSP can process either external I2S input or an internal test signal for development and diagnostics.
  • Propagate the selected source from the parameter bridge into the real-time audio callback so the running engine can switch sources without reconfiguring I2S.

Description

  • Added audio_source.h and audio_source.cpp implementing I2sInputSource, InternalTestTriangleSource, and the AudioSourceType enum.
  • Extended ParameterBridge to carry a sourceType setting via a new atomic sourceType_ and to snapshot AudioParams::sourceType in publish/consumeIfUpdated.
  • Refactored the audio path in app_main.cpp to use a templated processWithSource and updateActiveSource, switched the audio callback to prepare and render from the selected source, and initialized the internal test source parameters.
  • Updated CMakeLists.txt to include the new main/audio_source.cpp and set AudioEngineEsp32::Config.enableRx to false when using the internal source by default.

Testing

  • Ran the ESP-IDF build for the embedded_esp32s3 target with idf.py build, which completed successfully.
  • Ran CMake configuration and a local cmake --build/ninja build of the target tree, which completed successfully.

Codex Task

Summary by CodeRabbit

  • New Features

    • Audio source can now be dynamically switched via configuration parameters during runtime.
  • Configuration Changes

    • Default audio source switched to internal test mode.
    • Audio receive (RX) functionality disabled; transmit (TX) remains enabled.

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The pull request adds audio source type configuration to the parameter bridge system, allowing runtime audio source switching. Changes include extending AudioParams with a sourceType field, updating ParameterBridge to synchronize this parameter, and modifying the embedded application to default to internal test source and support dynamic source switching via the parameter system.

Changes

Cohort / File(s) Summary
Parameter Bridge Infrastructure
targets/embedded_esp32s3/main/parameter_bridge.h, targets/embedded_esp32s3/main/parameter_bridge.cpp
Added AudioSourceType sourceType field to AudioParams struct and std::atomic<AudioSourceType> sourceType_ member to ParameterBridge class to manage parameter publication and consumption of audio source type.
Application Integration
targets/embedded_esp32s3/main/app_main.cpp
Changed default audio source from ExternalI2s to InternalTest, updated audioCallback to read and apply sourceType from parameters, disabled RX in audio engine configuration while keeping TX enabled.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

  • ovelhaaa/orbit-echo#9: Introduces the initial ESP32-S3 embedded code structure that this PR directly extends with audio source type parameterization.
  • ovelhaaa/orbit-echo#27: Modifies the ParameterBridge parameter publication/consumption logic; both PRs update the same parameter handling code paths.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding pluggable audio sources and switching the default to an internal test tone generator.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/plan-implementation-for-esp32s3-interface-vsl18e

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ovelhaaa

Copy link
Copy Markdown
Owner Author

@coderabbitai resolve merge conflicts

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Merge conflicts resolved successfully!

Resolved 1 conflict file(s). Commit: eb0593d0657a3ad17f00f7b591dbe65f43136c5b pushed to codex/plan-implementation-for-esp32s3-interface-vsl18e.

3 file operation(s)
  • targets/embedded_esp32s3/main/app_main.cpp (update)
  • targets/embedded_esp32s3/main/parameter_bridge.cpp (update)
  • targets/embedded_esp32s3/main/parameter_bridge.h (update)
View agent analysis
I'll start by inspecting the git state and examining the conflicting file.

The conflict is fully resolved. Here's a summary of what was done:

**Conflict analysis:**

- **Stage 2 (main)**: Had initial audio params set to `Accidental` read mode, board_config pin centralization, and T9 integration. Used hardcoded UI task values.
- **Stage 3 (PR branch)**: Introduced pluggable audio source abstraction — `AudioSourceType` enum, `I2sInputSource`/`InternalTestTriangleSource` types, templated `processWithSource<TSource>()`, `updateActiveSource()`, and board_config constants for UI task configuration.

**Resolution:**

Both sets of changes were merged together:

1. **PR's audio source abstraction fully preserved** — `AppContext` carries `sourceType` and `activeSource` pointer; `audioCallback()` calls `updateActiveSource()` and dispatches via `processWithSource<>()`; the switch between `I2sSource` and `InternalTest` is intact.
2. **Main branch's initial params kept** — `AudioParams::readMode` initialized to `Accidental`, consistent with the recent commit (`3501d6c`).
3. **Board_config constants used for UI** — `board::ui::kRefreshPeriodMs`, `board::ui::kTaskCore`, `board::ui::kTaskPriority` replace hardcoded values (both sides agreed on this direction).
4. **Default source** set to `InternalTest` (matching the most recent commit message: "Default embedded audio source to internal test triangle").

`git ls-files -u` is empty — no unmerged paths remain. The file is staged and ready for the merge commit.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a flexible audio source system for the ESP32-S3 target, enabling runtime switching between external I2S input and an internal triangle wave generator. Key changes include the addition of I2sInputSource and InternalTestTriangleSource classes, and updates to the audio callback and parameter bridge to support source selection. Review feedback identifies a configuration issue where disabling RX prevents external audio input and suggests optimizing the triangle wave generator by pre-calculating the phase increment to avoid per-sample floating-point division.

AudioEngineEsp32::Config audioCfg;
audioCfg.sampleRate = board::audio::kSampleRate;
audioCfg.enableRx = true;
audioCfg.enableRx = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Setting enableRx to false prevents the I2S driver from being initialized with receive capabilities. This contradicts the PR's stated goal of allowing runtime switching to an external I2S source without reconfiguration. If enableRx is false, the I2sInputSource will receive a null pointer and produce silence when selected. Consider enabling RX by default to support seamless switching.

Suggested change
audioCfg.enableRx = false;
audioCfg.enableRx = true;

outL = sample;
outR = sample;

phase_ += frequencyHz_ / sampleRateHz_;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Performing a floating-point division (frequencyHz_ / sampleRateHz_) inside the renderFrame method is inefficient as it executes for every audio sample. It is recommended to pre-calculate this value (e.g., as a phaseIncrement_ member) whenever frequencyHz_ or sampleRateHz_ is updated via their respective setters.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c4f18c35ad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

AudioEngineEsp32::Config audioCfg;
audioCfg.sampleRate = board::audio::kSampleRate;
audioCfg.enableRx = true;
audioCfg.enableRx = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep I2S RX enabled when external source is selectable

Setting audioCfg.enableRx = false disables I2S capture for the lifetime of the audio engine, so when sourceType is switched to ExternalI2s the callback still receives inInterleaved == nullptr (see AudioEngineEsp32::audioTaskLoop), and I2sInputSource therefore outputs silence. This means the new runtime source-selection path cannot actually produce external input unless the engine is reinitialized, which this change does not do.

Useful? React with 👍 / 👎.

Resolved conflicts in:
- targets/embedded_esp32s3/main/app_main.cpp (content)

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
targets/embedded_esp32s3/main/app_main.cpp (1)

138-143: ⚠️ Potential issue | 🟡 Minor

uiTick overwrites sourceType to default on every publish.

The local AudioParams p is default-initialized with sourceType = InternalTest. Since uiTick publishes partial updates without preserving the current sourceType, any runtime source change would be reverted on the next UI tick.

If uiTick is only meant to cycle visual/demo parameters, consider preserving sourceType (and other "stable" params) from the current state, or refactor to publish incremental updates.

Suggested approach: preserve current sourceType
 void uiTick(void* userData) {
     auto* app = static_cast<AppContext*>(userData);
     static uint32_t tick = 0;
     ++tick;

     AudioParams p;
+    // Preserve current source type
+    p.sourceType = app->sourceType;
     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);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@targets/embedded_esp32s3/main/app_main.cpp` around lines 138 - 143, uiTick is
creating a fresh AudioParams p which default-initializes sourceType
(InternalTest) and then app->params.publish(p), unintentionally overwriting any
runtime source selection; fix by first reading the current params from the
params store (e.g., via app->params.get()/read()/value() or whatever accessor
exists), copy over stable fields like sourceType (and any other non-UI fields)
into the local AudioParams p before mutating mix/feedback/toneHz/smearAmount,
then call app->params.publish(p) so only the intended UI/demo values are
updated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@targets/embedded_esp32s3/main/app_main.cpp`:
- Line 183: The runtime crash occurs because audioCfg.enableRx is false so the
audio engine passes nullptr as inInterleaved and
I2sInputSource::prepare(nullptr) stores a null pointer; fix by either enabling
RX at init or adding a guard to prevent switching to ExternalI2s when RX was
disabled: set audioCfg.enableRx = true in the initialization to always provide
valid input buffers, or in updateActiveSource()/audioCallback check
audioCfg.enableRx (or the saved rx-enabled flag) and ignore/rollback any request
to set AudioSourceType::ExternalI2s; ensure I2sInputSource::prepare never stores
a nullptr (add a null-check) so renderFrame() cannot dereference it.

---

Outside diff comments:
In `@targets/embedded_esp32s3/main/app_main.cpp`:
- Around line 138-143: uiTick is creating a fresh AudioParams p which
default-initializes sourceType (InternalTest) and then app->params.publish(p),
unintentionally overwriting any runtime source selection; fix by first reading
the current params from the params store (e.g., via
app->params.get()/read()/value() or whatever accessor exists), copy over stable
fields like sourceType (and any other non-UI fields) into the local AudioParams
p before mutating mix/feedback/toneHz/smearAmount, then call
app->params.publish(p) so only the intended UI/demo values are updated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3e42ff3b-4557-4f7e-8a4e-8865b176a523

📥 Commits

Reviewing files that changed from the base of the PR and between 4b5757a and eb0593d.

📒 Files selected for processing (3)
  • targets/embedded_esp32s3/main/app_main.cpp
  • targets/embedded_esp32s3/main/parameter_bridge.cpp
  • targets/embedded_esp32s3/main/parameter_bridge.h

AudioEngineEsp32::Config audioCfg;
audioCfg.sampleRate = board::audio::kSampleRate;
audioCfg.enableRx = true;
audioCfg.enableRx = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Runtime switch to ExternalI2s will fail when RX is disabled at init.

With enableRx = false, the audio engine passes nullptr as inInterleaved to the callback (per audio_engine_esp32.cpp:168). If a user switches to AudioSourceType::ExternalI2s via the parameter bridge at runtime, I2sInputSource::prepare(nullptr) stores a null pointer, and subsequent renderFrame() calls will dereference it—causing a crash or undefined behavior.

Options to resolve:

  1. Prevent runtime switching to ExternalI2s when RX is disabled – add a guard in updateActiveSource() or audioCallback that ignores the source change request if RX was not enabled at init.
  2. Enable RX unconditionally – set enableRx = true so I2S input is always available, even if unused.
  3. Document the constraint – if internal-only is the intended permanent mode, remove or stub out the ExternalI2s path entirely.
Option 1: Guard against invalid source switch
+// Store RX capability at init
+struct AppContext {
+    ...
+    bool rxEnabled = false;
+    ...
+};

 // In app_main, after audioCfg setup:
+app.rxEnabled = audioCfg.enableRx;

 // In audioCallback or updateActiveSource:
 void updateActiveSource(AppContext* app) {
     if (app->activeSourceType == app->sourceType) {
         return;
     }
+    // Prevent switch to ExternalI2s if RX not available
+    if (app->sourceType == AudioSourceType::ExternalI2s && !app->rxEnabled) {
+        return;
+    }
     app->activeSourceType = app->sourceType;
     ...
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@targets/embedded_esp32s3/main/app_main.cpp` at line 183, The runtime crash
occurs because audioCfg.enableRx is false so the audio engine passes nullptr as
inInterleaved and I2sInputSource::prepare(nullptr) stores a null pointer; fix by
either enabling RX at init or adding a guard to prevent switching to ExternalI2s
when RX was disabled: set audioCfg.enableRx = true in the initialization to
always provide valid input buffers, or in updateActiveSource()/audioCallback
check audioCfg.enableRx (or the saved rx-enabled flag) and ignore/rollback any
request to set AudioSourceType::ExternalI2s; ensure I2sInputSource::prepare
never stores a nullptr (add a null-check) so renderFrame() cannot dereference
it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant