Skip to content

build -> master - #15

Merged
jerryschen31 merged 62 commits into
masterfrom
build
Jul 19, 2026
Merged

jerryschen31 merged 62 commits into
masterfrom
build

Conversation

@jerryschen31

Copy link
Copy Markdown
Owner

No description provided.

jerryschen31 and others added 30 commits May 21, 2026 16:55
Biome (2.5.4) replaces the ESLint+Prettier pair from the feature notes:
one tool, VCS-aware ignores, 2-space/double-quote style matching the
existing code. Existing sources reformatted to the enforced style.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
commit-msg hook runs commitlint (config-conventional); pre-commit runs
biome check so style violations never reach CI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Runs install (frozen lockfile), build, typecheck, lint, and tests on
every PR and on pushes to master/build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
commitlint 21.x needs Node >=22.12; engines.node and .nvmrc now reflect
that instead of the stale >=20 floor. Husky shebang review comments
declined — v9 shims source these files, see docs/pr-reviews/pr1.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Next.js App Router app deployed via @opennextjs/cloudflare with custom
domains webplay.io + www. Vitest renders the page server-side as a CI
smoke test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
deploy-web.yml runs opennextjs-cloudflare deploy on pushes to
master/build touching apps/web. Feature 1 status doc records done items,
deviations (Workers not Vercel), and deferred work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Least-privilege permissions block on both workflows, per Copilot review
on PR #2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- fetchWithRetry: per-attempt timeout, exponential backoff with jitter,
  Retry-After support; retries 429/5xx/network errors only.
- env helper gates credentialed providers; .env.example documents vars.
- ElevenLabsMusicAdapter (POST /v1/music, mp3 output to temp file);
  registered in the generator CLI, which now loads the repo-root .env.
- 15 new unit tests (mocked fetch); live smoke test under test:live,
  excluded from CI.

Live status: free-tier key returns HTTP 402 paid_plan_required — the
Music API needs a paid plan; live test passes once upgraded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per Copilot review on PR #3: clamp attempts to >=1, combine caller
AbortSignal with the timeout signal (and never retry caller aborts),
cancel unconsumed retryable response bodies, and only use provider
request-ids in file paths when they match a safe charset. Five new
tests cover each behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…) and bench harness

LocalPythonAdapter runs uv-managed Python generator scripts as argv-only
subprocesses; scripts print a JSON result line (LocalScriptResult
contract). Priority adapters: acestep (ACE-Step 1.5 checkout via
ACESTEP_PROJECT_DIR, turbo variant) and stable-audio-open (gated HF
weights, needs HF_TOKEN). musicgen is benchmark-only — Meta's weights
are CC-BY-NC, so it is marked licenseTerms:restricted and must not
enter commercial rotation.

pnpm bench runs the Feature 2 robustness protocol: N sequential
generations per provider with latency/failure/cost recorded to
bench-results/ as JSON+CSV.

15 new unit tests (mocked subprocess); zero model downloads in CI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…isolation

