feat(music): synthesize seeded ambient background loops - #173
DonIsmaelito wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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>
| 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" |
| 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(): |
There was a problem hiding this comment.
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>
| 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(): |
| ) | ||
| 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): |
There was a problem hiding this comment.
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>
| if isinstance(key, bool) or not isinstance(key, int): | |
| if ( | |
| isinstance(key, bool) | |
| or not isinstance(key, int) | |
| or not 0 <= key <= 127 | |
| ): |
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
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.