Skip to content

Add reverse-mode stability tests and reverse golden render#80

Merged
ovelhaaa merged 2 commits into
mainfrom
codex/add-test-cases-for-reverse-functionality
May 27, 2026
Merged

Add reverse-mode stability tests and reverse golden render#80
ovelhaaa merged 2 commits into
mainfrom
codex/add-test-cases-for-reverse-functionality

Conversation

@ovelhaaa

Copy link
Copy Markdown
Owner

Motivation

  • Add targeted coverage for reverse/readback mode to catch regressions in window periodicity, abrupt-delay behavior, per-channel counters in stereo, and numerical stability under high feedback.
  • Provide a deterministic golden render for reverse mode to detect sonic regressions over time.

Description

  • Expanded tests/test_orbit_delay.cpp with a maxStepDelta helper and dedicated reverse tests covering fixed-delay periodicity and finiteness, abrupt delay jumps with window-counter reset and anti-click assertion, stereo reverse behavior validated against two independent mono instances, and reverse with high feedback ensuring numerical stability.
  • Updated tests/render_golden.cpp to still write the existing baseline golden (tests/golden/orbit_delay_reference.txt) and also emit a reverse-mode golden file at tests/golden/orbit_delay_reverse_reference.txt using a deterministic reverse-mode stimulus.
  • All new tests use the public API (OrbitDelayCore methods) and only modify test sources; no production code changes were made.

Testing

  • Built and ran the test suite with cmake -S . -B build, cmake --build build -j4 and ctest --test-dir build --output-on-failure as part of CI validation.
  • An initial test run exposed a failing stereo reverse assertion which was corrected by comparing stereo output to two independent mono instances; after the fix the test binary was rebuilt.
  • Final automated test run completed with all tests passing (100% tests passed, 5/5) and the golden render executable writing both reference files successfully.

Codex Task

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@ovelhaaa, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 15 minutes and 52 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bd2a9cf7-291c-43bf-9a46-e24bbf611690

📥 Commits

Reviewing files that changed from the base of the PR and between 49f4db6 and 4387ebc.

📒 Files selected for processing (2)
  • tests/render_golden.cpp
  • tests/test_orbit_delay.cpp
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/add-test-cases-for-reverse-functionality

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.

@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 reverse-mode golden rendering and comprehensive unit tests for the OrbitDelayCore class, verifying periodic window resets, abrupt delay changes, stereo/mono equivalence, and high-feedback numerical stability. The reviewer identified a critical issue across all tests and the golden render: because the default tempo delay is too large, the delay is clamped to the buffer limits, resulting in silent outputs and trivial test passes. To resolve this, the reviewer suggests configuring the tempo BPM and note division to extreme values to reduce the base delay, allowing the signals to be properly processed and verified.

Comment thread tests/render_golden.cpp Outdated
Comment on lines +63 to +67
OrbitDelayCore reverseCore;
reverseCore.reset(48000.0f);
reverseCore.setReadMode(OrbitDelayCore::ReadMode::AccidentalReverse);
reverseCore.setOrbit(0.95f);
reverseCore.setOffsetSamples(180.0f);

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

Issue: Golden Render Outputs Only Zeros

The default tempoDelaySamples is initialized to 24000.0f (based on 120 BPM and 1.0 note division at 48kHz). Since the buffer size reverseDelayL is only 2048, the effective reverse delay is clamped to 2032.0f samples. Because the golden render only processes 384 samples, the read pointer never reaches any written input signal, resulting in a golden reference file containing only zeros (0.0f).

Solution

Configure the tempo and note division to their extreme values (320.0f BPM and 0.0625f note division) before resetting the core to reduce the base tempoDelaySamples to 562.5f. Then, set offsetSamples to -382.5f to achieve the target delay of exactly 180.0f samples. This ensures the input signal is actually processed and rendered into the golden file.

Suggested change
OrbitDelayCore reverseCore;
reverseCore.reset(48000.0f);
reverseCore.setReadMode(OrbitDelayCore::ReadMode::AccidentalReverse);
reverseCore.setOrbit(0.95f);
reverseCore.setOffsetSamples(180.0f);
OrbitDelayCore reverseCore;
reverseCore.setTempoBpm(320.0f);
reverseCore.setNoteDivision(0.0625f);
reverseCore.reset(48000.0f);
reverseCore.setReadMode(OrbitDelayCore::ReadMode::AccidentalReverse);
reverseCore.setOrbit(0.95f);
reverseCore.setOffsetSamples(-382.5f);