ACE-Step script now receives --project-root explicitly from the adapter
instead of guessing from env/cwd; toCsv escapes quotes per RFC 4180
(test added); local adapter test fixtures moved into a per-process
mkdtemp dir to avoid cross-worker collisions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PyPI's latest is 0.0.x — the >=1.0.0 pin made uv dependency resolution
fail before any install.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
stable-audio-tools 0.0.x pins wandb 0.15.4 -> pathtools, whose build
imports the stdlib imp module removed in 3.12; uv otherwise selects
3.14 and the env build fails.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First real M4 run OOM'd MPS (11.7GiB model vs 18.1GiB cap): enable
offload_to_cpu/offload_dit_to_cpu per the repo's own 16GB-tier defaults
plus torchao int8_weight_only (overridable via --quantization none).
Also ignore runtime .cache/ dirs the model writes. Verified: 30s lofi
track generated end-to-end in ~217s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3/3 runs, 226s mean per 30s clip cold; ~37s pure inference — model
load dominates, so the Phase 1 generator worker must keep the model
resident.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three real-run fixes: pytorch-lightning is imported by
stable_audio_tools at inference time but only ships in its [train]
extra; torchaudio.save needs the soundfile backend on macOS; and
DiffusionTransformer.apg_project upcasts to float64, which MPS lacks —
patched to run that method with double aliased to float32. Verified:
45s stereo clip generated end-to-end in ~513s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Records what Feature 2's local-model work delivered (adapters, bench,
16GB fixes), M4 results for ACE-Step and Stable Audio Open, owner
listening feedback, and the path to webplay.io playback: 4-5 min
ACE-Step tracks, resident-model worker, R2 + Liquidsoap crossfade +
edge Worker HLS per the Phase 1 notes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
chore: repo tooling and CI quality gates (Phase 0 F1, PR 1/2)
feat: hello world at webplay.io with auto-deploy (Phase 0 F1, PR 2/2)
feat: ElevenLabs Music adapter + retry/env infrastructure (Phase 0 F2, PR 3)
feat: local model adapters + bench harness (Phase 0 F2, PR 4)
- provider-licensing.md: verified license posture per provider (HF
  metadata + live API evidence), launch-gate confirmation items.
- cost-model-generation.md: EL-only ruled out (~$394/mo vs $80
  budget); local-primary ~$0; blended premium option ~$26.
- listening-test.md: protocol + rating template; owner's informal
  verdicts recorded.
- phase0-feature2-decision.md: GO — ACE-Step primary, SAO textures,
  EL optional premium, MusicGen excluded.
- notes/: dated revision blocks aligning phase0-f3 and phase1-f1..f6
  with reality — CF Workers hosting, local-primary generation on the
  M4, resident-model worker, ffmpeg-prerendered milestone 0 before the
  Liquidsoap droplet, deferred observability accounts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
wrangler.jsonc in phase1-feature3 steps; generator-on-Mac + simple job
table in phase1-feature2 architecture section, per Copilot review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
docs: phase 0 feature 2 close-out — licensing, cost model, decision (PR 5)
apps/edge-worker serves /stream/{channel}/{file} from the
webplay-stream R2 bucket at stream.webplay.io with never-stale playlist
and immutable segment caching, CORS, and a /health probe (7 tests).
pnpm hls crossfades a directory of tracks with ffmpeg acrossfade,
chops VOD HLS segments, and uploads playlist+segments to R2 via the
Cloudflare API (3 tests). Also makes the acestep factory test hermetic
— it assumed no ACE-Step checkout exists, which is now false on the
dev machine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same pattern as deploy-web.yml, scoped to apps/edge-worker paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lib ES2022+WebWorker for explicit worker globals; preflight allows
Range and exposes ETag; 405s carry CORS + Allow; parseStreamPath now
only serves .m3u8/.ts/.aac so unrelated bucket objects can't leak.
buildCrossfadeFilter moved to hlslib.ts — importing it from the test
was executing the hls CLI main() at module load.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat: edge streaming worker + milestone-0 crossfade playlist (Phase 1 F3 + F1-M0)
hls.js player (Safari-native fallback) wired to the live
stream.webplay.io HLS rotation, with play/pause button, spacebar
toggle, autoplay-block handling, and VOD-loop so the milestone-0
rotation feels continuous. Background is images/lofi-beats-1 (owner
art), recompressed 2.4MB PNG -> 400KB JPEG, with all three images
staged in public/backgrounds for the rotating-backgrounds idea
(docs/ideas/rotating-backgrounds.md).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jerryschen31 and others added 23 commits July 17, 2026 21:18
fix: lengthen loop fade-out to 4 seconds
feat: unique segment prefix per hls rotation build
Live tracks intermittently had non-diatonic piano chords and off-beat
bass. Root causes on the generation side: the caption was too thin to
constrain harmony/tempo, and the model ran with no key/BPM conditioning.

- acestep_generate.py: expose --keyscale, --bpm, --inference-steps and
  --guidance-scale (were hardcoded), wired into GenerationParams and
  echoed in the result JSON for observability.
