Skip to content

feat(smplr): add sampler instruments#212

Merged
heypoom merged 29 commits into
mainfrom
add-smplr-objects
Jul 2, 2026
Merged

feat(smplr): add sampler instruments#212
heypoom merged 29 commits into
mainfrom
add-smplr-objects

Conversation

@heypoom

@heypoom heypoom commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Add sampler instruments from the smplr library

Summary by CodeRabbit

  • New Features
    • Added sampled-instrument objects: piano~, epiano~, drums~, mallet~, mellotron~, versilian~, smolken~, plus soundfont~, soundfont2~, and multi-channel gm~.
    • Enhanced object settings with searchable comboboxes and conditional field visibility.
    • Extended pads~ with optional absolute-time scheduling for triggers.
  • Bug Fixes
    • Improved sampled-instrument playback/message behavior with reliable async load/reload handling.
    • Refined WebAudio/V2 sampler and progress synchronization.
  • Documentation
    • Added/updated docs for gm~, sampled-instrument objects, soundfont~, soundfont2~, and midi.file loaded metadata.
  • Tests
    • Added/expanded coverage for settings helpers, visibility, message normalization, runtime behavior, and GM/sampler logic.

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@heypoom, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 25 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ae3be409-a7d0-40e1-a142-8d2cf0fc0261

📥 Commits

Reviewing files that changed from the base of the PR and between 12bf228 and 1ea1e03.

📒 Files selected for processing (1)
  • ui/src/lib/audio/sampler-playback-progress.test.ts
📝 Walkthrough
📝 Walkthrough
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. 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 matches the main change: adding smplr-based sampler instruments.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-smplr-objects

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 27, 2026

Copy link
Copy Markdown

Deploying patchies with  Cloudflare Pages  Cloudflare Pages

Latest commit: 12bf228
Status: ✅  Deploy successful!
Preview URL: https://fa8e179c.patchies.pages.dev
Branch Preview URL: https://add-smplr-objects.patchies.pages.dev

View logs

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

🧹 Nitpick comments (2)
ui/src/objects/smplr/SmplrInstrumentAudioNode.ts (1)

165-175: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Avoid floating promise in applyProgramChange.

void this.applySettings(...) creates an unawaited promise; if applyLiveSettings or reload throws, it becomes an unhandled rejection. Since applyProgramChange is only called from the synchronous applyCommand, you can safely mark it async and await the call, or catch and route errors to onStatusChange.

-  private applyProgramChange(program: number): void {
+  private async applyProgramChange(program: number): Promise<void> {
     const patch = this.descriptor.handleProgramChange?.(program, {
       ...this.settings,
       instrumentNames: this.instrument?.instrumentNames
     });

     if (!patch) return;

     this.onSettingsPatch?.(patch);
-    void this.applySettings({ ...this.settings, ...patch });
+    await this.applySettings({ ...this.settings, ...patch });
   }

Then update the call site in applyCommand:

       case 'program':
-        this.applyProgramChange(command.program);
+        await this.applyProgramChange(command.program);
         break;

And mark applyCommand as async (or handle the promise in send).

-  private applyCommand(command: ReturnType<typeof normalizeSmplrMessage>): void {
+  private async applyCommand(command: ReturnType<typeof normalizeSmplrMessage>): Promise<void> {

Or, minimally, keep it synchronous but surface the error:

-    void this.applySettings({ ...this.settings, ...patch });
+    this.applySettings({ ...this.settings, ...patch }).catch((err) => {
+      this.onStatusChange?.({ state: 'error', message: String(err) });
+    });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/src/objects/smplr/SmplrInstrumentAudioNode.ts` around lines 165 - 175, The
`applyProgramChange` flow in `SmplrInstrumentAudioNode` is creating a floating
promise by calling `applySettings` with `void`, which can surface as an
unhandled rejection. Make `applyProgramChange` async and await the
`applySettings` call, then update the synchronous `applyCommand` path (and any
direct caller like `send`) to await or otherwise handle the returned promise; if
you keep it sync, catch errors from `applySettings` and route them through
`onStatusChange` instead of dropping them.
ui/src/objects/smplr/descriptors.test.ts (1)

42-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

This test only asserts a declaration shape, not behavior.

Checking typeof descriptor.loadInstrument === 'function' inspects an implementation detail rather than an observable outcome. Either drop it or convert it into a behavioral assertion (e.g., that invoking a descriptor's loader stays lazy / doesn't eagerly import heavy modules). As per coding guidelines: "Do NOT create tests that only inspect source text, declarations, prompts, imports, or implementation details."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/src/objects/smplr/descriptors.test.ts` around lines 42 - 46, The current
test in smplrDescriptors only checks the declaration shape of loadInstrument
instead of observable behavior. Update the descriptors test to either remove
this assertion or replace it with a behavioral test against smplrDescriptors
that verifies the loader stays lazy (for example, invoking a descriptor’s loader
does not eagerly import heavy modules). Focus the change in the descriptors test
suite and the loadInstrument-related descriptor entries.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/design-docs/specs/165-smplr-sampled-instruments.md`:
- Around line 277-292: The spec for the Soundfont2 parser currently shows a
direct constructor call, but the implementation uses a closure-based adapter via
createSoundfont passed into module.Soundfont2(). Update the spec to describe the
local adapter pattern used by the Soundfont2 integration, making clear that
parser construction is wrapped in an indirection layer. Reference the soundfont2
parser section and the createSoundfont adapter so the documented contract
matches the actual API-evolution-resistant implementation.
- Around line 81-103: The SmplrInstrumentDescriptor spec is out of sync with the
real descriptor contract, so update the interface to match the implementation
used by SmplrInstrumentDescriptor and loadInstrument: include the missing
destination and onLoadProgress parameters, the getDisplayName(settings) shape,
and defaultVelocity, and remove updateLiveSetting if it is not part of the
canonical type. If this doc is meant to stay simplified, add an explicit note
that it is non-canonical and point readers to the shared descriptor definition
in smplr/descriptors for the authoritative interface.

In `@ui/src/lib/components/settings/ObjectSettings.svelte`:
- Around line 405-413: The trigger markup in ObjectSettings.svelte nests a
<button> inside Popover.Trigger, which already renders a button and creates
invalid interactive content. Update the Popover.Trigger usage around
getSelectedOptionLabel(field) and ChevronsUpDown to use the child snippet
pattern or apply the button styling directly to Popover.Trigger so there is only
one interactive element.

In `@ui/src/lib/nodes/defaultNodeData.ts`:
- Around line 395-397: The node defaults mapping in defaultNodeData should not
return the shared descriptor object from smplrDescriptors[type].defaultSettings.
Update the logic around the settings/settingsSchema creation so each node gets a
fresh deep copy of defaultSettings before it is assigned, using the existing
descriptor lookup in the mapping callback. Preserve settingsSchema as-is, but
ensure the returned settings value is cloned per node to avoid shared nested
state like soundfont2~ instrumentNames leaking across instances.

In `@ui/src/objects/smplr/descriptors.ts`:
- Around line 240-256: The SF2 loader in loadInstrument is dropping progress
updates because it omits onLoadProgress and passes a no-op into commonOptions,
so the status UI never advances during SoundFont2 loading. Update the
soundfont2~ descriptor to accept onLoadProgress from the loader args and forward
that real callback through commonOptions (and any Soundfont2 progress hook
exposed by smplr) instead of using an empty function, keeping behavior
consistent with the other descriptor loaders.

---

Nitpick comments:
In `@ui/src/objects/smplr/descriptors.test.ts`:
- Around line 42-46: The current test in smplrDescriptors only checks the
declaration shape of loadInstrument instead of observable behavior. Update the
descriptors test to either remove this assertion or replace it with a behavioral
test against smplrDescriptors that verifies the loader stays lazy (for example,
invoking a descriptor’s loader does not eagerly import heavy modules). Focus the
change in the descriptors test suite and the loadInstrument-related descriptor
entries.

In `@ui/src/objects/smplr/SmplrInstrumentAudioNode.ts`:
- Around line 165-175: The `applyProgramChange` flow in
`SmplrInstrumentAudioNode` is creating a floating promise by calling
`applySettings` with `void`, which can surface as an unhandled rejection. Make
`applyProgramChange` async and await the `applySettings` call, then update the
synchronous `applyCommand` path (and any direct caller like `send`) to await or
otherwise handle the returned promise; if you keep it sync, catch errors from
`applySettings` and route them through `onStatusChange` instead of dropping
them.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 64c51042-ec03-4000-bc2c-ba9d139fbe3c

📥 Commits

Reviewing files that changed from the base of the PR and between 09f6ae8 and 22e8555.

⛔ Files ignored due to path filters (2)
  • ui/bun.lock is excluded by !**/*.lock
  • ui/src/lib/generated/object-schemas.generated.ts is excluded by !**/generated/**
📒 Files selected for processing (45)
  • docs/design-docs/specs/165-smplr-sampled-instruments.md
  • docs/superpowers/plans/2026-06-27-smplr-sampled-instruments.md
  • ui/package.json
  • ui/src/lib/ai/object-descriptions-types.ts
  • ui/src/lib/ai/object-prompts/index.ts
  • ui/src/lib/audio/v2/nodes/index.ts
  • ui/src/lib/components/settings/ObjectSettings.svelte
  • ui/src/lib/data/license-data.ts
  • ui/src/lib/extensions/object-packs.ts
  • ui/src/lib/nodes/defaultNodeData.ts
  • ui/src/lib/nodes/node-types.ts
  • ui/src/lib/objects/schemas/index.ts
  • ui/src/lib/settings/index.ts
  • ui/src/lib/settings/options.test.ts
  • ui/src/lib/settings/options.ts
  • ui/src/lib/settings/types.ts
  • ui/src/objects/smplr/DrumMachineNode.svelte
  • ui/src/objects/smplr/ElectricPianoNode.svelte
  • ui/src/objects/smplr/MalletNode.svelte
  • ui/src/objects/smplr/MellotronNode.svelte
  • ui/src/objects/smplr/PianoNode.svelte
  • ui/src/objects/smplr/SmolkenNode.svelte
  • ui/src/objects/smplr/SmplrInstrumentAudioNode.test.ts
  • ui/src/objects/smplr/SmplrInstrumentAudioNode.ts
  • ui/src/objects/smplr/SmplrNodeLayout.svelte
  • ui/src/objects/smplr/Soundfont2Node.svelte
  • ui/src/objects/smplr/SoundfontNode.svelte
  • ui/src/objects/smplr/VersilianNode.svelte
  • ui/src/objects/smplr/audio-nodes.ts
  • ui/src/objects/smplr/descriptors.test.ts
  • ui/src/objects/smplr/descriptors.ts
  • ui/src/objects/smplr/messages.test.ts
  • ui/src/objects/smplr/messages.ts
  • ui/src/objects/smplr/programs.ts
  • ui/src/objects/smplr/prompt.ts
  • ui/src/objects/smplr/schema.ts
  • ui/static/content/objects/drum-machine~.md
  • ui/static/content/objects/epiano~.md
  • ui/static/content/objects/mallet~.md
  • ui/static/content/objects/mellotron~.md
  • ui/static/content/objects/piano~.md
  • ui/static/content/objects/smolken~.md
  • ui/static/content/objects/soundfont2~.md
  • ui/static/content/objects/soundfont~.md
  • ui/static/content/objects/versilian~.md

Comment thread docs/design-docs/specs/165-smplr-sampled-instruments.md
Comment thread docs/design-docs/specs/165-smplr-sampled-instruments.md
Comment thread ui/src/lib/components/settings/ObjectSettings.svelte Outdated
Comment thread ui/src/lib/nodes/defaultNodeData.ts
Comment thread ui/src/objects/smplr/descriptors.ts Outdated
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 4 file(s) based on 5 unresolved review comments.

Files modified:

  • docs/design-docs/specs/165-smplr-sampled-instruments.md
  • ui/src/lib/components/settings/ObjectSettings.svelte
  • ui/src/lib/nodes/defaultNodeData.ts
  • ui/src/objects/smplr/descriptors.ts

Commit: 0b20154cc7434129c24627ca55d39f1d260cddcc

The changes have been pushed to the add-smplr-objects branch.

Time taken: 3m 45s

heypoom and others added 2 commits June 27, 2026 23:16
Fixed 4 file(s) based on 5 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
@heypoom heypoom force-pushed the add-smplr-objects branch from 0b20154 to e22da58 Compare June 27, 2026 16:16

@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

🧹 Nitpick comments (1)
docs/superpowers/plans/2026-06-27-smplr-sampled-instruments.md (1)

13-13: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix heading level for Task 1.

### Task 1 should be ## Task 1 (or add a ## Tasks h2 before it) to comply with standard Markdown heading increment rules. This keeps the document structure consistent with markdownlint expectations.

-### Task 1: Dependencies And Spec Baseline
+## Task 1: Dependencies And Spec Baseline

(Apply the same change to all Task headings if they remain h3.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-06-27-smplr-sampled-instruments.md` at line 13,
The Task headings in this plan are using an h3 level where the document
structure expects an h2-level section or a preceding Tasks section. Update the
heading for Task 1, and any other Task headings in this document, to use
consistent Markdown hierarchy by either promoting them to ## or inserting a ##
Tasks parent heading before the task list.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ui/src/objects/smplr/messages.ts`:
- Around line 139-140: The controlChange mapping in messages.ts only clamps
msg.value, so invalid CC numbers can still pass through unchanged. Update the
controlChange branch to normalize msg.control to a valid MIDI byte as well,
alongside msg.value, before returning the cc object. Use the existing
isFiniteNumber, clampMidi, and the controlChange handling logic in this module
to keep the fix localized and consistent.

---

Nitpick comments:
In `@docs/superpowers/plans/2026-06-27-smplr-sampled-instruments.md`:
- Line 13: The Task headings in this plan are using an h3 level where the
document structure expects an h2-level section or a preceding Tasks section.
Update the heading for Task 1, and any other Task headings in this document, to
use consistent Markdown hierarchy by either promoting them to ## or inserting a
## Tasks parent heading before the task list.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 65d80ab4-ac24-48b9-b3de-6d2902d9c734

📥 Commits

Reviewing files that changed from the base of the PR and between 0b20154 and e22da58.

⛔ Files ignored due to path filters (2)
  • ui/bun.lock is excluded by !**/*.lock
  • ui/src/lib/generated/object-schemas.generated.ts is excluded by !**/generated/**
📒 Files selected for processing (45)
  • docs/design-docs/specs/165-smplr-sampled-instruments.md
  • docs/superpowers/plans/2026-06-27-smplr-sampled-instruments.md
  • ui/package.json
  • ui/src/lib/ai/object-descriptions-types.ts
  • ui/src/lib/ai/object-prompts/index.ts
  • ui/src/lib/audio/v2/nodes/index.ts
  • ui/src/lib/components/settings/ObjectSettings.svelte
  • ui/src/lib/data/license-data.ts
  • ui/src/lib/extensions/object-packs.ts
  • ui/src/lib/nodes/defaultNodeData.ts
  • ui/src/lib/nodes/node-types.ts
  • ui/src/lib/objects/schemas/index.ts
  • ui/src/lib/settings/index.ts
  • ui/src/lib/settings/options.test.ts
  • ui/src/lib/settings/options.ts
  • ui/src/lib/settings/types.ts
  • ui/src/objects/smplr/DrumMachineNode.svelte
  • ui/src/objects/smplr/ElectricPianoNode.svelte
  • ui/src/objects/smplr/MalletNode.svelte
  • ui/src/objects/smplr/MellotronNode.svelte
  • ui/src/objects/smplr/PianoNode.svelte
  • ui/src/objects/smplr/SmolkenNode.svelte
  • ui/src/objects/smplr/SmplrInstrumentAudioNode.test.ts
  • ui/src/objects/smplr/SmplrInstrumentAudioNode.ts
  • ui/src/objects/smplr/SmplrNodeLayout.svelte
  • ui/src/objects/smplr/Soundfont2Node.svelte
  • ui/src/objects/smplr/SoundfontNode.svelte
  • ui/src/objects/smplr/VersilianNode.svelte
  • ui/src/objects/smplr/audio-nodes.ts
  • ui/src/objects/smplr/descriptors.test.ts
  • ui/src/objects/smplr/descriptors.ts
  • ui/src/objects/smplr/messages.test.ts
  • ui/src/objects/smplr/messages.ts
  • ui/src/objects/smplr/programs.ts
  • ui/src/objects/smplr/prompt.ts
  • ui/src/objects/smplr/schema.ts
  • ui/static/content/objects/drum-machine~.md
  • ui/static/content/objects/epiano~.md
  • ui/static/content/objects/mallet~.md
  • ui/static/content/objects/mellotron~.md
  • ui/static/content/objects/piano~.md
  • ui/static/content/objects/smolken~.md
  • ui/static/content/objects/soundfont2~.md
  • ui/static/content/objects/soundfont~.md
  • ui/static/content/objects/versilian~.md
✅ Files skipped from review due to trivial changes (14)
  • ui/src/objects/smplr/VersilianNode.svelte
  • ui/src/objects/smplr/Soundfont2Node.svelte
  • ui/static/content/objects/mellotron~.md
  • ui/src/objects/smplr/SmolkenNode.svelte
  • ui/static/content/objects/epiano~.md
  • ui/static/content/objects/drum-machine~.md
  • ui/static/content/objects/versilian~.md
  • ui/src/objects/smplr/ElectricPianoNode.svelte
  • ui/static/content/objects/mallet~.md
  • ui/static/content/objects/piano~.md
  • ui/static/content/objects/soundfont2~.md
  • ui/static/content/objects/smolken~.md
  • ui/package.json
  • ui/static/content/objects/soundfont~.md
🚧 Files skipped from review as they are similar to previous changes (28)
  • ui/src/lib/settings/options.test.ts
  • ui/src/objects/smplr/prompt.ts
  • ui/src/objects/smplr/messages.test.ts
  • ui/src/lib/ai/object-prompts/index.ts
  • ui/src/objects/smplr/PianoNode.svelte
  • ui/src/lib/nodes/defaultNodeData.ts
  • ui/src/objects/smplr/programs.ts
  • ui/src/objects/smplr/descriptors.test.ts
  • ui/src/objects/smplr/audio-nodes.ts
  • ui/src/objects/smplr/SoundfontNode.svelte
  • ui/src/objects/smplr/DrumMachineNode.svelte
  • ui/src/objects/smplr/MalletNode.svelte
  • ui/src/lib/extensions/object-packs.ts
  • ui/src/lib/settings/index.ts
  • ui/src/objects/smplr/MellotronNode.svelte
  • ui/src/lib/audio/v2/nodes/index.ts
  • ui/src/lib/ai/object-descriptions-types.ts
  • ui/src/lib/data/license-data.ts
  • ui/src/lib/nodes/node-types.ts
  • ui/src/lib/objects/schemas/index.ts
  • ui/src/objects/smplr/SmplrInstrumentAudioNode.test.ts
  • ui/src/lib/settings/options.ts
  • ui/src/objects/smplr/descriptors.ts
  • ui/src/lib/settings/types.ts
  • ui/src/objects/smplr/schema.ts
  • ui/src/objects/smplr/SmplrNodeLayout.svelte
  • ui/src/lib/components/settings/ObjectSettings.svelte
  • ui/src/objects/smplr/SmplrInstrumentAudioNode.ts

Comment thread ui/src/objects/smplr/messages.ts Outdated

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

Caution

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

⚠️ Outside diff range comments (1)
ui/src/lib/components/settings/ObjectSettings.svelte (1)

163-170: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear the combobox filter on every close path.

comboboxQuery[field.key] is only reset after selectComboboxOption(). If the popover closes via outside click, Escape, or blur, reopening it keeps the stale filter and can hide most options immediately.

Also applies to: 414-454

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/src/lib/components/settings/ObjectSettings.svelte` around lines 163 - 170,
The combobox filter state in ObjectSettings.svelte is only cleared in
selectComboboxOption(), so closing the popover through other paths leaves
comboboxQuery[field.key] stale. Update the combobox close handling in
ObjectSettings.svelte so every close path (outside click, Escape, blur, and
option selection) also resets comboboxQuery for the affected field, using the
existing comboboxOpen/comboboxQuery state and the combobox-related handlers in
the same component.
🧹 Nitpick comments (1)
ui/src/objects/smplr/descriptors.test.ts (1)

1-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid locking descriptor order in this test.

These assertions are order-sensitive and mostly pin declaration shape, so a harmless reorder of SMPLR_OBJECT_TYPES or smplrDescriptors will fail the suite without changing behavior. Prefer asserting membership/coverage and resolving each supported type through getSmplrDescriptor(...) instead of checking exact key order.

As per coding guidelines, **/*.test.{ts,tsx}: "Do NOT add 'guardrail' tests that lock wording or code shape unless that wording is itself a user-visible product contract."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/src/objects/smplr/descriptors.test.ts` around lines 1 - 22, The smplr
descriptors test is over-constraining declaration order by asserting exact array
and object key ordering in the smplrDescriptors/SMPLR_OBJECT_TYPES checks.
Update the test to verify the supported descriptor set by membership/coverage
instead of exact order, and use getSmplrDescriptor(...) to confirm each object
type resolves correctly rather than comparing Object.keys(...) to
SMPLR_OBJECT_TYPES.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@ui/src/lib/components/settings/ObjectSettings.svelte`:
- Around line 163-170: The combobox filter state in ObjectSettings.svelte is
only cleared in selectComboboxOption(), so closing the popover through other
paths leaves comboboxQuery[field.key] stale. Update the combobox close handling
in ObjectSettings.svelte so every close path (outside click, Escape, blur, and
option selection) also resets comboboxQuery for the affected field, using the
existing comboboxOpen/comboboxQuery state and the combobox-related handlers in
the same component.

---

Nitpick comments:
In `@ui/src/objects/smplr/descriptors.test.ts`:
- Around line 1-22: The smplr descriptors test is over-constraining declaration
order by asserting exact array and object key ordering in the
smplrDescriptors/SMPLR_OBJECT_TYPES checks. Update the test to verify the
supported descriptor set by membership/coverage instead of exact order, and use
getSmplrDescriptor(...) to confirm each object type resolves correctly rather
than comparing Object.keys(...) to SMPLR_OBJECT_TYPES.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9b8b0175-7de3-43bf-a05c-61b59dc33879

📥 Commits

Reviewing files that changed from the base of the PR and between 2f0b325 and 6bec09f.

📒 Files selected for processing (10)
  • docs/design-docs/specs/165-smplr-sampled-instruments.md
  • ui/src/lib/components/settings/ObjectSettings.svelte
  • ui/src/lib/extensions/object-packs.ts
  • ui/src/lib/settings/types.ts
  • ui/src/lib/settings/visibility.test.ts
  • ui/src/lib/settings/visibility.ts
  • ui/src/objects/smplr/descriptors.test.ts
  • ui/src/objects/smplr/descriptors.ts
  • ui/src/objects/smplr/prompt.ts
  • ui/static/content/objects/soundfont~.md
✅ Files skipped from review due to trivial changes (4)
  • ui/src/lib/settings/visibility.test.ts
  • ui/src/objects/smplr/prompt.ts
  • ui/static/content/objects/soundfont~.md
  • docs/design-docs/specs/165-smplr-sampled-instruments.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • ui/src/objects/smplr/descriptors.ts

@heypoom heypoom force-pushed the add-smplr-objects branch from 9021dcb to 644dc0b Compare June 28, 2026 05:05

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

🧹 Nitpick comments (3)
docs/superpowers/plans/2026-06-28-gm-smplr-multichannel.md (1)

9-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix tech stack reference: "SvelteKit 5" → "Svelte 5".

SvelteKit versioning is independent of Svelte. The current major Svelte version is 5, but SvelteKit is at v2. This should read "Svelte 5" or "SvelteKit + Svelte 5".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-06-28-gm-smplr-multichannel.md` at line 9, Update
the tech stack line to use the correct framework version reference: replace
“SvelteKit 5” with “Svelte 5” or “SvelteKit + Svelte 5”. Adjust the “Tech Stack”
entry in the markdown so it reflects that SvelteKit and Svelte versioning are
separate, and keep the rest of the stack list unchanged.
ui/src/objects/smplr/GmAudioNode.ts (1)

180-241: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer ts-pattern for SmplrCommand dispatch.

This is discriminated-union branching on command.type, so the current switch loses the exhaustiveness guarantee the repo standard expects here. As per coding guidelines, **/*.{ts,tsx}: "Use ts-pattern for exhaustive branching on discriminated unions, enums, or mode/state values..."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/src/objects/smplr/GmAudioNode.ts` around lines 180 - 241, The SmplrCommand
dispatch in applyCommand is using a switch on command.type, which should be
rewritten to the repo’s ts-pattern style for exhaustive discriminated-union
handling. Update GmAudioNode.applyCommand to use ts-pattern matching over
SmplrCommand so every command variant is covered explicitly and future variants
are checked exhaustively, while preserving the current start, stop, stopAll, cc,
program, volume, ignored, detune, and reverse behaviors.

Source: Coding guidelines

ui/src/lib/objects/builtin-shorthands.test.ts (1)

47-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise the registry path instead of the backing array.

This only proves the array entry exists and that its inline transform() returns the expected literal. Please assert ObjectShorthandRegistry.getInstance().tryTransform('gm~') instead. As per coding guidelines, **/*.test.{ts,tsx}: Test observable behavior through public APIs, rendered UI, store state, emitted events, tool results, or user-visible outcomes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/src/lib/objects/builtin-shorthands.test.ts` around lines 47 - 58, The test
is asserting the shorthand entry directly from the backing array instead of
exercising the public registry API. Update the gm~ test in
builtin-shorthands.test.ts to use
ObjectShorthandRegistry.getInstance().tryTransform('gm~') and assert on that
returned result, so the test covers observable behavior through the registry
path rather than the inline transform function.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/plans/2026-06-28-gm-smplr-multichannel.md`:
- Around line 107-111: The focused test command for Task 5 Step 1 includes
unrelated settings tests, which should be removed from this verification step.
Update the test command in the plan so it only runs the SMPLR-focused paths
under src/objects/smplr, and leave out src/lib/settings/options.test.ts and
src/lib/settings/visibility.test.ts.
- Line 13: The task headings are using too deep a level after the top-level
heading, so update `Task 1: Channel Routing Helpers` and the other similar task
headings to the next appropriate markdown level. Adjust the heading markup in
the plan document so the section titles follow the document hierarchy correctly
and satisfy the markdown lint rule.

In `@ui/src/objects/midi.file/midi-file-player.ts`:
- Around line 312-340: The preload metadata helpers are skipping channels that
never emit a programChange, so channels using the GM default program 0 are not
warmed ahead of playback. Update getProgramStateAt and getUniqueProgramStates in
midi-file-player.ts to seed each used channel with program 0 when no prior
programChange exists, so the arrays consumed by
GmAudioNode.applyLoadedProgramMetadata() still include those channels. Keep the
existing sorting/shape of MidiFileProgramState output unchanged.

In `@ui/src/objects/smplr/gm-settings.ts`:
- Around line 48-56: The `drumInstrument` setting in `GM_SETTINGS` is shown too
broadly because `visibleWhen` only checks `source === 'soundfont'`, while
`createSoundfontInstrument()` only uses it when the kit is not `Custom`. Update
the visibility condition for the `drumInstrument` field to also exclude custom
kits so the UI matches the runtime behavior and only shows it when it can affect
Soundfont playback.

In `@ui/src/objects/smplr/GmAudioNode.ts`:
- Around line 373-381: `loadProgramInstrument()` is emitting the `ready` status
before the new channel has been added to `this.channels`, so `activeChannels` is
one behind. Update the status emission in `GmAudioNode.loadProgramInstrument()`
/ `ensureChannelInstrument()` flow so the channel is registered before calling
`onStatusChange`, or compute `activeChannels` from the pending channel count,
and keep the `updateMonitorChannel`/`onStatusChange` state in sync.
- Around line 676-678: Clamp loaded program values to the MIDI range 0..127 in
normalizeProgram so state stays aligned with program lookup behavior. Update the
GmAudioNode helper normalizeProgram to bound values above 127 as well as
negatives before writing into preloadRequests and channelState, matching the
normalization used by the program resolution helpers in programs.ts.

In `@ui/src/objects/smplr/GmNode.svelte`:
- Around line 68-71: The revert flow in revertSettings() is not handling the
audioService.send() result the same way as the normal settings write, so a
failed revert can leave persisted defaults out of sync with the runtime. Update
revertSettings() to await or otherwise handle the audioService.send(node.id,
'settings', GM_DEFAULT_SETTINGS) promise with the same success/error path used
for the regular settings save, and only commit the reverted state when the
runtime update succeeds.
- Around line 84-112: The async mount flow in onMount is not guarded against
teardown, so if the component unmounts while audioService.createNode() or the
initial audioService.send() is still pending, the continuation can still attach
runtimeNode callbacks and persist settings after onDestroy() has already cleaned
up. Add a mounted/aborted guard around the GmNode.svelte onMount sequence using
the existing node/messageContext/runtimeNode flow, and bail out before
installing onStatusChange/onMonitorChange or calling send() if onDestroy() has
run. Ensure onDestroy() marks the instance as disposed and that any
late-resolving createNode() path removes or ignores the orphaned node.

---

Nitpick comments:
In `@docs/superpowers/plans/2026-06-28-gm-smplr-multichannel.md`:
- Line 9: Update the tech stack line to use the correct framework version
reference: replace “SvelteKit 5” with “Svelte 5” or “SvelteKit + Svelte 5”.
Adjust the “Tech Stack” entry in the markdown so it reflects that SvelteKit and
Svelte versioning are separate, and keep the rest of the stack list unchanged.

In `@ui/src/lib/objects/builtin-shorthands.test.ts`:
- Around line 47-58: The test is asserting the shorthand entry directly from the
backing array instead of exercising the public registry API. Update the gm~ test
in builtin-shorthands.test.ts to use
ObjectShorthandRegistry.getInstance().tryTransform('gm~') and assert on that
returned result, so the test covers observable behavior through the registry
path rather than the inline transform function.

In `@ui/src/objects/smplr/GmAudioNode.ts`:
- Around line 180-241: The SmplrCommand dispatch in applyCommand is using a
switch on command.type, which should be rewritten to the repo’s ts-pattern style
for exhaustive discriminated-union handling. Update GmAudioNode.applyCommand to
use ts-pattern matching over SmplrCommand so every command variant is covered
explicitly and future variants are checked exhaustively, while preserving the
current start, stop, stopAll, cc, program, volume, ignored, detune, and reverse
behaviors.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8f8ea5fe-7a67-4eef-9466-9432f80f340a

📥 Commits

Reviewing files that changed from the base of the PR and between 2f0b325 and 644dc0b.

⛔ Files ignored due to path filters (1)
  • ui/src/lib/generated/object-schemas.generated.ts is excluded by !**/generated/**
📒 Files selected for processing (38)
  • docs/design-docs/specs/165-smplr-sampled-instruments.md
  • docs/superpowers/plans/2026-06-28-gm-smplr-multichannel.md
  • ui/src/lib/ai/object-descriptions-types.ts
  • ui/src/lib/ai/object-prompts/index.ts
  • ui/src/lib/components/settings/ObjectSettings.svelte
  • ui/src/lib/extensions/object-packs.ts
  • ui/src/lib/migration/migrate-patch.ts
  • ui/src/lib/migration/migrations/015-gm-to-ui-node.test.ts
  • ui/src/lib/migration/migrations/015-gm-to-ui-node.ts
  • ui/src/lib/nodes/defaultNodeData.ts
  • ui/src/lib/nodes/node-types.ts
  • ui/src/lib/objects/builtin-shorthands.test.ts
  • ui/src/lib/objects/builtin-shorthands.ts
  • ui/src/lib/objects/schemas/index.ts
  • ui/src/lib/services/NodeOperationsService.test.ts
  • ui/src/lib/settings/types.ts
  • ui/src/lib/settings/visibility.test.ts
  • ui/src/lib/settings/visibility.ts
  • ui/src/objects/midi.file/midi-file-player.test.ts
  • ui/src/objects/midi.file/midi-file-player.ts
  • ui/src/objects/midi.file/schema.ts
  • ui/src/objects/smplr/GmAudioNode.test.ts
  • ui/src/objects/smplr/GmAudioNode.ts
  • ui/src/objects/smplr/GmNode.svelte
  • ui/src/objects/smplr/SmplrNodeLayout.svelte
  • ui/src/objects/smplr/audio-nodes.ts
  • ui/src/objects/smplr/descriptors.test.ts
  • ui/src/objects/smplr/descriptors.ts
  • ui/src/objects/smplr/gm-channel-state.test.ts
  • ui/src/objects/smplr/gm-channel-state.ts
  • ui/src/objects/smplr/gm-settings.ts
  • ui/src/objects/smplr/programs.ts
  • ui/src/objects/smplr/prompt.ts
  • ui/src/objects/smplr/schema.ts
  • ui/static/content/objects/gm~.md
  • ui/static/content/objects/midi.file.md
  • ui/static/content/objects/soundfont2~.md
  • ui/static/content/objects/soundfont~.md
✅ Files skipped from review due to trivial changes (5)
  • ui/src/objects/smplr/gm-channel-state.test.ts
  • ui/src/lib/migration/migrations/015-gm-to-ui-node.test.ts
  • ui/static/content/objects/soundfont2~.md
  • ui/static/content/objects/midi.file.md
  • ui/src/lib/ai/object-descriptions-types.ts
🚧 Files skipped from review as they are similar to previous changes (14)
  • ui/src/lib/extensions/object-packs.ts
  • ui/src/objects/smplr/audio-nodes.ts
  • ui/src/lib/settings/visibility.test.ts
  • ui/src/lib/nodes/defaultNodeData.ts
  • ui/src/lib/ai/object-prompts/index.ts
  • ui/src/lib/objects/schemas/index.ts
  • ui/src/objects/smplr/prompt.ts
  • ui/src/objects/smplr/schema.ts
  • ui/src/lib/nodes/node-types.ts
  • ui/src/objects/smplr/descriptors.test.ts
  • ui/src/lib/components/settings/ObjectSettings.svelte
  • docs/design-docs/specs/165-smplr-sampled-instruments.md
  • ui/src/objects/smplr/descriptors.ts
  • ui/src/objects/smplr/SmplrNodeLayout.svelte

Comment thread docs/superpowers/plans/2026-06-28-gm-smplr-multichannel.md Outdated
Comment thread docs/superpowers/plans/2026-06-28-gm-smplr-multichannel.md
Comment thread ui/src/objects/midi.file/midi-file-player.ts
Comment thread ui/src/objects/smplr/gm-settings.ts
Comment thread ui/src/objects/smplr/GmAudioNode.ts
Comment thread ui/src/objects/smplr/GmAudioNode.ts
Comment thread ui/src/objects/smplr/GmNode.svelte Outdated
Comment thread ui/src/objects/smplr/GmNode.svelte

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

Caution

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

⚠️ Outside diff range comments (1)
ui/src/objects/pads/PadsAudioNode.ts (1)

111-155: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Keep onTrigger aligned with the scheduled start time.

triggerOn() still calls onTrigger before source.start(). When time is in the future, the pad flash now leads the actual audio, which breaks the "when a pad is triggered" contract on Line 57. Either fire the callback at the resolved start time or pass that time through so the component can schedule the flash correctly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/src/objects/pads/PadsAudioNode.ts` around lines 111 - 155, The `triggerOn`
method in `PadsAudioNode` fires `onTrigger` immediately, but it should be
aligned with the resolved `startTime` used for `source.start()` so scheduled
notes don’t flash early. Update the callback flow so `onTrigger` is invoked at
the same time as playback begins, or pass the computed start time through to the
caller so the UI can schedule the flash correctly. Keep the fix localized to
`triggerOn` and preserve the existing voice/gain handling.
🧹 Nitpick comments (1)
ui/src/objects/smplr/GmAudioNode.test.ts (1)

336-340: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid asserting Smplr constructor options directly.

expect(soundfontCalls[0]).toMatchObject(...) couples this test to the internal module.Soundfont(...) call shape instead of observable node behavior. The monitor snapshot assertion already covers the public contract; if you want stronger coverage, drive it through send()/status or monitor updates instead. As per coding guidelines, **/*.test.{ts,tsx}: "Test observable behavior through public APIs... Do NOT create tests that only inspect ... implementation details."

Also applies to: 356-356

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/src/objects/smplr/GmAudioNode.test.ts` around lines 336 - 340, The test in
GmAudioNode.test is asserting the internal Soundfont constructor options via
soundfontCalls, which is an implementation detail. Remove the direct expectation
on module.Soundfont arguments and keep the snapshot/public-API assertions; if
you need extra coverage, exercise the node through send() or monitor/status
updates and verify observable behavior from GmAudioNode instead.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ui/src/objects/smplr/GmAudioNode.ts`:
- Around line 251-253: The ensureProgramInstrument cache-hit/preload-join path
in GmAudioNode only updates channels and status, so promoted instruments can
leave the monitor idle and miss live settings updates. In the
ensureProgramInstrument flow, after registering the channel, also refresh the
monitor state and reapply live settings for the promoted instrument so it
reflects the current monitor state and any later volume changes. Use the
existing GmAudioNode methods and fields around this branch, especially
this.channels, onStatusChange, applyLiveSettings, and the monitor update logic.
- Around line 213-220: Clamp the `programChange` value before it is written to
state or shown in the monitor in the `with({ type: 'program' })` branch of
`GmAudioNode`; right now `addPreloadRequest()` normalizes the value but
`setChannelProgram()` and `updateMonitorChannel()` still receive the raw
`programCommand.program`, causing `channelState` and monitor data to diverge
from the actual loaded instrument. Use the same normalized/clamped program value
consistently for `programChannels.add(channel)`, `setChannelProgram(...)`, and
`updateMonitorChannel(...)`, relying on the existing program-handling flow in
`GmAudioNode` and `addPreloadRequest()`.

---

Outside diff comments:
In `@ui/src/objects/pads/PadsAudioNode.ts`:
- Around line 111-155: The `triggerOn` method in `PadsAudioNode` fires
`onTrigger` immediately, but it should be aligned with the resolved `startTime`
used for `source.start()` so scheduled notes don’t flash early. Update the
callback flow so `onTrigger` is invoked at the same time as playback begins, or
pass the computed start time through to the caller so the UI can schedule the
flash correctly. Keep the fix localized to `triggerOn` and preserve the existing
voice/gain handling.

---

Nitpick comments:
In `@ui/src/objects/smplr/GmAudioNode.test.ts`:
- Around line 336-340: The test in GmAudioNode.test is asserting the internal
Soundfont constructor options via soundfontCalls, which is an implementation
detail. Remove the direct expectation on module.Soundfont arguments and keep the
snapshot/public-API assertions; if you need extra coverage, exercise the node
through send() or monitor/status updates and verify observable behavior from
GmAudioNode instead.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7e265057-f21c-4cd7-8a33-8a4f9479b465

📥 Commits

Reviewing files that changed from the base of the PR and between 644dc0b and 385ebea.

📒 Files selected for processing (22)
  • docs/design-docs/specs/97-pads-drum-sampler.md
  • docs/superpowers/plans/2026-06-27-smplr-sampled-instruments.md
  • docs/superpowers/plans/2026-06-28-gm-smplr-multichannel.md
  • ui/src/lib/components/nodes/ScopeNode.svelte
  • ui/src/lib/objects/builtin-shorthands.test.ts
  • ui/src/lib/settings/types.ts
  • ui/src/lib/settings/visibility.test.ts
  • ui/src/lib/settings/visibility.ts
  • ui/src/objects/midi.file/midi-file-player.test.ts
  • ui/src/objects/midi.file/midi-file-player.ts
  • ui/src/objects/pads/PadsAudioNode.test.ts
  • ui/src/objects/pads/PadsAudioNode.ts
  • ui/src/objects/pads/prompt.ts
  • ui/src/objects/smplr/GmAudioNode.test.ts
  • ui/src/objects/smplr/GmAudioNode.ts
  • ui/src/objects/smplr/GmNode.svelte
  • ui/src/objects/smplr/SmplrNodeLayout.svelte
  • ui/src/objects/smplr/descriptors.test.ts
  • ui/src/objects/smplr/gm-settings.ts
  • ui/src/objects/smplr/messages.test.ts
  • ui/src/objects/smplr/messages.ts
  • ui/static/content/objects/pads~.md
✅ Files skipped from review due to trivial changes (4)
  • ui/src/lib/components/nodes/ScopeNode.svelte
  • docs/superpowers/plans/2026-06-27-smplr-sampled-instruments.md
  • docs/superpowers/plans/2026-06-28-gm-smplr-multichannel.md
  • ui/static/content/objects/pads~.md
🚧 Files skipped from review as they are similar to previous changes (9)
  • ui/src/lib/settings/visibility.ts
  • ui/src/lib/objects/builtin-shorthands.test.ts
  • ui/src/objects/smplr/messages.test.ts
  • ui/src/objects/smplr/gm-settings.ts
  • ui/src/objects/midi.file/midi-file-player.ts
  • ui/src/objects/midi.file/midi-file-player.test.ts
  • ui/src/objects/smplr/descriptors.test.ts
  • ui/src/objects/smplr/GmNode.svelte
  • ui/src/objects/smplr/SmplrNodeLayout.svelte

Comment thread ui/src/objects/smplr/GmAudioNode.ts
Comment thread ui/src/objects/smplr/GmAudioNode.ts

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

🧹 Nitpick comments (1)
ui/src/objects/pads/PadsAudioNode.test.ts (1)

79-79: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid asserting the internal GainNode state here.

Line 79 couples the test to PadsAudioNode's current wiring instead of a public contract. If the bang→velocity mapping needs coverage, extract that mapping into a small helper and test its input/output, while keeping this spec focused on observable effects like scheduled playback. As per coding guidelines, **/*.test.{ts,tsx} should "Test observable behavior through public APIs" and should "Do NOT create tests that only inspect ... implementation details."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/src/objects/pads/PadsAudioNode.test.ts` at line 79, The test is asserting
the internal GainNode state instead of a public behavior, so it is too coupled
to PadsAudioNode’s implementation. Move the bang-to-velocity mapping into a
small helper from PadsAudioNode and add direct input/output coverage there, then
update PadsAudioNode.test.ts to verify observable effects only, such as
scheduled playback or other public outcomes exposed by PadsAudioNode.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@ui/src/objects/pads/PadsAudioNode.test.ts`:
- Line 79: The test is asserting the internal GainNode state instead of a public
behavior, so it is too coupled to PadsAudioNode’s implementation. Move the
bang-to-velocity mapping into a small helper from PadsAudioNode and add direct
input/output coverage there, then update PadsAudioNode.test.ts to verify
observable effects only, such as scheduled playback or other public outcomes
exposed by PadsAudioNode.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ade72b67-6cdb-4442-a612-875dcb6c31c4

📥 Commits

Reviewing files that changed from the base of the PR and between 385ebea and 6966332.

⛔ Files ignored due to path filters (1)
  • ui/src/lib/generated/object-schemas.generated.ts is excluded by !**/generated/**
📒 Files selected for processing (6)
  • docs/design-docs/specs/97-pads-drum-sampler.md
  • ui/src/objects/pads/PadsAudioNode.test.ts
  • ui/src/objects/pads/PadsAudioNode.ts
  • ui/src/objects/pads/prompt.ts
  • ui/src/objects/pads/schema.ts
  • ui/static/content/objects/pads~.md
✅ Files skipped from review due to trivial changes (2)
  • ui/static/content/objects/pads~.md
  • docs/design-docs/specs/97-pads-drum-sampler.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • ui/src/objects/pads/prompt.ts
  • ui/src/objects/pads/PadsAudioNode.ts

@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

🧹 Nitpick comments (1)
ui/src/objects/pads/PadsAudioNode.ts (1)

212-241: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate timer-scheduling pattern with SamplerNode.ts.

scheduleTriggerFlash/clearTriggerTimer here reimplement the same "delay a visual callback via setTimeout, track per-key timers plus a bulk-clear Set" logic that SamplerNode.ts implements independently (schedulePlaybackStart/clearPlaybackStartTimer/playbackStartTimerSet). Consider extracting a small shared TimerRegistry/ScheduledNotifier helper (e.g. schedule(key, delayMs, cb), clear(key), clearAll()) used by both nodes to avoid maintaining two copies of the same cleanup logic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/src/objects/pads/PadsAudioNode.ts` around lines 212 - 241, The timer
scheduling in PadsAudioNode’s scheduleTriggerFlash and clearTriggerTimer
duplicates the same per-item setTimeout bookkeeping already present in
SamplerNode’s schedulePlaybackStart, clearPlaybackStartTimer, and
playbackStartTimerSet. Extract the shared delay-and-cleanup behavior into a
small reusable helper such as TimerRegistry or ScheduledNotifier with methods
like schedule, clear, and clearAll, then update both PadsAudioNode and
SamplerNode to use it. Keep the existing Voice-specific and playback-specific
callbacks, but remove the duplicated Set/timer cleanup logic from both classes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ui/src/lib/components/nodes/SamplerNode.svelte`:
- Around line 350-369: The playback progress bar logic in SamplerNode.svelte is
treating all playback as a single session, but SamplerNoteVoiceManager can have
multiple overlapping voices. Update startPlaybackProgressBar and
stopPlaybackProgressBar to track active playback sources by ID or count instead
of a single isPlaying flag, and only initialize/reset the interval when
transitioning from zero active sources to one. Ensure
onPlaybackStart/onPlaybackStop only hide or stop the bar when no sources remain
active, so overlapping notes do not cancel each other’s progress.

---

Nitpick comments:
In `@ui/src/objects/pads/PadsAudioNode.ts`:
- Around line 212-241: The timer scheduling in PadsAudioNode’s
scheduleTriggerFlash and clearTriggerTimer duplicates the same per-item
setTimeout bookkeeping already present in SamplerNode’s schedulePlaybackStart,
clearPlaybackStartTimer, and playbackStartTimerSet. Extract the shared
delay-and-cleanup behavior into a small reusable helper such as TimerRegistry or
ScheduledNotifier with methods like schedule, clear, and clearAll, then update
both PadsAudioNode and SamplerNode to use it. Keep the existing Voice-specific
and playback-specific callbacks, but remove the duplicated Set/timer cleanup
logic from both classes.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d0938936-7852-4ee2-bf51-e4b62effeaa6

📥 Commits

Reviewing files that changed from the base of the PR and between 6966332 and 6a709fd.

📒 Files selected for processing (9)
  • docs/design-docs/specs/163-sampler-scheduled-playback.md
  • docs/design-docs/specs/97-pads-drum-sampler.md
  • ui/src/lib/audio/v2/nodes/SamplerNode.test.ts
  • ui/src/lib/audio/v2/nodes/SamplerNode.ts
  • ui/src/lib/components/nodes/SamplerNode.svelte
  • ui/src/objects/pads/PadsAudioNode.test.ts
  • ui/src/objects/pads/PadsAudioNode.ts
  • ui/src/objects/smplr/GmAudioNode.test.ts
  • ui/src/objects/smplr/GmAudioNode.ts
✅ Files skipped from review due to trivial changes (2)
  • docs/design-docs/specs/163-sampler-scheduled-playback.md
  • docs/design-docs/specs/97-pads-drum-sampler.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • ui/src/objects/smplr/GmAudioNode.test.ts
  • ui/src/objects/smplr/GmAudioNode.ts

Comment thread ui/src/lib/components/nodes/SamplerNode.svelte Outdated
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 1 file(s) based on 1 unresolved review comment.

Files modified:

  • ui/src/lib/components/nodes/SamplerNode.svelte

Commit: 7fdb99515c4af7796c83a1b444bfaa3cdc83b71c

The changes have been pushed to the add-smplr-objects branch.

Time taken: 5m 9s

Fixed 1 file(s) based on 1 unresolved review comment.

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.

Caution

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

⚠️ Outside diff range comments (1)
ui/src/lib/components/nodes/SamplerNode.svelte (1)

355-407: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Auto-stop still follows only the first active voice. The global stopPlayback() path stops all sources, but activePlaybackRate/effectiveEnd are set only on the 0→1 transition, so overlapping voices with different rates or offsets can still trigger a whole-sampler stop based on the first voice’s estimate. Track end timing per source, or disable this auto-stop path once multiple voices are active.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/src/lib/components/nodes/SamplerNode.svelte` around lines 355 - 407, The
auto-stop logic in `startPlaybackProgressBar`/`stopPlaybackProgressBar` is still
using a single shared `activePlaybackRate` and `effectiveEnd` from the first
voice, so overlapping voices with different offsets or rates can incorrectly
trigger `stopPlayback()`. Update the playback tracking in `SamplerNode.svelte`
to compute end timing per source event (for example by tracking each active
voice separately) or change the interval logic to skip auto-stopping once more
than one voice is active. Keep the `stopPlayback()` and
`startPlaybackProgressBar()` paths aligned so the sampler only stops when the
actual active source(s) have finished.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@ui/src/lib/components/nodes/SamplerNode.svelte`:
- Around line 355-407: The auto-stop logic in
`startPlaybackProgressBar`/`stopPlaybackProgressBar` is still using a single
shared `activePlaybackRate` and `effectiveEnd` from the first voice, so
overlapping voices with different offsets or rates can incorrectly trigger
`stopPlayback()`. Update the playback tracking in `SamplerNode.svelte` to
compute end timing per source event (for example by tracking each active voice
separately) or change the interval logic to skip auto-stopping once more than
one voice is active. Keep the `stopPlayback()` and `startPlaybackProgressBar()`
paths aligned so the sampler only stops when the actual active source(s) have
finished.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2cd9924c-035c-4116-99cb-81f51a9494d5

📥 Commits

Reviewing files that changed from the base of the PR and between 6a709fd and 7fdb995.

📒 Files selected for processing (1)
  • ui/src/lib/components/nodes/SamplerNode.svelte

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

🧹 Nitpick comments (1)
ui/src/lib/audio/sampler-playback-progress.test.ts (1)

10-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing coverage for the loop-enabled wraparound branch.

All three tests exercise loopEnabled: false. The loopEnabled: true branch in advanceSamplerPlaybackProgress (resetting voiceProgress to loopStart) is untested, including the case where the step overshoots voice.end before wrapping.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/src/lib/audio/sampler-playback-progress.test.ts` around lines 10 - 75, Add
a test in sampler-playback-progress.test.ts that exercises the loop-enabled path
in advanceSamplerPlaybackProgress, specifically the branch that wraps
voiceProgress back to loopStart when loopEnabled is true. Use the existing
addVoice helper and the advanceSamplerPlaybackProgress function to cover an
overshoot past voice.end in a single step, then assert the voice remains active
and its progress is reset to loopStart instead of stopping playback.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@ui/src/lib/audio/sampler-playback-progress.test.ts`:
- Around line 10-75: Add a test in sampler-playback-progress.test.ts that
exercises the loop-enabled path in advanceSamplerPlaybackProgress, specifically
the branch that wraps voiceProgress back to loopStart when loopEnabled is true.
Use the existing addVoice helper and the advanceSamplerPlaybackProgress function
to cover an overshoot past voice.end in a single step, then assert the voice
remains active and its progress is reset to loopStart instead of stopping
playback.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd19ecea-b694-44a3-b759-8a4e43fef492

📥 Commits

Reviewing files that changed from the base of the PR and between 7fdb995 and 187f4d3.

📒 Files selected for processing (3)
  • ui/src/lib/audio/sampler-playback-progress.test.ts
  • ui/src/lib/audio/sampler-playback-progress.ts
  • ui/src/lib/components/nodes/SamplerNode.svelte

@heypoom heypoom force-pushed the add-smplr-objects branch from ff7a7a1 to 12bf228 Compare July 2, 2026 12:12
@heypoom heypoom merged commit db3dd05 into main Jul 2, 2026
1 of 2 checks passed
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.

1 participant