Comment thread tests/test_orbit_delay.cpp Outdated
Comment on lines +118 to +127
// Reverse ativo com delay fixo: periodicidade por janela + estabilidade numérica.
OrbitDelayCore reversePeriodic;
reversePeriodic.reset(48000.0f);
std::vector<float> reverseDelay(2048u, 0.0f);
if (!reversePeriodic.attachBufferMono(reverseDelay.data(), static_cast<uint32_t>(reverseDelay.size()))) {
return fail("reverse periodic test attach should succeed");
}
reversePeriodic.setMix(1.0f);
reversePeriodic.setFeedback(0.0f);
reversePeriodic.setOffsetSamples(96.0f);

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

Issue: Test Passes on Silence

The default tempoDelaySamples is 24000.0f. Since the buffer size is 2048, the delay is clamped to 2032.0f samples. Because the test only processes 257 samples, the read pointer never reaches the written impulse, and the output is entirely 0.0f. Thus, reverseA and reverseB are both entirely zero, making the assertion pass trivially on silence without actually testing the periodic window reset.

Solution

Set the tempo to 320.0f BPM and note division to 0.0625f before resetting the core to reduce tempoDelaySamples to 562.5f. Then, set offsetSamples to -434.5f to achieve a delay of exactly 128.0f samples. This makes the window size exactly 128 samples, so reverseA and reverseB will contain the actual impulse and match perfectly.

Suggested change
// Reverse ativo com delay fixo: periodicidade por janela + estabilidade numérica.
OrbitDelayCore reversePeriodic;
reversePeriodic.reset(48000.0f);
std::vector<float> reverseDelay(2048u, 0.0f);
if (!reversePeriodic.attachBufferMono(reverseDelay.data(), static_cast<uint32_t>(reverseDelay.size()))) {
return fail("reverse periodic test attach should succeed");
}
reversePeriodic.setMix(1.0f);
reversePeriodic.setFeedback(0.0f);
reversePeriodic.setOffsetSamples(96.0f);
// Reverse ativo com delay fixo: periodicidade por janela + estabilidade numérica.
OrbitDelayCore reversePeriodic;
reversePeriodic.setTempoBpm(320.0f);
reversePeriodic.setNoteDivision(0.0625f);
reversePeriodic.reset(48000.0f);
std::vector<float> reverseDelay(2048u, 0.0f);
if (!reversePeriodic.attachBufferMono(reverseDelay.data(), static_cast<uint32_t>(reverseDelay.size()))) {
return fail("reverse periodic test attach should succeed");
}
reversePeriodic.setMix(1.0f);
reversePeriodic.setFeedback(0.0f);
reversePeriodic.setOffsetSamples(-434.5f);

Comment thread tests/test_orbit_delay.cpp Outdated
Comment on lines +147 to +165
// Mudança abrupta de delay: reset da janela e continuidade sem click explosivo.
OrbitDelayCore reverseJump;
reverseJump.reset(48000.0f);
std::vector<float> reverseJumpDelay(4096u, 0.0f);
if (!reverseJump.attachBufferMono(reverseJumpDelay.data(), static_cast<uint32_t>(reverseJumpDelay.size()))) {
return fail("reverse abrupt-delay test attach should succeed");
}
reverseJump.setMix(1.0f);
reverseJump.setFeedback(0.0f);
reverseJump.setReadMode(OrbitDelayCore::ReadMode::AccidentalReverse);
reverseJump.setOffsetSamples(64.0f);
for (uint32_t i = 0; i < 160u; ++i) {
reverseJump.processSampleMono((i == 0u) ? 1.0f : 0.0f);
}
std::vector<float> beforeJump(64u, 0.0f);
for (uint32_t i = 0; i < beforeJump.size(); ++i) {
beforeJump[i] = reverseJump.processSampleMono(0.0f);
}
reverseJump.setOffsetSamples(256.0f);

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

Issue: Test Passes on Silence and Incorrect Delay Jump

Due to the default tempoDelaySamples of 24000.0f, both 64.0f and 256.0f offsets are clamped to the buffer limit of 4080.0f samples. This means the delay doesn't actually change, the window counter is not reset, and the test only passes because the output is entirely silent (0.0f).

Solution

  1. Set the tempo to 320.0f BPM and note division to 0.0625f to reduce tempoDelaySamples to 562.5f.
  2. Set the initial delay to 256.0f samples (offsetSamples = -306.5f).
  3. Set the post-jump delay to 64.0f samples (offsetSamples = -498.5f).

This ensures the delay actually changes, the window counter resets, and the output becomes periodic with a period of 64 samples, making afterJumpA and afterJumpB identical with actual signal.

    // Mudança abrupta de delay: reset da janela e continuidade sem click explosivo.
    OrbitDelayCore reverseJump;
    reverseJump.setTempoBpm(320.0f);
    reverseJump.setNoteDivision(0.0625f);
    reverseJump.reset(48000.0f);
    std::vector<float> reverseJumpDelay(4096u, 0.0f);
    if (!reverseJump.attachBufferMono(reverseJumpDelay.data(), static_cast<uint32_t>(reverseJumpDelay.size()))) {
        return fail("reverse abrupt-delay test attach should succeed");
    }
    reverseJump.setMix(1.0f);
    reverseJump.setFeedback(0.0f);
    reverseJump.setReadMode(OrbitDelayCore::ReadMode::AccidentalReverse);
    reverseJump.setOffsetSamples(-306.5f);
    for (uint32_t i = 0; i < 160u; ++i) {
        reverseJump.processSampleMono((i == 0u) ? 1.0f : 0.0f);
    }
    std::vector<float> beforeJump(64u, 0.0f);
    for (uint32_t i = 0; i < beforeJump.size(); ++i) {
        beforeJump[i] = reverseJump.processSampleMono(0.0f);
    }
    reverseJump.setOffsetSamples(-498.5f);

Comment thread tests/test_orbit_delay.cpp Outdated
Comment on lines +188 to +215
// Estéreo em reverse: contadores independentes por canal (equivalência com dois monos).
OrbitDelayCore reverseStereo;
OrbitDelayCore reverseMonoL;
OrbitDelayCore reverseMonoR;
reverseStereo.reset(48000.0f);
reverseMonoL.reset(48000.0f);
reverseMonoR.reset(48000.0f);
std::vector<float> reverseStereoL(4096u, 0.0f);
std::vector<float> reverseStereoR(4096u, 0.0f);
std::vector<float> reverseMonoDelayL(4096u, 0.0f);
std::vector<float> reverseMonoDelayR(4096u, 0.0f);
if (!reverseStereo.attachBuffers(reverseStereoL.data(), reverseStereoR.data(), static_cast<uint32_t>(reverseStereoL.size())) ||
!reverseMonoL.attachBufferMono(reverseMonoDelayL.data(), static_cast<uint32_t>(reverseMonoDelayL.size())) ||
!reverseMonoR.attachBufferMono(reverseMonoDelayR.data(), static_cast<uint32_t>(reverseMonoDelayR.size()))) {
return fail("reverse stereo/mono attach should succeed");
}
reverseStereo.setMix(1.0f);
reverseMonoL.setMix(1.0f);
reverseMonoR.setMix(1.0f);
reverseStereo.setFeedback(0.0f);
reverseMonoL.setFeedback(0.0f);
reverseMonoR.setFeedback(0.0f);
reverseStereo.setReadMode(OrbitDelayCore::ReadMode::AccidentalReverse);
reverseMonoL.setReadMode(OrbitDelayCore::ReadMode::AccidentalReverse);
reverseMonoR.setReadMode(OrbitDelayCore::ReadMode::AccidentalReverse);
reverseStereo.setOffsetSamples(112.0f);
reverseMonoL.setOffsetSamples(112.0f);
reverseMonoR.setOffsetSamples(112.0f);

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

Issue: Test Passes on Silence

The default tempoDelaySamples of 24000.0f clamps the delay to 4080.0f samples. Since the test only processes 320 samples, the output is entirely silent, and the stereo-to-mono comparison passes trivially on zeros.

Solution

Set the tempo to 320.0f BPM and note division to 0.0625f for all three instances to reduce tempoDelaySamples to 562.5f. Then, set offsetSamples to -450.5f to achieve a delay of exactly 112.0f samples. This allows the impulses to be processed and compared with actual signal.

    // Estéreo em reverse: contadores independentes por canal (equivalência com dois monos).
    OrbitDelayCore reverseStereo;
    OrbitDelayCore reverseMonoL;
    OrbitDelayCore reverseMonoR;
    reverseStereo.setTempoBpm(320.0f);
    reverseStereo.setNoteDivision(0.0625f);
    reverseMonoL.setTempoBpm(320.0f);
    reverseMonoL.setNoteDivision(0.0625f);
    reverseMonoR.setTempoBpm(320.0f);
    reverseMonoR.setNoteDivision(0.0625f);
    reverseStereo.reset(48000.0f);
    reverseMonoL.reset(48000.0f);
    reverseMonoR.reset(48000.0f);
    std::vector<float> reverseStereoL(4096u, 0.0f);
    std::vector<float> reverseStereoR(4096u, 0.0f);
    std::vector<float> reverseMonoDelayL(4096u, 0.0f);
    std::vector<float> reverseMonoDelayR(4096u, 0.0f);
    if (!reverseStereo.attachBuffers(reverseStereoL.data(), reverseStereoR.data(), static_cast<uint32_t>(reverseStereoL.size())) ||
        !reverseMonoL.attachBufferMono(reverseMonoDelayL.data(), static_cast<uint32_t>(reverseMonoDelayL.size())) ||
        !reverseMonoR.attachBufferMono(reverseMonoDelayR.data(), static_cast<uint32_t>(reverseMonoDelayR.size()))) {
        return fail("reverse stereo/mono attach should succeed");
    }
    reverseStereo.setMix(1.0f);
    reverseMonoL.setMix(1.0f);
    reverseMonoR.setMix(1.0f);
    reverseStereo.setFeedback(0.0f);
    reverseMonoL.setFeedback(0.0f);
    reverseMonoR.setFeedback(0.0f);
    reverseStereo.setReadMode(OrbitDelayCore::ReadMode::AccidentalReverse);
    reverseMonoL.setReadMode(OrbitDelayCore::ReadMode::AccidentalReverse);
    reverseMonoR.setReadMode(OrbitDelayCore::ReadMode::AccidentalReverse);
    reverseStereo.setOffsetSamples(-450.5f);
    reverseMonoL.setOffsetSamples(-450.5f);
    reverseMonoR.setOffsetSamples(-450.5f);

Comment on lines +238 to +251
// Reverse + feedback alto: estabilidade numérica.
OrbitDelayCore reverseHighFeedback;
reverseHighFeedback.reset(48000.0f);
std::vector<float> reverseHighFeedbackDelay(4096u, 0.0f);
if (!reverseHighFeedback.attachBufferMono(reverseHighFeedbackDelay.data(), static_cast<uint32_t>(reverseHighFeedbackDelay.size()))) {
return fail("reverse high-feedback test attach should succeed");
}
reverseHighFeedback.setMix(1.0f);
reverseHighFeedback.setReadMode(OrbitDelayCore::ReadMode::AccidentalReverse);
reverseHighFeedback.setFeedback(0.94f);
reverseHighFeedback.setFeedbackDrive(12.0f);
reverseHighFeedback.setFeedbackNonlinearAmount(1.0f);
reverseHighFeedback.setFeedbackCompThreshold(0.25f);
reverseHighFeedback.setToneHz(4200.0f);

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

Issue: Feedback Recirculation Not Tested

With the default tempoDelaySamples of 24000.0f, the delay is clamped to 4080.0f samples. Since the test only processes 2048 samples, the feedback loop never recirculates the signal even once. This fails to test the numerical stability of the high feedback path.

Solution

Set the tempo to 320.0f BPM and note division to 0.0625f to reduce tempoDelaySamples to 562.5f. Then, set offsetSamples to -462.5f to achieve a delay of exactly 100.0f samples. This allows the feedback loop to recirculate the signal over 20 times within the 2048-sample run, properly testing numerical stability.

// Reverse + feedback alto: estabilidade numérica.
    OrbitDelayCore reverseHighFeedback;
    reverseHighFeedback.setTempoBpm(320.0f);
    reverseHighFeedback.setNoteDivision(0.0625f);
    reverseHighFeedback.reset(48000.0f);
    std::vector<float> reverseHighFeedbackDelay(4096u, 0.0f);
    if (!reverseHighFeedback.attachBufferMono(reverseHighFeedbackDelay.data(), static_cast<uint32_t>(reverseHighFeedbackDelay.size()))) {
        return fail("reverse high-feedback test attach should succeed");
    }
    reverseHighFeedback.setMix(1.0f);
    reverseHighFeedback.setReadMode(OrbitDelayCore::ReadMode::AccidentalReverse);
    reverseHighFeedback.setFeedback(0.94f);
    reverseHighFeedback.setFeedbackDrive(12.0f);
    reverseHighFeedback.setFeedbackNonlinearAmount(1.0f);
    reverseHighFeedback.setFeedbackCompThreshold(0.25f);
    reverseHighFeedback.setToneHz(4200.0f);
    reverseHighFeedback.setOffsetSamples(-462.5f);

@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: aa3e1c5c66

ℹ️ 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".

Comment thread tests/test_orbit_delay.cpp Outdated
Comment on lines +127 to +131
reversePeriodic.setOffsetSamples(96.0f);
reversePeriodic.setReadMode(OrbitDelayCore::ReadMode::AccidentalReverse);
reversePeriodic.processSampleMono(1.0f);
std::vector<float> reverseA(128u, 0.0f);
std::vector<float> reverseB(128u, 0.0f);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Drive reverse tests with a reachable delay window

This setup leaves tempoDelaySamples at its default (~24000), and reverse mode computes delay as tempoDelay + offset before clamping to the buffer range. With a 2048-sample buffer, that clamps near the guard band, so the next 256 samples after the impulse are silence (reverseA/reverseB are both zeros), making the periodic-reset assertion pass vacuously even if reverse window reset logic regresses. Please set tempo/note-division/orbit (or offset) so the tested window actually contains signal before comparing the two segments.

Useful? React with 👍 / 👎.

@ovelhaaa
ovelhaaa merged commit 15fd7ae into main May 27, 2026
2 checks passed
@ovelhaaa
ovelhaaa deleted the codex/add-test-cases-for-reverse-functionality branch May 27, 2026 01:18
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