- local.ts: enrich the lofi caption (instruments, production, groove,
  "in key/in time") via a genre-profile map, and add a per-call
  musicalArgs hook so model-specific flags never leak to other adapters.
- local-models.ts: aceStepMusicalArgs resolves keyscale + BPM from the
  genre profile (or explicit BPM-range midpoint); steps/guidance stay at
  turbo defaults unless ACESTEP_* env engages the non-turbo base path.
- keyscale + BPM apply on turbo (no cost regression); guidance/steps are
  the opt-in base-model quality tier (ACESTEP_VARIANT=acestep-v15-base).
- Document the new env knobs; add unit tests for the caption enrichment,
  the musicalArgs passthrough, and env overrides.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
buildLocalPrompt used the genre-profile BPM even when the request carried
an explicit BPM range, while aceStepMusicalArgs derived the range midpoint
for --bpm — so the caption could say "75 BPM" while the model got
--bpm 85. Give an explicit request BPM precedence over the profile default
in the caption too, matching the structured-arg precedence. Add a test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The genre-profile caption from #10 ("clean / steady / in time / no
dissonance") fixed the off chords and off-beat bass but sounded sterile.
An owner A/B (2026-07-17) landed on warmer, tape-saturated wording, which
is now live on webplay.io. Bake it into GENRE_PROFILES.lofi so every
future `pnpm generate` lofi track inherits the vibe instead of the
sterile default. keyscale=C Major + bpm=75 still lock harmony/tempo, so
the caption is free to describe character. Update the caption test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…controls

feat: ACE-Step harmony/rhythm controls to tame off chords & bass
feat: fold approved "true Lofi Beats" wording into the lofi profile
.env.example was a comment-only template (no secret values); drop it from
the tree and ignore .env.* so no env files are tracked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
.env.example was removed from the tree, taking the Cloudflare/Kinde/
provider/local-generation env documentation with it. Move that reference
into docs/environment.md and repoint the requireEnv() error message at it
so the config surface (incl. the ACESTEP_* generation knobs) stays
discoverable without tracking any .env file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the Calm Music for Sleep channel profile (F Major, 58 BPM, beatless
airy/heavenly caption palette) and exposes the already-supported seed
parameter through pnpm generate for reproducible renders.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Channel config lives in apps/web/lib/channels.ts; ChannelShell (client)
owns the selected channel and renders background, tagline and a
glassmorphic pill dropdown. AudioPlayer now takes streamUrl as a prop
and is remounted per channel. Adding a channel is one config entry
plus a background image.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First-draft palette (choir aahs, string swells, cathedral reverb)
missed the vibe; replaced with the owner-supplied deep-sleep-ambient
wording. Adds the compressed calm-sleep-1 background and points the
sleep channel at it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Analysis of the reference (librosa): spectral centroid ~300 Hz, pulse
clarity 0.4, 0.1% percussive energy, ~2 dB RMS variance — a deep dark
steady drone. Drafts v1/v2 measured 5-25x off on all four. Caption now
uses dark/still/drone wording, and GenreProfile.bpm becomes optional:
beatless genres must not send --bpm, which conditions a beat grid in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop "no drums, no percussion, no beat" (with CFG the caption still
contains "drums"); "beatless" carries the constraint as a positive
tag. Switcher labels use owner wording: "Lofi Beats for Study" /
"Calm Music for Sleep".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat: Calm Music for Sleep channel + channel switcher
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…istry

- jazz genre profile in adapters: Bb Major, 90 BPM relaxed swing, lounge
  trio + tenor sax caption (positive tags only)
- stream live at stream.webplay.io/stream/jazz/playlist.m3u8 (3x300s
  crossfaded, build prefix r1784415298873, 224 segments)
- channel switcher entry + jazz-1.jpg background (owner-supplied)
- docs/channel-seeds.md: per-channel seed registry for reproducing live
  tracks and rendering future ones

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat: jazz channel "Jazz for Lounging"
- CreatorMenu client component: glassmorphic hamburger button fixed
  top-left, sliding left drawer with "Created by Jerry Chen" header and
  external links to Github and Buy me a Coffee (new tab, noopener)
- click-away backdrop closes the drawer; matches existing pill styling
- README rewritten for curious listeners arriving from webplay.io:
  describes the generate/curate/stream pipeline and repo layout without
  naming specific tools or services

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat: creator hamburger menu + listener-facing README
@jerryschen31 jerryschen31 self-assigned this Jul 19, 2026
Copilot AI review requested due to automatic review settings July 19, 2026 00:12

Copilot AI 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.

Pull request overview

This PR syncs the build branch into master by introducing the repo’s monorepo tooling + CI/CD, plus the core building blocks for webplay: adapter-based music generation, a generator CLI/bench/HLS builder, an edge Worker to serve HLS from R2, and a minimal Next.js player.

Changes:

  • Establish pnpm workspace + shared TypeScript/Biome/commitlint/husky configuration and GitHub Actions (CI + deploy).
  • Add @webplay/adapters (local Python model runner + ElevenLabs adapter + retry/env utilities) with unit + live-test separation.
  • Add generator tooling (CLI, bench harness, offline HLS build+upload) plus the Cloudflare edge worker and web UI.

Reviewed changes

Copilot reviewed 89 out of 96 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tsconfig.base.json Shared strict TS base config
README.md Expanded project overview and structure
pnpm-workspace.yaml Defines pnpm workspace packages
packages/adapters/vitest.live.config.ts Live-test vitest config + .env loading
packages/adapters/vitest.config.ts Default vitest config excluding live tests
packages/adapters/tsconfig.json Adapters TS build config
packages/adapters/test/mock.test.ts Mock adapter unit tests
packages/adapters/test/local.test.ts Local adapter + prompt/tests
packages/adapters/test/local-models.test.ts Local model factory + musical args tests
packages/adapters/test/live/elevenlabs.live.test.ts ElevenLabs live smoke test
packages/adapters/test/http.test.ts fetch retry logic unit tests
packages/adapters/test/elevenlabs.test.ts ElevenLabs adapter unit tests
packages/adapters/src/types.ts Core adapter types + AdapterError
packages/adapters/src/mock.ts Mock WAV-synth adapter
packages/adapters/src/local.ts Local uv/python adapter + genre profiles
packages/adapters/src/local-models.ts Local model factory helpers
packages/adapters/src/index.ts Package public exports
packages/adapters/src/http.ts fetchWithRetry + backoff utilities
packages/adapters/src/env.ts Env helpers + credential checks
packages/adapters/src/elevenlabs.ts ElevenLabs Music adapter
packages/adapters/package.json Adapters package scripts/metadata
package.json Repo-level scripts + tooling deps
notes/phase1-feature6.md Phase 1 feature 6 revisions
notes/phase1-feature4.md Phase 1 feature 4 revisions
notes/phase1-feature3.md Phase 1 feature 3 revisions
notes/phase1-feature2.md Phase 1 feature 2 revisions
notes/phase1-feature1.md Phase 1 feature 1 revisions
notes/phase0-feature3.md Phase 0 feature 3 revisions
notes/phase0-feature2.md Phase 0 feature 2 status update
notes/phase0-feature1-status.md Phase 0 feature 1 status doc
docs/provider-licensing.md Provider licensing summary + open items
docs/pr-reviews/pr9.md Stored review notes for PR #9
docs/pr-reviews/pr7.md Stored review notes for PR #7
docs/pr-reviews/pr6.md Stored review notes for PR #6
docs/pr-reviews/pr5.md Stored review notes for PR #5
docs/pr-reviews/pr4.md Stored review notes for PR #4
docs/pr-reviews/pr3.md Stored review notes for PR #3
docs/pr-reviews/pr2.md Stored review notes for PR #2
docs/pr-reviews/pr10.md Stored review notes for PR #10
docs/pr-reviews/pr1.md Stored review notes for PR #1
docs/phase0-feature2-local-generation.md Phase 0 Feature 2 recap/results
docs/phase0-feature2-decision.md Provider decision doc + conditions
docs/listening-test.md Listening test protocol scaffold
docs/ideas/rotating-backgrounds.md Idea doc for rotating backgrounds
docs/environment.md Environment variable documentation
docs/cost-model-generation.md Generation cost model doc
docs/channel-seeds.md Channel seed registry doc
docs/bench-m4-local.md Local generation benchmark results
commitlint.config.mjs Commitlint conventional config
biome.json Biome lint/format configuration
apps/web/wrangler.jsonc Web worker wrangler config
apps/web/vitest.config.ts Web vitest config (JSX transform)
apps/web/tsconfig.json Web app TS config
apps/web/test/page.test.tsx SSR smoke tests for landing page
apps/web/test/audio-player.test.tsx AudioPlayer unit tests
apps/web/package.json Web app dependencies/scripts
apps/web/open-next.config.ts OpenNext Cloudflare config
apps/web/next.config.ts Next.js config stub
apps/web/lib/channels.ts Channel definitions + defaults
apps/web/components/CreatorMenu.tsx Slide-out creator links menu
apps/web/components/ChannelShell.tsx Channel picker + page shell
apps/web/components/AudioPlayer.tsx HLS audio player w/ loop + fade
apps/web/app/page.tsx Main page composition
apps/web/app/layout.tsx Next layout + metadata
apps/generator/tsconfig.json Generator TS config
apps/generator/test/hls.test.ts HLS crossfade filter tests
apps/generator/test/benchlib.test.ts Bench harness tests
apps/generator/src/registry.ts Adapter registry + lookup
apps/generator/src/hlslib.ts ffmpeg acrossfade filter builder
apps/generator/src/hls.ts Offline HLS build + optional R2 upload
apps/generator/src/cli.ts Generator CLI (single track)
apps/generator/src/benchlib.ts Bench runner + CSV formatting
apps/generator/src/bench.ts Bench CLI command
apps/generator/py/stable_audio_generate.py Stable Audio Open generation script
apps/generator/py/musicgen_generate.py MusicGen benchmark-only script
apps/generator/py/acestep_generate.py ACE-Step generation script
apps/generator/package.json Generator package scripts/deps
apps/edge-worker/wrangler.jsonc Edge worker wrangler config
apps/edge-worker/tsconfig.json Edge worker TS config (WebWorker libs)
apps/edge-worker/test/index.test.ts Edge worker unit tests
apps/edge-worker/src/index.ts R2-backed HLS serving worker
apps/edge-worker/package.json Edge worker package metadata
.nvmrc Pin Node version (22.22.2)
.husky/pre-commit Pre-commit hook (pnpm lint)
.husky/commit-msg Commit-msg hook (commitlint)
.gitignore Expanded ignores for outputs/artifacts
.github/workflows/deploy-web.yml Deploy workflow for web app
.github/workflows/deploy-edge.yml Deploy workflow for edge worker
.github/workflows/ci.yml CI workflow (build/typecheck/lint/test)
.editorconfig Editor defaults (2-space indent, etc.)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/web/components/CreatorMenu.tsx Resolved
Comment on lines +27 to +32
const html = renderToStaticMarkup(<Home />);
expect(html).toContain("Created by Jerry Chen");
expect(html).toContain("Github");
expect(html).toContain("https://github.com/jerryschen31");
expect(html).toContain("Buy me a Coffee");
expect(html).toContain("https://buymeacoffee.com/jerryschen7");

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 32bbc96 — test now expects "GitHub", matching the corrected label.

Comment thread apps/generator/src/hls.ts Resolved
jerryschen31 and others added 2 commits July 18, 2026 17:17
- CreatorMenu: "Github" -> "GitHub" (correct branding), and the matching
  SSR test expectation in page.test.tsx.
- hls.ts: encode R2 object key path segments individually so the "/"
  separator is preserved instead of being percent-encoded into the key.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jerryschen31
jerryschen31 merged commit f046133 into master Jul 19, 2026
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.

2 participants