Spec-driven development - aka writing a PRD first - is the workflow I typically use with AI agents. I take my pre-existing notes on hobbies and projects and can quickly turn them into springboards for vibecoding.
I play in a few bands and often encounter the laborious process of transcribing music and arranging it for our instrumentation. I wanted to quickly make a low-stakes tool to help with the process.
For developing a PRD, I like to use an iterative 'ask me clarifying questions' prompt in ChatGPT and seed it with a rough description of my user needs, often pasted directly from throwaway Obsidian notes:
Let's iterate on a PRD together. Each response, give the current PRD and then ask me 5 questions which enhance it by 10%.
Try to avoid making assumptions about the target product. Your goal is to understand, document, and probe intelligently. Your job is NOT to randomly pad out the PRD.
Starting point: I want to tap a rhythm on my phone (to a metronome), have it quantised, and see the result as sheet music. I want to set the pitches of these notes and listen to playback, and also visualise as sheet music. Finally I want to export as a transferable format like MIDI or lilypond.
Once I'm happy with the output, I'll copy it directly into a PRD.md file (after first stripping out any of ChatGPT's header/footer fluff).
My kick-off prompt in Claude Code is then something like: Read PRD.md and turn it into a step-by-step PLAN.md. It's important to understand the conceptual difference: the PRD is a fixed specification of the use case we're aiming to achieve; the plan are the steps we need to go through to implement it.
Helpfully, Claude will often naturally write the plan into phases:
### Phase 1 β Project Scaffold
**Goal:** Running app skeleton with correct tooling wired up.
- `server/` β FastAPI app serving `dist/` (or Vite dev proxy)
- `client/` β Vite + React + TypeScript
- Vitest + Playwright configured
- Layout components: `ConfigPanel`, `TapZone`, `NotationView`, `PianoRoll`, `PlaybackBar`
- Placeholder routing between views (tabs or single-page stacked)
- CI: `pytest` + `vitest run` in Makefile
### Phase 2 β Tap Input + Metronome
**Goal:** User can configure BPM/time-sig, start a metronome, tap, and see raw tap events logged.
- `AudioEngine` singleton: Tone.js `Transport` + `MetronomePlayer`
- Downbeat emphasis (louder click, different pitch)
- Optional visual pulse (CSS flash on beat)
- `RecordingManager`: state machine `idle β armed β recording β stopped`
- Start: sync tap clock to Tone.js transport start
- Capture: `performance.now()` on every tap keypress/click/touch
- Stop: at user trigger or 60s hard cap
- Visual: tap flash feedback, tap counter, elapsed time
- **Tests:** mock timestamps β verify event list
### Phase 3 β Quantisation Engine
**Goal:** Raw tap timestamps β `Note[]` sequence. This is the core algorithm; build TDD.
...
which has the added bonus of hyper-efficient next step prompting:
### π€ Assistant - 06/05/2026
9/9 tests passing, clean build at 48KB.
**What was built:**
- `engine/quantise.ts` β pure function `quantise(taps, config): Note[]` with exported `gridIntervalBeats` helper. Algorithm: msβbeats, snap to nearest grid point, deduplicate same-cell taps, note-per-tap (one grid unit each), rests fill gaps >1 grid unit, optional trailing rest to fill total recording duration.
- `tests/quantise.test.ts` β 9 tests covering: empty input, grid snapping, rest detection, triplet subdivisions, multi-bar sequences, deduplication, pitch/flag invariants.
- `TapZone.svelte` β wired to call `quantise()` on stop, writing results directly into the session store via `setNotes()`. The stopped UI now shows "N notes Β· M rests quantised".
Phase 4 (VexFlow notation rendering) is next.
---
### π€ User - 06/05/2026
proceed with phase 4
One slight hurdle I would note is the need to handle environment setup - I had to run a few sudo, apt and python -m venv commands before Claude could get into coding the phases. I'm sure this could be made more efficient with a curated set of bootstrap scripts, or --dangerously-skip-permissions, but I'm OK with spending a few more minutes on careful, project-specific setup.
While a project is still fairly simple, Claude can quite reliably write and implement its own plan, and the user's role is mostly in UX/UI testing and reporting back on any bugs. For example, I found that quantisation wasn't working exactly as intended, but Claude hadn't picked up on it because it could only see that the code was functioning without errors (and I hadn't added much by way of rich feedback signals at this point). I did try to shortcut the process by asking it to "implement a 'gold standard' set of tests where simulate an imperfect human-like tapping of known rhythms to see if they match up after quantisation" but it didn't seem to improve the accuracy. A more sophisticated testing and optimisation suite would probably be a good thing to build out.