Skip to content

Repository files navigation

Versetile

A songwriter's workstation. Chords, scales and progressions across the whole neck, plus a bar-grid multi-track looper.

Built as a web app first, with native iOS/Android wrapping later once the web version is validated.

Status

Mid-migration. The app began as one self-contained 4,164-line HTML file; this repo is the React project it is moving into.

Module Where it runs
Chords Classic app
Scales Classic app
Progressions Classic app
Looper Classic app
Lyrics Not built

"Classic app" means the original single file, preserved unchanged at public/legacy/index.html and served at /legacy/. It works exactly as it always has. Modules move into the React app one at a time; see docs/migration.md for the order and docs/architecture.md for why the code is laid out the way it is.

Running it locally

You need Node.js. If node --version doesn't work, install the LTS macOS installer from nodejs.org — it's a normal double-click installer, no terminal required. Then restart your terminal.

npm install
npm run dev

That prints a http://localhost:5173 URL. Open it.

The microphone will not work over your local network. npm run dev also prints a http://192.168.x.x address you can open on a phone, and the Looper will load there but cannot record. Browsers only grant microphone access in a secure context — https, or localhost specifically. Real on-device Looper testing goes through the deployed URL below, not the LAN address.

Other commands:

npm run build
npm run typecheck

Deploying

Cloudflare Workers is the live host. Either host serves https, which is what makes the microphone work on a real iPhone — the thing local files never could.

npm run deploy

That builds and uploads in one step. wrangler.jsonc holds the whole configuration, and it is an assets-only Worker — there is no server code, so Cloudflare serves dist/ straight from its edge:

  • dist/ uploaded as static assets
  • SPA fallback, so /looper survives a refresh
  • /legacy/ matched as a real file, so the fallback never swallows it
  • public/_headers sets Permissions-Policy: microphone=(self) and caches the hashed /assets/ files forever

Pushes to main republish once the repo is connected to Workers Builds in the Cloudflare dashboard. Until then, npm run deploy from a checkout is the way.

netlify.toml is kept in sync as a working fallback — the same SPA, legacy and header rules expressed for Netlify. The app stays deployable to either.

Layout

src/
  theory/     Pure music theory — no DOM, no React, no audio
  audio/      Imperative audio engines — no React, owns the clock
  song/       The shared Song Project every module reads and writes
  modules/    One folder per feature; registry.ts is the table of contents
  components/ Shared presentational React
  lib/        Small utilities
  styles/     Design tokens + app shell
public/legacy/  The original single-file app, byte-for-byte
docs/           Roadmap, architecture, migration, brand, legacy notes

Two rules matter more than the rest:

  1. React never owns the audio clock. Audio engines are plain TypeScript classes; React renders the controls around them and hands them a canvas.
  2. Never hardcode a hex value. All colour comes from src/styles/tokens.css.

Both are explained in docs/architecture.md.

Docs

About

A songwriter's workstation. Chords, scales, progressions, and a bar-grid looper

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages