Skip to content

Tone App - #48

Open
NellowTCS wants to merge 7 commits into
TactilityProject:mainfrom
NellowTCS:tone-app
Open

NellowTCS wants to merge 7 commits into
TactilityProject:mainfrom
NellowTCS:tone-app

Conversation

@NellowTCS

@NellowTCS NellowTCS commented Sep 26, 2026 •

Copy link
Copy Markdown

Adds a Tone Generator app.

Images

CL-32

IMG_0830 IMG_0831

CYD-2.2"

IMG_0829 IMG_0828

Cardputer

IMG_0826 IMG_0827

T-Deck

IMG_0822 IMG_0823

Summary by CodeRabbit

  • New Features
    • Added Tone, a signal generator with sine, square, saw, triangle, and noise waveforms.
    • Choose from frequency presets or enter a custom frequency, configure a frequency sweep, and select mono or stereo output.
    • Adjust volume and control playback with live frequency and status readouts. Frequencies are limited to 20 Hz–20 kHz, and the interface adapts to different layouts.

@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The pull request adds Tone as a Tactility app, with CMake configuration, a manifest, and feature documentation. It adds five waveform generators, fixed-frequency and sweep selection, and task-based audio playback with start, stop, and status functions. The interface provides frequency, waveform, channel, sweep, and volume controls, plus playback readouts. The app initializes its context and event handling, then stops playback and cleans up when it receives a close event.


Priority: ⬇️ Low

Merge Risk

Merge Risk: 🟡 Moderate · up to 652a5

Closing the Tone app while audio is stopping can free synchronization state that the audio task is still using. This can crash or corrupt memory on the device. The fix is a small reordering in the playback task and should be made before merging.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 5546e

Playback can overlap during a quick stop and restart, and closing the app may finish before its playback task has stopped using app-owned state. These are locally triggered risks; broader device impact is not established.

Retained concerns

  • Medium · security · inferred: Stop does not await the old playback task. An immediate restart can replace the shared stream handle while that task still runs; its cleanup can then close the new stream, or both tasks can continue against shared state.
  • High · security · inferred: Shutdown uses a null task handle as its completion signal, but the playback task clears that handle before its final Context and UI accesses. Closing the app can therefore destroy state still in use by the task.
  • Medium · reliability · inferred: Closing the app waits for a playback task that may be inside an audio write with an unbounded timeout. If the device does not complete or interrupt that write, the stop flag cannot complete shutdown.

Security review details

Security Blast Radius

  • inferred — The demonstrated trigger is local interaction with Tone's controls or app close, and the immediate affected resources are its task, UI state, and selected audio stream. Host-wide impact or remote reachability is not established.

Security Findings and Attack Paths

  • inferred — Quick stop and restart can make concurrent playback instances share a replaceable stream handle. Closing during playback can also release app-owned state after the task signals completion but before its final accesses. Both paths require local app interaction; no privilege escalation is demonstrated.

Trust Boundaries and Controls

  • observed — The callbacks send bounded local UI selections to audio-device operations. No network, tenant selector, credential use, or authorization change appears in the examined Tone paths.

Resilience and Maintainability Implications

  • inferred — A stop request alone does not interrupt the visible unbounded write. The resulting shutdown risk depends on audio-driver behavior that was not established; the analogous SfxEngine write uses a finite timeout.

Hardening Proposals

  • proposed — Make stream ownership per playback instance, prevent restart until the prior instance finishes, and signal completion only after all Context and UI accesses are done. Establish a bounded or cancellable write path before waiting for shutdown.



🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 11.54% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 52 functions across 8 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly identifies the main change: adding the Tone app. It is concise and relevant to the pull request contents.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.


  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR



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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: a05f571a-0d0d-44e7-aabd-73562038d3b6

📥 Commits

Reviewing files that changed from the base of the PR and between 085c5db and 5546e2a.

📒 Files selected for processing (5)
  • .github/workflows/main.yml
  • Apps/Tone/CMakeLists.txt
  • Apps/Tone/main/CMakeLists.txt
  • Apps/Tone/main/Source/App.cpp
  • Apps/Tone/manifest.properties

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread Apps/Tone/main/Source/App.cpp Outdated
Comment thread Apps/Tone/main/Source/App.cpp Outdated
@KenVanHoeylandt

Copy link
Copy Markdown
Contributor

Neat!
Can you add a screenshot?
Ideally an extra one on a tiny device too, like a cardputer.

@KenVanHoeylandt

Copy link
Copy Markdown
Contributor

Seems like I broke the build. I'll fix it.

@KenVanHoeylandt

KenVanHoeylandt commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

@NellowTCS I fixed things. Please pull in latest from main.

You'll get a conflict with .github/workflows/main.yml: disregard your changes and accept mine instead.
I made it so that the list of apps is now automatically generated.

edit: I was able to resolve it myself.

Signed-off-by: Ken Van Hoeylandt <git@kenvanhoeylandt.net>
@NellowTCS

Copy link
Copy Markdown
Author

I'll rebase the branch, test UI + photos, and get it ready to merge today!

@NellowTCS

Copy link
Copy Markdown
Author

Today, he said 😭

@NellowTCS
NellowTCS marked this pull request as draft October 9, 2026 08:17

@coderabbitai coderabbitai 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.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 98e0cfa8-139b-4318-aee7-7dfd1c41921a
📥 Commits

Reviewing files that changed from the base of the PR and between 95ede0a and 2e37c3a.

📒 Files selected for processing (9)
  • Apps/Tone/README.md
  • Apps/Tone/main/CMakeLists.txt
  • Apps/Tone/main/Source/Tone.h
  • Apps/Tone/main/Source/TonePlayback.cpp
  • Apps/Tone/main/Source/TonePlayback.h
  • Apps/Tone/main/Source/ToneSynth.cpp
  • Apps/Tone/main/Source/ToneSynth.h
  • Apps/Tone/main/Source/ToneUi.cpp
  • Apps/Tone/main/Source/main.cpp

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread Apps/Tone/main/Source/TonePlayback.cpp Outdated
Comment thread Apps/Tone/main/Source/TonePlayback.cpp
Comment thread Apps/Tone/main/Source/ToneUi.cpp
@NellowTCS
NellowTCS marked this pull request as ready for review October 9, 2026 10:25

@coderabbitai coderabbitai 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.

Caution

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

⚠️ Outside diff range comments (1)

🟠 Major · Serialize closing with Play callbacks. · main.cpp:46-53

Apps/Tone/main/Source/main.cpp:46-53
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Serialize closing with Play callbacks.

onPlayPressed can run on the LVGL thread while the app main thread handles APP_EVENT_CLOSE. tone_playback_stop waits only for the task that exists when it checks task. A Play callback that runs after that check can start a new task with &ctx.playback. window_manager_remove can then complete and main can destroy the stack-owned Context while the new task still accesses it.

Set the closing state before stopping playback. Serialize that state with tone_playback_start so a callback cannot pass the check during the close transition.

Suggested fix
diff --git a/Apps/Tone/main/Source/Tone.h b/Apps/Tone/main/Source/Tone.h
@@
 #include <lvgl/lvgl.h>
+#include <freertos/semphr.h>
@@
 struct Context {
     AppInstanceId appInstanceId = 0;
 
     TonePlayback playback;
+    SemaphoreHandle_t playbackGate = nullptr;
+    bool closing = false;
 
     // UI-only state, touched exclusively on the LVGL thread.
diff --git a/Apps/Tone/main/Source/main.cpp b/Apps/Tone/main/Source/main.cpp
@@
     Context ctx {};
+    ctx.playbackGate = xSemaphoreCreateMutex();
+    check(ctx.playbackGate != nullptr);
     ctx.appInstanceId = appInstanceId;
@@
     }
 
     // Stop playback and join the audio task before the window (and its widgets) are destroyed.
+    xSemaphoreTake(ctx.playbackGate, portMAX_DELAY);
+    ctx.closing = true;
+    xSemaphoreGive(ctx.playbackGate);
     tone_playback_stop(&ctx.playback);
@@
     check(app_event_unsubscribe(&sub) == ERROR_NONE);
     task_event_group_destruct(&eventGroup);
+    vSemaphoreDelete(ctx.playbackGate);
 
     return 0;
diff --git a/Apps/Tone/main/Source/ToneUi.cpp b/Apps/Tone/main/Source/ToneUi.cpp
@@
 void onPlayPressed(lv_event_t* event) {
     auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
 
+    xSemaphoreTake(ctx->playbackGate, portMAX_DELAY);
+    if (ctx->closing) {
+        xSemaphoreGive(ctx->playbackGate);
+        return;
+    }
+
     if (ctx->playback.playing.load()) {
         tone_playback_stop(&ctx->playback);
         updatePlayButton(ctx);
         updateChannelAvailability(ctx);
         updateReadout(ctx);
+        xSemaphoreGive(ctx->playbackGate);
         return;
     }
 
     const error_t result = tone_playback_start(&ctx->playback);
     if (result != ERROR_NONE) {
@@
             lv_label_set_text_fmt(ctx->readoutStatusLabel, "Playback failed (%s)", error_to_string(result));
         }
+        xSemaphoreGive(ctx->playbackGate);
         return;
     }
     updatePlayButton(ctx);
     updateChannelAvailability(ctx);
     updateReadout(ctx);
+    xSemaphoreGive(ctx->playbackGate);
 }

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6d2f46f3-be56-4a10-961d-574e2966f6ff
📥 Commits

Reviewing files that changed from the base of the PR and between 2e37c3a and 5f83eb7.

📒 Files selected for processing (4)
  • Apps/Tone/main/Source/TonePlayback.cpp
  • Apps/Tone/main/Source/ToneSynth.cpp
  • Apps/Tone/main/Source/ToneSynth.h
  • Apps/Tone/main/Source/ToneUi.cpp

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 15460e4e-0b23-4364-96ce-821f1af0f406
📥 Commits

Reviewing files that changed from the base of the PR and between 5f83eb7 and 652a530.

📒 Files selected for processing (5)
  • Apps/Tone/main/Source/Tone.h
  • Apps/Tone/main/Source/TonePlayback.cpp
  • Apps/Tone/main/Source/TonePlayback.h
  • Apps/Tone/main/Source/ToneUi.cpp
  • Apps/Tone/main/Source/main.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • Apps/Tone/main/Source/TonePlayback.h
  • Apps/Tone/main/Source/TonePlayback.cpp

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&eventGroup);
vSemaphoreDelete(ctx.playbackGate);
vSemaphoreDelete(ctx.playback.lifecycleMutex);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not delete the lifecycle mutex before the playback task finishes.

If the playback task pauses after clearing playback.task, tone_playback_stop() can return while the task still needs playback.lifecycleMutex. Line 64 can then delete the mutex before the task calls xSemaphoreGive(). Make task completion observable only after its final use of the mutex, including on the allocation-failure path. (github.com)

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.

@NellowTCS This seems like a valid concern.
I reviewed the rest of the code and it looks fine.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants