Skip to content

feat(music): synthesize seeded ambient background loops - #173

Open
DonIsmaelito wants to merge 4 commits into
browser-use:mainfrom
DonIsmaelito:submit/music
Open

DonIsmaelito wants to merge 4 commits into
browser-use:mainfrom
DonIsmaelito:submit/music

Conversation

@DonIsmaelito

@DonIsmaelito DonIsmaelito commented Sep 17, 2026

Copy link
Copy Markdown

Why

A video sometimes needs a simple background track without sourcing a recording or calling a paid service. This adds a local command that synthesizes an ambient loop.

Changes

  • Generate a seeded four-chord pad with bass pulses and soft noise ticks as a mono 48 kHz WAV.
  • Validate tempo, notes, duration settings and sample peak before synthesis. Use sample-based timing and short fades at the loop boundary.
  • Protect existing output files and publish only a completed WAV.
  • Four focused commits cover synthesis, delivery tests, documentation and guidance on when the agent should choose this helper. All 43 branch tests pass, including 27 Music cases.

Limits

This produces one procedural ambient style. It does not analyze beats, duck dialogue, normalize loudness or mix a final soundtrack. The peak setting controls sample amplitude rather than perceived loudness. Listening review remains pending.

The whole loop is held in memory, with supported settings capped at 153.6 seconds. Output publication requires filesystem hard-link support. Reproducibility is tested within the current environment, not across NumPy versions.

This targets main independently. It creates audio rather than replacing the recording-mixing work in #167, #39, #15 or #153. No external recordings, fonts or other media assets are bundled, and no API calls are made.

@DonIsmaelito
DonIsmaelito marked this pull request as ready for review September 17, 2026 22:53

@cubic-dev-ai cubic-dev-ai 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.

3 issues found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/test_music_delivery.py">

<violation number="1" location="tests/test_music_delivery.py:74">
P2: When a writer follows the dangling symlink and creates its target, this assertion still passes because it checks only the link type. Also verify that the symlink target remains absent.</violation>
</file>

<file name="helpers/music_bed.py">

<violation number="1" location="helpers/music_bed.py:68">
P3: When a progression compensates for it, the key check accepts an out-of-range MIDI key such as `-1`. Reject keys outside the MIDI range before validating the derived chord roots.</violation>

<violation number="2" location="helpers/music_bed.py:142">
P2: When `write_wav` receives a complex sample array, it silently drops the imaginary component during PCM encoding and writes different audio. Reject complex samples before the finite-value and range checks.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

out.write_bytes(b"keep")
with pytest.raises(FileExistsError):
music_bed.write_wav(np.zeros(10), out)
assert out.is_symlink() if symlink else out.read_bytes() == b"keep"

@cubic-dev-ai cubic-dev-ai Bot Sep 17, 2026

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: When a writer follows the dangling symlink and creates its target, this assertion still passes because it checks only the link type. Also verify that the symlink target remains absent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/test_music_delivery.py, line 74:

<comment>When a writer follows the dangling symlink and creates its target, this assertion still passes because it checks only the link type. Also verify that the symlink target remains absent.</comment>

<file context>
@@ -0,0 +1,128 @@
+        out.write_bytes(b"keep")
+    with pytest.raises(FileExistsError):
+        music_bed.write_wav(np.zeros(10), out)
+    assert out.is_symlink() if symlink else out.read_bytes() == b"keep"
+
+
</file context>
Suggested change
assert out.is_symlink() if symlink else out.read_bytes() == b"keep"
assert (out.is_symlink() and not out.resolve().exists()) if symlink else out.read_bytes() == b"keep"
Fix with cubic

Comment thread helpers/music_bed.py
if out.suffix.lower() != ".wav":
raise ValueError("output must have a wav extension")
samples = np.asarray(samples)
if samples.ndim != 1 or not samples.size or not np.isfinite(samples).all():

@cubic-dev-ai cubic-dev-ai Bot Sep 17, 2026

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: When write_wav receives a complex sample array, it silently drops the imaginary component during PCM encoding and writes different audio. Reject complex samples before the finite-value and range checks.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helpers/music_bed.py, line 142:

<comment>When `write_wav` receives a complex sample array, it silently drops the imaginary component during PCM encoding and writes different audio. Reject complex samples before the finite-value and range checks.</comment>

<file context>
@@ -0,0 +1,201 @@
+    if out.suffix.lower() != ".wav":
+        raise ValueError("output must have a wav extension")
+    samples = np.asarray(samples)
+    if samples.ndim != 1 or not samples.size or not np.isfinite(samples).all():
+        raise ValueError("samples must be a nonempty finite mono array")
+    if np.max(np.abs(samples)) > 1:
</file context>
Suggested change
if samples.ndim != 1 or not samples.size or not np.isfinite(samples).all():
if samples.ndim != 1 or not samples.size or np.iscomplexobj(samples) or not np.isfinite(samples).all():
Fix with cubic

Comment thread helpers/music_bed.py
)
if not math.isfinite(peak_dbfs) or not -60 <= peak_dbfs <= 0:
raise ValueError("peak dbfs must be between minus 60 and zero")
if isinstance(key, bool) or not isinstance(key, int):

@cubic-dev-ai cubic-dev-ai Bot Sep 17, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: When a progression compensates for it, the key check accepts an out-of-range MIDI key such as -1. Reject keys outside the MIDI range before validating the derived chord roots.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helpers/music_bed.py, line 68:

<comment>When a progression compensates for it, the key check accepts an out-of-range MIDI key such as `-1`. Reject keys outside the MIDI range before validating the derived chord roots.</comment>

<file context>
@@ -0,0 +1,201 @@
+        )
+    if not math.isfinite(peak_dbfs) or not -60 <= peak_dbfs <= 0:
+        raise ValueError("peak dbfs must be between minus 60 and zero")
+    if isinstance(key, bool) or not isinstance(key, int):
+        raise ValueError("key must be an integer MIDI note")
+    if len(progression) != 4 or any(
</file context>
Suggested change
if isinstance(key, bool) or not isinstance(key, int):
if (
isinstance(key, bool)
or not isinstance(key, int)
or not 0 <= key <= 127
):
Fix with cubic

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