[{"data":1,"prerenderedAt":3428},["ShallowReactive",2],{"navigation":3,"\u002Fblog\u002Fhow-i-cant-clap-gets-made":224,"\u002Fblog\u002Fhow-i-cant-clap-gets-made-surround":3424},[4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100,104,108,112,116,120,124,128,132,136,140,144,148,152,156,160,164,168,172,176,180,184,188,192,196,200,204,208,212,216,220],{"title":5,"path":6,"stem":7},"You do not have time to not have tests","\u002Fblog\u002Fyou-do-not-have-time-to-not-have-tests","2.blog\u002F20211217.you-do-not-have-time-to-not-have-tests",{"title":9,"path":10,"stem":11},"Migrate Vue 2 with Vuetify and Jest to Vite and Vitest","\u002Fblog\u002Fmigrate-vue-2-with-vuetify-and-jest-to-vite-and-vitest","2.blog\u002F20220109.migrate-vue-2-with-vuetify-and-jest-to-vite-and-vitest",{"title":13,"path":14,"stem":15},"I am a Dark Matter Developer","\u002Fblog\u002Fi-am-a-dark-matter-developer","2.blog\u002F20220626.i-am-a-dark-matter-developer",{"title":17,"path":18,"stem":19},"Why using Conventional commits is useful","\u002Fblog\u002Fusing-conventional-commits","2.blog\u002F20240623.using-conventional-commits",{"title":21,"path":22,"stem":23},"Why you should make a toolbox repository","\u002Fblog\u002Fwhy-you-should-make-a-toolbox-repository","2.blog\u002F20240630.Why-you-should-make-a-toolbox-repository",{"title":25,"path":26,"stem":27},"Apache Airflow Part 1 - Why and Goals for a near Serverless ELT","\u002Fblog\u002Fapache-airflow-part-1-why-and-goals","2.blog\u002F20240710.apache-airflow-part-1-why-and-goals",{"title":29,"path":30,"stem":31},"Oh My Zsh on your server","\u002Fblog\u002Foh-my-zsh-on-your-server","2.blog\u002F20240711.oh-my-zsh-on-your-server",{"title":33,"path":34,"stem":35},"Fire tablet and YouTube Kids","\u002Fblog\u002Ffire-tablet-and-youtube-kids","2.blog\u002F20240714.fire-tablet-and-youtube-kids",{"title":37,"path":38,"stem":39},"Using Ollama and Continue as a GitHub Copilot Alternative","\u002Fblog\u002Fusing-ollama-and-continue-as-github-copilot-alternative","2.blog\u002F20240723.using-ollama-and-continue-as-github-copilot-alternative",{"title":41,"path":42,"stem":43},"Debugging Local Packages Made Easy with pnpm","\u002Fblog\u002Fdebugging-local-packages-with-pnpm-link","2.blog\u002F20250422.debugging local-packages-with-pnpm-link",{"title":45,"path":46,"stem":47},"Two Weeks with Cloudflare AI and Tools","\u002Fblog\u002Ftwo-weeks-with-cloudflare-ai-and-tools","2.blog\u002F20250509.two-weeks-with-cloudflare-aI-and-tools",{"title":49,"path":50,"stem":51},"Adding Prompts to VS Code - How I Learned to Stop Worrying and Love AI Context","\u002Fblog\u002Fadding-prompts-to-vscode","2.blog\u002F20250528.adding-prompts-to-vscode",{"title":53,"path":54,"stem":55},"My Best Practices","\u002Fblog\u002Fmy-best-practicies","2.blog\u002F20250607.my-best-practicies",{"title":57,"path":58,"stem":59},"Creating my own CLI Tool - Towles Tool","\u002Fblog\u002Ftowles-tool","2.blog\u002F20250607.towles-tool",{"title":61,"path":62,"stem":63},"Software Development Best Practices & ITIL","\u002Fblog\u002Fsoftware-engineering-and-itil-best-practices","2.blog\u002F20250612.software-engineering-and-itil-best-practices",{"title":65,"path":66,"stem":67},"Voice to Text","\u002Fblog\u002Fvoice-to-text","2.blog\u002F20250622.voice-to-text",{"title":69,"path":70,"stem":71},"Setting Up ComfyUI - A Better Alternative to Fooocus","\u002Fblog\u002Fcomfy-ui-setup","2.blog\u002F20250628.comfy-ui-setup",{"title":73,"path":74,"stem":75},"Voice to System","\u002Fblog\u002Fvoice-to-system","2.blog\u002F20250705.voice-to-system",{"title":77,"path":78,"stem":79},"Tips for Claude Code","\u002Fblog\u002Ftips-for-claude-code","2.blog\u002F20250713.tips-for-claude-code",{"title":81,"path":82,"stem":83},"Review That AI Code: Why I Read Every Line Generated Code","\u002Fblog\u002Freview-that-ai-code","2.blog\u002F20250720.review-that-ai-code",{"title":85,"path":86,"stem":87},"My Context Engineering Journey: From Dev Scripts to AI Collaboration","\u002Fblog\u002F20250803-1.my-context-engineering-journey","2.blog\u002F20250803-1.my-context-engineering-journey",{"title":89,"path":90,"stem":91},"Context Engineering at Scale: Enterprise Lessons and the Future of Development","\u002Fblog\u002F20250803-2.context-engineering-at-scale","2.blog\u002F20250803-2.context-engineering-at-scale",{"title":93,"path":94,"stem":95},"Check That Your Tools and Linters Do Not Burn Tokens","\u002Fblog\u002Fcheck-that-your-tools-and-linters-do-not-burn-tokens","2.blog\u002F20250806.check-that-your-tools-and-linters-do-not-burn-tokens",{"title":97,"path":98,"stem":99},"Markdown + AI: The Communication Protocol That Changes Everything","\u002Fblog\u002Fmarkdown-plus-ai-the-communication-protocol-that-changes-everything","2.blog\u002F20250814.markdown-plus-ai-the-communication-protocol-that-changes-everything",{"title":101,"path":102,"stem":103},"Finally: Type-Safe AI in Production (And Why I'm Here For It)","\u002Fblog\u002Ffinally-type-safe-ai-in-production-and-why-im-here-for-it","2.blog\u002F20250819.finally-type-safe-ai-in-production-and-why-im-here-for-it",{"title":105,"path":106,"stem":107},"Dotfiles: Masterpiece or Late Stage Picasso?","\u002Fblog\u002Fdotfiles-masterpiece-or-late-stage-picasso","2.blog\u002F20250822.dotfiles-masterpiece-or-late-stage-picasso",{"title":109,"path":110,"stem":111},"Beyond API Wrappers: Building State-Driven MCP Servers for Long-Horizon Agent Orchestration","\u002Fblog\u002Fbeyond-api-wrappers-mcp-servers","2.blog\u002F20250907.beyond-api-wrappers-mcp-servers",{"title":113,"path":114,"stem":115},"Why Vertical Integration Wins: A Software Engineer's Case for Owning Your Stack","\u002Fblog\u002Fwhy-i-bought-tesla-model-3-vertical-integration","2.blog\u002F20250928.why-i-bought-tesla-model-3-vertical-integration",{"title":117,"path":118,"stem":119},"The Min-Maxer's Trifecta: Building Tools for the Game You Actually Play","\u002Fblog\u002Fmin-maxer-trifecta","2.blog\u002F20251004.min-maxer-trifecta",{"title":121,"path":122,"stem":123},"Read The Source: Learning by Cutting Out The Middleman and RTFM","\u002Fblog\u002Fread-the-source","2.blog\u002F20251010.read-the-source",{"title":125,"path":126,"stem":127},"The Exponential Shift: Why AI Progress Feels Different Now","\u002Fblog\u002Fthe-exponential-shift","2.blog\u002F20251015.the-exponential-shift",{"title":129,"path":130,"stem":131},"Plan Mode for Your Problems, Edit Mode for Claude's","\u002Fblog\u002Fplan-mode-problems-edit-mode-solutions","2.blog\u002F20251019.plan-mode-problems-edit-mode-solutions",{"title":133,"path":134,"stem":135},"AWS Aurora DSQL Looked Perfect Until I Needed the Connection String","\u002Fblog\u002Faws-aurora-dsql-postgres-serverless-authentication","2.blog\u002F20251028.aws-aurora-dsql-postgres-serverless-authentication",{"title":137,"path":138,"stem":139},"Switchback: Browser History for Your Thoughts","\u002Fblog\u002Fswitchback-second-order-reasoning","2.blog\u002F20251205.switchback-second-order-reasoning",{"title":141,"path":142,"stem":143},"AI Pairing: Notes to Self","\u002Fblog\u002Fai-pairing-notes-to-self","2.blog\u002F20251216.ai-pairing-notes-to-self",{"title":145,"path":146,"stem":147},"I've Been Sleeping on Zellij","\u002Fblog\u002Fsleeping-on-zellij","2.blog\u002F20251229.sleeping-on-zellij",{"title":149,"path":150,"stem":151},"Implementing a Ralph Wiggum Loop: The Secret is Session Markers","\u002Fblog\u002Fimplementing-ralph-wiggum-loop-for-autonomous-ai-coding","2.blog\u002F20260114.implementing-ralph-wiggum-loop-for-autonomous-ai-coding",{"title":153,"path":154,"stem":155},"Goodhart's Law Ate My Context Window","\u002Fblog\u002Fgoodharts-law-ate-my-context-window","2.blog\u002F20260119.goodharts-law-ate-my-context-window",{"title":157,"path":158,"stem":159},"Claude Code's Hidden Multi-Agent System Is Real","\u002Fblog\u002Fclaude-code-hidden-multi-agent-system","2.blog\u002F20260124.claude-code-hidden-multi-agent-system",{"title":161,"path":162,"stem":163},"Free Printable Math Sheets for Kids — Number Chart, Skip Counting, Multiplication, and More","\u002Fblog\u002Ffree-printable-number-chart-and-coin-sheets","2.blog\u002F20260214.free-printable-number-chart-and-coin-sheets",{"title":165,"path":166,"stem":167},"We Are Near the End of the Exponential","\u002Fblog\u002Fnear-the-end-of-the-exponential","2.blog\u002F20260214.near-the-end-of-the-exponential",{"title":169,"path":170,"stem":171},"Free Printable Language Arts Sheets for Kids — Sight Words, Parts of Speech, Homophones, and More","\u002Fblog\u002Ffree-printable-sight-words-and-grammar-sheets","2.blog\u002F20260215.free-printable-sight-words-and-grammar-sheets",{"title":173,"path":174,"stem":175},"Interactive Code Execution with Artifacts","\u002Fblog\u002Finteractive-code-execution-with-artifacts","2.blog\u002F20260215.interactive-code-execution-with-artifacts",{"title":177,"path":178,"stem":179},"Free Printable Telling Time Worksheet for Kids — Clock Reference & Practice Sheet","\u002Fblog\u002Ffree-printable-telling-time-worksheet","2.blog\u002F20260216.free-printable-telling-time-worksheet",{"title":181,"path":182,"stem":183},"Claude Code Skills: Teaching AI Your Playbook","\u002Fblog\u002Fclaude-code-skills-guide","2.blog\u002F20260221.claude-code-skills-guide",{"title":185,"path":186,"stem":187},"Building a Multi-Agent Loan Approval System with Human-in-the-Loop","\u002Fblog\u002Fmulti-agent-loan-approval-human-in-the-loop","2.blog\u002F20260225.multi-agent-loan-approval-human-in-the-loop",{"title":189,"path":190,"stem":191},"The Inception of AI Infrastructure: Bottlenecks All the Way Down","\u002Fblog\u002Fbiggest-bottleneck-scaling-ai-compute","2.blog\u002F20260313.biggest-bottleneck-scaling-ai-compute",{"title":193,"path":194,"stem":195},"What I Tell Teams About Claude Code","\u002Fblog\u002Fwhat-i-tell-teams-about-claude-code","2.blog\u002F20260314.what-i-tell-teams-about-claude-code",{"title":197,"path":198,"stem":199},"The Hardest Part of AI Isn't the AI","\u002Fblog\u002Fthe-hardest-part-of-ai-isnt-the-ai","2.blog\u002F20260327.the-hardest-part-of-ai-isnt-the-ai",{"title":201,"path":202,"stem":203},"Claude Code Hooks: The Capability I Left on the Table","\u002Fblog\u002Fclaude-code-hooks-capability-left-on-the-table","2.blog\u002F20260401.claude-code-hooks-capability-left-on-the-table",{"title":205,"path":206,"stem":207},"Claude in a Box: Trying Computer-Use on My Laptop","\u002Fblog\u002Fclaude-computer-use-in-a-box","2.blog\u002F20260418.claude-computer-use-in-a-box",{"title":209,"path":210,"stem":211},"MCP Apps: We Spent $100B on AI to Bring Back the Iframe","\u002Fblog\u002Fmcp-ext-apps-claude-desktop-developer-mode","2.blog\u002F20260505.mcp-ext-apps-claude-desktop-developer-mode",{"title":213,"path":214,"stem":215},"I Couldn't Give Up Ghostty, So I Put It Inside My App","\u002Fblog\u002Fi-couldnt-give-up-ghostty-so-i-put-it-inside-my-app","2.blog\u002F20260710.i-couldnt-give-up-ghostty-so-i-put-it-inside-my-app",{"title":217,"path":218,"stem":219},"We're Not Asking Enough of These Models","\u002Fblog\u002Fwere-not-asking-enough-of-these-models","2.blog\u002F20261004.were-not-asking-enough-of-these-models",{"title":221,"path":222,"stem":223},"How the “I Can't Clap” Videos Get Made","\u002Fblog\u002Fhow-i-cant-clap-gets-made","2.blog\u002F20261021.how-i-cant-clap-gets-made",{"id":225,"title":221,"authors":226,"badge":232,"body":234,"date":3413,"description":3414,"extension":3415,"image":3416,"meta":3419,"navigation":371,"path":222,"rawbody":3420,"seo":3421,"status":3422,"stem":223,"__hash__":3423},"posts\u002F2.blog\u002F20261021.how-i-cant-clap-gets-made.md",[227],{"name":228,"to":229,"avatar":230},"Chris Towles","https:\u002F\u002Ftwitter.com\u002FChris_Towles",{"src":231},"\u002Fimages\u002Fctowles-profile-512x512.png",{"label":233},"AI",{"type":235,"value":236,"toc":3400},"minimark",[237,256,266,279,286,291,298,303,306,309,440,445,473,476,578,581,620,623,631,641,645,656,659,665,668,671,1169,1173,1176,1181,1184,1187,1712,1715,1787,1791,1801,1806,1812,1827,1948,1951,2024,2027,2033,2843,2847,2881,3054,3345,3349,3352,3355,3359,3362,3366,3393,3396],[238,239,240,241,250,251,255],"p",{},"People keep asking how the videos for my album ",[242,243,246],"a",{"href":244,"target":245},"\u002Fi-cant-clap","\\_blank",[247,248,249],"em",{},"I Can't Clap"," get made. Claude Code agents write the songs, Suno sings them, a GPU script times every word, and each video is a three.js scene drawn from the audio clock. I pick the premise, leave notes on lines, choose takes, and click Create and Download. Here is the whole chain, using real data from ",[242,252,254],{"href":253,"target":245},"\u002Fi-cant-clap\u002Fthe-steep-part","The Steep Part",".",[238,257,258,259,265],{},"It started with my kids. After hearing a few AI-generated songs that weren't half bad, I began making spelling-word and reading songs for them while I played with ",[242,260,264],{"href":261,"target":245,"rel":262},"https:\u002F\u002Ffarwonder.app",[263],"nofollow","Farwonder",", the app I'm building for them. The ones I post here are the grown-up side of that: humorous songs about working with AI.",[267,268,270],"callout",{"icon":269},"i-lucide-play",[238,271,272,278],{},[273,274,275],"strong",{},[242,276,277],{"href":253,"target":245},"Watch The Steep Part →"," It's the one this post takes apart: the lyrics, the judge rounds, the word timings and the video are all its real files.",[280,281],"demo-embed",{"part":282,"src":284,"title":285},[283],"chain","\u002Fdemos\u002Fhow-i-cant-clap-gets-made\u002Findex.html","Five stages: Write (Claude Code), Sing (Suno, I pick and download), Time (GPU, about 5 minutes), Film (Claude Code) and Ship (pages on this blog), with my notes and an early sketch take feeding back into Write",[287,288,290],"h2",{"id":289},"_1-write-a-writer-agent-and-a-judge-it-cant-sway","1. Write: a writer agent and a judge it can't sway",[238,292,293,294,297],{},"Each song starts as a one-line premise: an AI or IT ideal against the messy reality. One writer agent reads a shared brief, the two earlier songs, my blog posts, and a Suno style guide, brainstorms about 25 gags, keeps the best dozen, and writes the song. Then it spawns a ",[247,295,296],{},"new"," judge agent every round. The judge scores the song against a rubric, quotes the weak lines, and says APPROVED or REVISE. A fresh judge each round means no one grades their own homework twice. Approval takes 8.5 or better with no rubric item below 7, in five rounds at most.",[280,299],{"part":300,"src":284,"title":302},[301],"judge","The Steep Part's judge scores by round: 7.1, 8.2, 8.2, then 8.6 approved, against a threshold of 8.5",[238,304,305],{},"Round 1 caught rap lines running 17 to 19 syllables and flat last words. By round 4 it passed at 8.6. Then I read it in the songbook app and left 15 line notes, and the writer applied them one by one.",[238,307,308],{},"I want songs under 3:30. An early song ran about 650 words and came back 4:07, so every draft now has to print OK before a judge sees it:",[310,311,316],"pre",{"className":312,"code":313,"language":314,"meta":315,"style":315},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# Suno sings roughly one line per two bars\nseconds = lines × 480 \u002F BPM × k\n# k = 1.0 sung, 0.8 patter, 0.5 for rap lines\n\n$ count.py songs\u002F24-the-steep-part.md\n110 BPM lines=54 rap=32 words=436\nest=2:45 OK  (target ≤ 3:10)\n","bash","",[317,318,319,328,360,366,373,385,411],"code",{"__ignoreMap":315},[320,321,324],"span",{"class":322,"line":323},"line",1,[320,325,327],{"class":326},"sHwdD","# Suno sings roughly one line per two bars\n",[320,329,331,335,339,342,345,349,352,355,357],{"class":322,"line":330},2,[320,332,334],{"class":333},"sBMFI","seconds",[320,336,338],{"class":337},"sfazB"," =",[320,340,341],{"class":337}," lines",[320,343,344],{"class":337}," ×",[320,346,348],{"class":347},"sbssI"," 480",[320,350,351],{"class":337}," \u002F",[320,353,354],{"class":337}," BPM",[320,356,344],{"class":337},[320,358,359],{"class":337}," k\n",[320,361,363],{"class":322,"line":362},3,[320,364,365],{"class":326},"# k = 1.0 sung, 0.8 patter, 0.5 for rap lines\n",[320,367,369],{"class":322,"line":368},4,[320,370,372],{"emptyLinePlaceholder":371},true,"\n",[320,374,376,379,382],{"class":322,"line":375},5,[320,377,378],{"class":333},"$",[320,380,381],{"class":337}," count.py",[320,383,384],{"class":337}," songs\u002F24-the-steep-part.md\n",[320,386,388,391,393,396,399,402,405,408],{"class":322,"line":387},6,[320,389,390],{"class":333},"110",[320,392,354],{"class":337},[320,394,395],{"class":337}," lines=",[320,397,398],{"class":347},"54",[320,400,401],{"class":337}," rap=",[320,403,404],{"class":347},"32",[320,406,407],{"class":337}," words=",[320,409,410],{"class":347},"436\n",[320,412,414,418,422,425,428,431,434,437],{"class":322,"line":413},7,[320,415,417],{"class":416},"sTEyZ","est",[320,419,421],{"class":420},"sMK4o","=",[320,423,424],{"class":337},"2:45",[320,426,427],{"class":333}," OK",[320,429,430],{"class":416},"  (target ",[320,432,433],{"class":337},"≤",[320,435,436],{"class":337}," 3:10",[320,438,439],{"class":416},")\n",[441,442,444],"h3",{"id":443},"then-i-read-it-edit-it-and-hear-it-early","Then I read it, edit it, and hear it early",[446,447,448,455,461,467],"ol",{},[449,450,451,454],"li",{},[273,452,453],{},"Read it in the songbook."," A small Bun app shows every song. I tap a line and mark it Love it, Cut or Change, with a note, and give the song a status: Keeper, Needs work or Drop.",[449,456,457,460],{},[273,458,459],{},"Sketch it in Suno."," Before many edits, I load a rough draft into Suno just to hear the style. Does the genre land, does the hook stick, can you hear the joke? If not, I change the style or drop the song while it's still cheap.",[449,462,463,466],{},[273,464,465],{},"The writer applies my notes."," Claude reads my notes file, revises the lyrics line by line, marks each note resolved and replies under it. The open page refreshes by itself.",[449,468,469,472],{},[273,470,471],{},"Repeat until it's a keeper."," Read, sketch, edit again. Only a keeper gets a final take, timings and a video.",[238,474,475],{},"My notes, as the writer sees them:",[310,477,481],{"className":478,"code":479,"language":480,"meta":315,"style":315},"language-jsonc shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F feedback\u002F24-the-steep-part.json: what the songbook app saves when I tap a line\n{\n  \"status\": \"rework\", \u002F\u002F keeper | rework | drop\n  \"pin\": \"working\",\n  \"note\": \"Push verse 2 harder\",\n  \"lines\": [\n    {\n      \"line\": \"So I made my loop look busy. Boss said \\\"clever\\\"\",\n      \"kind\": \"cut\", \u002F\u002F love | cut | change\n      \"note\": \"too slow\",\n      \"resolved\": false, \u002F\u002F the writer sets true once it's fixed…\n      \"reply\": \"\", \u002F\u002F …and answers here, under my note\n    },\n  ],\n}\n","jsonc",[317,482,483,488,493,501,506,511,516,521,527,536,542,551,560,566,572],{"__ignoreMap":315},[320,484,485],{"class":322,"line":323},[320,486,487],{},"\u002F\u002F feedback\u002F24-the-steep-part.json: what the songbook app saves when I tap a line\n",[320,489,490],{"class":322,"line":330},[320,491,492],{},"{\n",[320,494,495,498],{"class":322,"line":362},[320,496,497],{},"  \"status\": \"rework\",",[320,499,500],{}," \u002F\u002F keeper | rework | drop\n",[320,502,503],{"class":322,"line":368},[320,504,505],{},"  \"pin\": \"working\",\n",[320,507,508],{"class":322,"line":375},[320,509,510],{},"  \"note\": \"Push verse 2 harder\",\n",[320,512,513],{"class":322,"line":387},[320,514,515],{},"  \"lines\": [\n",[320,517,518],{"class":322,"line":413},[320,519,520],{},"    {\n",[320,522,524],{"class":322,"line":523},8,[320,525,526],{},"      \"line\": \"So I made my loop look busy. Boss said \\\"clever\\\"\",\n",[320,528,530,533],{"class":322,"line":529},9,[320,531,532],{},"      \"kind\": \"cut\",",[320,534,535],{}," \u002F\u002F love | cut | change\n",[320,537,539],{"class":322,"line":538},10,[320,540,541],{},"      \"note\": \"too slow\",\n",[320,543,545,548],{"class":322,"line":544},11,[320,546,547],{},"      \"resolved\": false,",[320,549,550],{}," \u002F\u002F the writer sets true once it's fixed…\n",[320,552,554,557],{"class":322,"line":553},12,[320,555,556],{},"      \"reply\": \"\",",[320,558,559],{}," \u002F\u002F …and answers here, under my note\n",[320,561,563],{"class":322,"line":562},13,[320,564,565],{},"    },\n",[320,567,569],{"class":322,"line":568},14,[320,570,571],{},"  ],\n",[320,573,575],{"class":322,"line":574},15,[320,576,577],{},"}\n",[238,579,580],{},"An early sketch take. Hearing it first is the cheapest test; a clever lyric in the wrong genre is still a dud.",[310,582,584],{"className":312,"code":583,"language":314,"meta":315,"style":315},"# a rough draft goes to Suno before the polishing rounds\n$ bun suno 24-the-steep-part --fill     # fill the form, I click Create and listen\n# does the genre land? does the hook stick? is the joke audible at this tempo?\n# if not, change the Style or the premise now, while it's still cheap\n",[317,585,586,591,610,615],{"__ignoreMap":315},[320,587,588],{"class":322,"line":323},[320,589,590],{"class":326},"# a rough draft goes to Suno before the polishing rounds\n",[320,592,593,595,598,601,604,607],{"class":322,"line":330},[320,594,378],{"class":333},[320,596,597],{"class":337}," bun",[320,599,600],{"class":337}," suno",[320,602,603],{"class":337}," 24-the-steep-part",[320,605,606],{"class":337}," --fill",[320,608,609],{"class":326},"     # fill the form, I click Create and listen\n",[320,611,612],{"class":322,"line":362},[320,613,614],{"class":326},"# does the genre land? does the hook stick? is the joke audible at this tempo?\n",[320,616,617],{"class":322,"line":368},[320,618,619],{"class":326},"# if not, change the Style or the premise now, while it's still cheap\n",[238,621,622],{},"The judge prompt and the log it leaves. The writer may push back on a fix that would hurt a joke, but it has to say why in the log.",[310,624,629],{"className":625,"code":627,"language":628,"meta":315},[626],"language-text","# research\u002Fwriter-process.md, step 4: the writer spawns this judge every round\n\"You are the judge for a comedic Suno song. Read research\u002Fbrief.md\n (the 'Judge rubric' section is your job), skim research\u002Fsuno-guide.md\n sections 1-3, and read the song at \u003Csong path>. Run count.py \u003Csong path>.\n Score each rubric item 1-10, then give a verdict: APPROVED or REVISE,\n with up to 6 prioritized, concrete fixes that quote weak lines. Be tough;\n earlier rounds' reviews are at \u003Cprior review paths>, so check whether\n those fixes landed. Write your review to reviews\u002F\u003Cslug>.r\u003Cround>.md, and\n end with 'VERDICT: APPROVED (score X)' or 'VERDICT: REVISE (score X)'.\n Do not edit the song.\"\n\n# songs\u002F24-the-steep-part.md, the Judge log the writer keeps\n- Round 1: REVISE (7.1) — V2 rap lines ran 17-19 syllables, weak last words…\n- Round 4: APPROVED (score 8.6\u002F10) — Listen-for on Suno: if V2 rushes,\n  drop the (Approved?) ad-lib first; check \"Q3\" and \"S-O-C two\" pronunciation\n","text",[317,630,627],{"__ignoreMap":315},[632,633,634],"blockquote",{},[238,635,636,637,640],{},"I burst into the standup: \"Guys, the singularity's here!\"",[638,639],"br",{},"\nSomebody said, \"Great. File a ticket. We'll talk next year.\"",[287,642,644],{"id":643},"_2-sing-suno-sings-it-i-pick-the-take","2. Sing: Suno sings it, I pick the take",[238,646,647,648,651,652,655],{},"Each song file carries a Style prompt, an Exclude list, and lyrics with section tags like ",[317,649,650],{},"[Verse 1: rap, male]"," or ",[317,653,654],{},"[Bridge: spoken, male, sparse]",". A script drives my own logged-in Chrome over the DevTools protocol, fills Suno's Advanced form, clicks Create, and records both clips it returns. I listen, pick the take I like, and download the MP3 myself.",[238,657,658],{},"What Suno gets for The Steep Part:",[310,660,663],{"className":661,"code":662,"language":628,"meta":315},[626],"Style   aggressive hardcore pop-rap anthem, 110 BPM, booming hard-hitting drums\n        and heavy 808s, dark minor piano riff, gritty distorted synth stabs,\n        intense rapid-fire snarling male rap with clear diction, soaring\n        haunting female pop hook, stadium build-ups, punchy loud mix\nExclude country, comedic voices, cheerful, autotune, guitar solo,\n        long instrumental, sung verses, lo-fi, mellow, jazz\n",[317,664,662],{"__ignoreMap":315},[238,666,667],{},"Exclude matters as much as Style. \"comedic voices\" keeps Suno from singing the jokes in a goofy voice, which kills them.",[238,669,670],{},"The script reads the form back before clicking Create, because a paste that silently fails still spends a generation:",[310,672,676],{"className":673,"code":674,"language":675,"meta":315,"style":315},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F src\u002Fsuno.ts: drive my logged-in Chrome over the DevTools protocol\nasync function fillForm(tab: Tab, song: Song) {\n  const { title, style, exclude, lyrics } = song.final;\n  await tab.eval(() => [...document.querySelectorAll('[role=\"tab\"]')]\n    .find(t => t.textContent?.trim() === \"Advanced\")?.click());\n  await tab.waitFor('[aria-label=\"Lyrics editor\"]');\n  \u002F\u002F The lyrics box is a Lexical editor; one paragraph per line is exactly what typing produces.\n  await tab.eval(lyrics => { \u002F* set the editor state, one paragraph per line *\u002F }, lyrics);\n  await tab.typeInto('textarea:not([aria-label]):not([placeholder^=\"Describe\"])', style);\n  await tab.typeInto('input[placeholder=\"Exclude styles\"]', exclude);\n  \u002F\u002F read the form back: a silent paste failure would waste a generation\n  const want = lyrics.split(\"\\n\").filter(l => l.trim()).length;\n  const got = shown.lyrics.split(\"\\n\").filter(l => l.trim()).length;\n  if (want !== got) throw new Error(`Lyrics box shows ${got} lines, expected ${want}. Check the form in Chrome.`);\n}\n","ts",[317,677,678,683,726,767,817,873,897,902,933,961,988,993,1048,1102,1165],{"__ignoreMap":315},[320,679,680],{"class":322,"line":323},[320,681,682],{"class":326},"\u002F\u002F src\u002Fsuno.ts: drive my logged-in Chrome over the DevTools protocol\n",[320,684,685,689,692,696,699,703,706,709,712,715,717,720,723],{"class":322,"line":330},[320,686,688],{"class":687},"spNyl","async",[320,690,691],{"class":687}," function",[320,693,695],{"class":694},"s2Zo4"," fillForm",[320,697,698],{"class":420},"(",[320,700,702],{"class":701},"sHdIc","tab",[320,704,705],{"class":420},":",[320,707,708],{"class":333}," Tab",[320,710,711],{"class":420},",",[320,713,714],{"class":701}," song",[320,716,705],{"class":420},[320,718,719],{"class":333}," Song",[320,721,722],{"class":420},")",[320,724,725],{"class":420}," {\n",[320,727,728,731,734,737,739,742,744,747,749,752,755,757,759,761,764],{"class":322,"line":362},[320,729,730],{"class":687},"  const",[320,732,733],{"class":420}," {",[320,735,736],{"class":416}," title",[320,738,711],{"class":420},[320,740,741],{"class":416}," style",[320,743,711],{"class":420},[320,745,746],{"class":416}," exclude",[320,748,711],{"class":420},[320,750,751],{"class":416}," lyrics",[320,753,754],{"class":420}," }",[320,756,338],{"class":420},[320,758,714],{"class":416},[320,760,255],{"class":420},[320,762,763],{"class":416},"final",[320,765,766],{"class":420},";\n",[320,768,769,773,776,778,781,784,787,790,793,796,799,801,804,806,809,812,814],{"class":322,"line":368},[320,770,772],{"class":771},"s7zQu","  await",[320,774,775],{"class":416}," tab",[320,777,255],{"class":420},[320,779,780],{"class":694},"eval",[320,782,698],{"class":783},"swJcz",[320,785,786],{"class":420},"()",[320,788,789],{"class":687}," =>",[320,791,792],{"class":783}," [",[320,794,795],{"class":420},"...",[320,797,798],{"class":416},"document",[320,800,255],{"class":420},[320,802,803],{"class":694},"querySelectorAll",[320,805,698],{"class":783},[320,807,808],{"class":420},"'",[320,810,811],{"class":337},"[role=\"tab\"]",[320,813,808],{"class":420},[320,815,816],{"class":783},")]\n",[320,818,819,822,825,827,830,832,835,837,840,843,846,849,852,855,858,861,863,865,868,871],{"class":322,"line":375},[320,820,821],{"class":420},"    .",[320,823,824],{"class":694},"find",[320,826,698],{"class":783},[320,828,829],{"class":701},"t",[320,831,789],{"class":687},[320,833,834],{"class":416}," t",[320,836,255],{"class":420},[320,838,839],{"class":416},"textContent",[320,841,842],{"class":420},"?.",[320,844,845],{"class":694},"trim",[320,847,848],{"class":783},"() ",[320,850,851],{"class":420},"===",[320,853,854],{"class":420}," \"",[320,856,857],{"class":337},"Advanced",[320,859,860],{"class":420},"\"",[320,862,722],{"class":783},[320,864,842],{"class":420},[320,866,867],{"class":694},"click",[320,869,870],{"class":783},"())",[320,872,766],{"class":420},[320,874,875,877,879,881,884,886,888,891,893,895],{"class":322,"line":387},[320,876,772],{"class":771},[320,878,775],{"class":416},[320,880,255],{"class":420},[320,882,883],{"class":694},"waitFor",[320,885,698],{"class":783},[320,887,808],{"class":420},[320,889,890],{"class":337},"[aria-label=\"Lyrics editor\"]",[320,892,808],{"class":420},[320,894,722],{"class":783},[320,896,766],{"class":420},[320,898,899],{"class":322,"line":413},[320,900,901],{"class":326},"  \u002F\u002F The lyrics box is a Lexical editor; one paragraph per line is exactly what typing produces.\n",[320,903,904,906,908,910,912,914,917,919,921,924,927,929,931],{"class":322,"line":523},[320,905,772],{"class":771},[320,907,775],{"class":416},[320,909,255],{"class":420},[320,911,780],{"class":694},[320,913,698],{"class":783},[320,915,916],{"class":701},"lyrics",[320,918,789],{"class":687},[320,920,733],{"class":420},[320,922,923],{"class":326}," \u002F* set the editor state, one paragraph per line *\u002F",[320,925,926],{"class":420}," },",[320,928,751],{"class":416},[320,930,722],{"class":783},[320,932,766],{"class":420},[320,934,935,937,939,941,944,946,948,951,953,955,957,959],{"class":322,"line":529},[320,936,772],{"class":771},[320,938,775],{"class":416},[320,940,255],{"class":420},[320,942,943],{"class":694},"typeInto",[320,945,698],{"class":783},[320,947,808],{"class":420},[320,949,950],{"class":337},"textarea:not([aria-label]):not([placeholder^=\"Describe\"])",[320,952,808],{"class":420},[320,954,711],{"class":420},[320,956,741],{"class":416},[320,958,722],{"class":783},[320,960,766],{"class":420},[320,962,963,965,967,969,971,973,975,978,980,982,984,986],{"class":322,"line":538},[320,964,772],{"class":771},[320,966,775],{"class":416},[320,968,255],{"class":420},[320,970,943],{"class":694},[320,972,698],{"class":783},[320,974,808],{"class":420},[320,976,977],{"class":337},"input[placeholder=\"Exclude styles\"]",[320,979,808],{"class":420},[320,981,711],{"class":420},[320,983,746],{"class":416},[320,985,722],{"class":783},[320,987,766],{"class":420},[320,989,990],{"class":322,"line":544},[320,991,992],{"class":326},"  \u002F\u002F read the form back: a silent paste failure would waste a generation\n",[320,994,995,997,1000,1002,1004,1006,1009,1011,1013,1016,1018,1020,1022,1025,1027,1030,1032,1035,1037,1039,1041,1043,1046],{"class":322,"line":553},[320,996,730],{"class":687},[320,998,999],{"class":416}," want",[320,1001,338],{"class":420},[320,1003,751],{"class":416},[320,1005,255],{"class":420},[320,1007,1008],{"class":694},"split",[320,1010,698],{"class":783},[320,1012,860],{"class":420},[320,1014,1015],{"class":416},"\\n",[320,1017,860],{"class":420},[320,1019,722],{"class":783},[320,1021,255],{"class":420},[320,1023,1024],{"class":694},"filter",[320,1026,698],{"class":783},[320,1028,1029],{"class":701},"l",[320,1031,789],{"class":687},[320,1033,1034],{"class":416}," l",[320,1036,255],{"class":420},[320,1038,845],{"class":694},[320,1040,870],{"class":783},[320,1042,255],{"class":420},[320,1044,1045],{"class":416},"length",[320,1047,766],{"class":420},[320,1049,1050,1052,1055,1057,1060,1062,1064,1066,1068,1070,1072,1074,1076,1078,1080,1082,1084,1086,1088,1090,1092,1094,1096,1098,1100],{"class":322,"line":562},[320,1051,730],{"class":687},[320,1053,1054],{"class":416}," got",[320,1056,338],{"class":420},[320,1058,1059],{"class":416}," shown",[320,1061,255],{"class":420},[320,1063,916],{"class":416},[320,1065,255],{"class":420},[320,1067,1008],{"class":694},[320,1069,698],{"class":783},[320,1071,860],{"class":420},[320,1073,1015],{"class":416},[320,1075,860],{"class":420},[320,1077,722],{"class":783},[320,1079,255],{"class":420},[320,1081,1024],{"class":694},[320,1083,698],{"class":783},[320,1085,1029],{"class":701},[320,1087,789],{"class":687},[320,1089,1034],{"class":416},[320,1091,255],{"class":420},[320,1093,845],{"class":694},[320,1095,870],{"class":783},[320,1097,255],{"class":420},[320,1099,1045],{"class":416},[320,1101,766],{"class":420},[320,1103,1104,1107,1110,1113,1116,1118,1121,1124,1127,1130,1132,1135,1138,1141,1144,1147,1150,1152,1154,1156,1159,1161,1163],{"class":322,"line":568},[320,1105,1106],{"class":771},"  if",[320,1108,1109],{"class":783}," (",[320,1111,1112],{"class":416},"want",[320,1114,1115],{"class":420}," !==",[320,1117,1054],{"class":416},[320,1119,1120],{"class":783},") ",[320,1122,1123],{"class":771},"throw",[320,1125,1126],{"class":420}," new",[320,1128,1129],{"class":694}," Error",[320,1131,698],{"class":783},[320,1133,1134],{"class":420},"`",[320,1136,1137],{"class":337},"Lyrics box shows ",[320,1139,1140],{"class":420},"${",[320,1142,1143],{"class":416},"got",[320,1145,1146],{"class":420},"}",[320,1148,1149],{"class":337}," lines, expected ",[320,1151,1140],{"class":420},[320,1153,1112],{"class":416},[320,1155,1146],{"class":420},[320,1157,1158],{"class":337},". Check the form in Chrome.",[320,1160,1134],{"class":420},[320,1162,722],{"class":783},[320,1164,766],{"class":420},[320,1166,1167],{"class":322,"line":574},[320,1168,577],{"class":420},[287,1170,1172],{"id":1171},"_3-time-find-every-word-to-the-millisecond","3. Time: find every word to the millisecond",[238,1174,1175],{},"The video has to know exactly when each word lands. faster-whisper transcribes the MP3, but under a loud mix it mishears and its stamps drift. Since we already know the lyrics, the script aligns whisper's words to the real ones. Then demucs pulls out the vocal stem and a forced aligner (torchaudio's MMS) places each line's known words on it, searching only near where whisper put the line. A last pass measures loudness, bass and the beat grid at 30 frames a second.",[280,1177],{"part":1178,"src":284,"title":1180},[1179],"timing","The first four lines of The Steep Part: each word, with what whisper heard struck through; below, loudness on top, whisper's word stamps with misheard words in red, and the final aligned stamps",[238,1182,1183],{},"Whisper missed \"Three\" entirely and heard \"Prius\" as \"priest\" and \"gain\" as \"game\". Aligning to the known lyrics fixes the words; the forced aligner fixes the times.",[238,1185,1186],{},"Step 1, align to the known lyrics:",[310,1188,1190],{"className":673,"code":1189,"language":675,"meta":315,"style":315},"\u002F\u002F src\u002Falign.ts: line up what whisper heard with the lyrics we already know.\n\u002F\u002F A Needleman-Wunsch alignment: exact match 3, close spelling 2, anything else -1.\nconst score = (a: string, b: string) =>\n  a === b ? 3 : a.length > 3 && lev(a, b) \u003C= Math.floor(a.length \u002F 4) ? 2 : -1;\n\nfor (let i = 1; i \u003C= n; i++)\n  for (let j = 1; j \u003C= m; j++) {\n    const d = S[i-1][j-1] + score(A[i-1], B[j-1]), u = S[i-1][j] + GAP, l = S[i][j-1] + GAP;\n    if (d >= u && d >= l) { S[i][j] = d; T[i][j] = 1; } else if (u >= l) { S[i][j] = u; T[i][j] = 2; } else { S[i][j] = l; T[i][j] = 3; }\n  }\n\u002F\u002F walk back: matched words take whisper's times; the rest (\"Three\") are\n\u002F\u002F interpolated between their matched neighbours\n",[317,1191,1192,1197,1202,1235,1321,1325,1363,1399,1530,1697,1702,1707],{"__ignoreMap":315},[320,1193,1194],{"class":322,"line":323},[320,1195,1196],{"class":326},"\u002F\u002F src\u002Falign.ts: line up what whisper heard with the lyrics we already know.\n",[320,1198,1199],{"class":322,"line":330},[320,1200,1201],{"class":326},"\u002F\u002F A Needleman-Wunsch alignment: exact match 3, close spelling 2, anything else -1.\n",[320,1203,1204,1207,1210,1212,1214,1216,1218,1221,1223,1226,1228,1230,1232],{"class":322,"line":362},[320,1205,1206],{"class":687},"const",[320,1208,1209],{"class":416}," score ",[320,1211,421],{"class":420},[320,1213,1109],{"class":420},[320,1215,242],{"class":701},[320,1217,705],{"class":420},[320,1219,1220],{"class":333}," string",[320,1222,711],{"class":420},[320,1224,1225],{"class":701}," b",[320,1227,705],{"class":420},[320,1229,1220],{"class":333},[320,1231,722],{"class":420},[320,1233,1234],{"class":687}," =>\n",[320,1236,1237,1240,1242,1245,1248,1251,1254,1257,1259,1262,1265,1267,1270,1273,1276,1278,1281,1284,1287,1289,1292,1294,1296,1298,1301,1304,1306,1308,1311,1313,1316,1319],{"class":322,"line":368},[320,1238,1239],{"class":416},"  a ",[320,1241,851],{"class":420},[320,1243,1244],{"class":416}," b ",[320,1246,1247],{"class":420},"?",[320,1249,1250],{"class":347}," 3",[320,1252,1253],{"class":420}," :",[320,1255,1256],{"class":416}," a",[320,1258,255],{"class":420},[320,1260,1261],{"class":416},"length ",[320,1263,1264],{"class":420},">",[320,1266,1250],{"class":347},[320,1268,1269],{"class":420}," &&",[320,1271,1272],{"class":694}," lev",[320,1274,1275],{"class":416},"(a",[320,1277,711],{"class":420},[320,1279,1280],{"class":416}," b) ",[320,1282,1283],{"class":420},"\u003C=",[320,1285,1286],{"class":416}," Math",[320,1288,255],{"class":420},[320,1290,1291],{"class":694},"floor",[320,1293,1275],{"class":416},[320,1295,255],{"class":420},[320,1297,1261],{"class":416},[320,1299,1300],{"class":420},"\u002F",[320,1302,1303],{"class":347}," 4",[320,1305,1120],{"class":416},[320,1307,1247],{"class":420},[320,1309,1310],{"class":347}," 2",[320,1312,1253],{"class":420},[320,1314,1315],{"class":420}," -",[320,1317,1318],{"class":347},"1",[320,1320,766],{"class":420},[320,1322,1323],{"class":322,"line":375},[320,1324,372],{"emptyLinePlaceholder":371},[320,1326,1327,1330,1332,1335,1338,1340,1343,1346,1348,1350,1353,1355,1358,1361],{"class":322,"line":387},[320,1328,1329],{"class":771},"for",[320,1331,1109],{"class":416},[320,1333,1334],{"class":687},"let",[320,1336,1337],{"class":416}," i ",[320,1339,421],{"class":420},[320,1341,1342],{"class":347}," 1",[320,1344,1345],{"class":420},";",[320,1347,1337],{"class":416},[320,1349,1283],{"class":420},[320,1351,1352],{"class":416}," n",[320,1354,1345],{"class":420},[320,1356,1357],{"class":416}," i",[320,1359,1360],{"class":420},"++",[320,1362,439],{"class":416},[320,1364,1365,1368,1370,1372,1375,1377,1379,1381,1383,1385,1388,1390,1393,1395,1397],{"class":322,"line":413},[320,1366,1367],{"class":771},"  for",[320,1369,1109],{"class":416},[320,1371,1334],{"class":687},[320,1373,1374],{"class":416}," j ",[320,1376,421],{"class":420},[320,1378,1342],{"class":347},[320,1380,1345],{"class":420},[320,1382,1374],{"class":416},[320,1384,1283],{"class":420},[320,1386,1387],{"class":416}," m",[320,1389,1345],{"class":420},[320,1391,1392],{"class":416}," j",[320,1394,1360],{"class":420},[320,1396,1120],{"class":416},[320,1398,492],{"class":420},[320,1400,1401,1404,1407,1409,1412,1415,1418,1421,1423,1426,1429,1431,1433,1436,1439,1442,1444,1447,1449,1451,1453,1455,1458,1460,1463,1465,1467,1469,1471,1474,1476,1479,1481,1483,1485,1487,1489,1491,1493,1495,1497,1499,1502,1504,1506,1508,1510,1512,1514,1516,1518,1520,1522,1524,1526,1528],{"class":322,"line":523},[320,1402,1403],{"class":687},"    const",[320,1405,1406],{"class":416}," d",[320,1408,338],{"class":420},[320,1410,1411],{"class":416}," S",[320,1413,1414],{"class":783},"[",[320,1416,1417],{"class":416},"i",[320,1419,1420],{"class":420},"-",[320,1422,1318],{"class":347},[320,1424,1425],{"class":783},"][",[320,1427,1428],{"class":416},"j",[320,1430,1420],{"class":420},[320,1432,1318],{"class":347},[320,1434,1435],{"class":783},"] ",[320,1437,1438],{"class":420},"+",[320,1440,1441],{"class":694}," score",[320,1443,698],{"class":783},[320,1445,1446],{"class":416},"A",[320,1448,1414],{"class":783},[320,1450,1417],{"class":416},[320,1452,1420],{"class":420},[320,1454,1318],{"class":347},[320,1456,1457],{"class":783},"]",[320,1459,711],{"class":420},[320,1461,1462],{"class":416}," B",[320,1464,1414],{"class":783},[320,1466,1428],{"class":416},[320,1468,1420],{"class":420},[320,1470,1318],{"class":347},[320,1472,1473],{"class":783},"])",[320,1475,711],{"class":420},[320,1477,1478],{"class":416}," u",[320,1480,338],{"class":420},[320,1482,1411],{"class":416},[320,1484,1414],{"class":783},[320,1486,1417],{"class":416},[320,1488,1420],{"class":420},[320,1490,1318],{"class":347},[320,1492,1425],{"class":783},[320,1494,1428],{"class":416},[320,1496,1435],{"class":783},[320,1498,1438],{"class":420},[320,1500,1501],{"class":416}," GAP",[320,1503,711],{"class":420},[320,1505,1034],{"class":416},[320,1507,338],{"class":420},[320,1509,1411],{"class":416},[320,1511,1414],{"class":783},[320,1513,1417],{"class":416},[320,1515,1425],{"class":783},[320,1517,1428],{"class":416},[320,1519,1420],{"class":420},[320,1521,1318],{"class":347},[320,1523,1435],{"class":783},[320,1525,1438],{"class":420},[320,1527,1501],{"class":416},[320,1529,766],{"class":420},[320,1531,1532,1535,1537,1540,1543,1545,1547,1549,1551,1553,1555,1558,1560,1562,1564,1566,1568,1570,1572,1574,1576,1579,1581,1583,1585,1587,1589,1591,1593,1595,1597,1600,1603,1605,1608,1610,1612,1614,1616,1618,1620,1622,1624,1626,1628,1630,1632,1634,1636,1638,1640,1642,1644,1646,1648,1650,1652,1654,1656,1658,1660,1662,1664,1666,1668,1670,1672,1674,1676,1678,1680,1682,1684,1686,1688,1690,1692,1694],{"class":322,"line":529},[320,1533,1534],{"class":771},"    if",[320,1536,1109],{"class":783},[320,1538,1539],{"class":416},"d",[320,1541,1542],{"class":420}," >=",[320,1544,1478],{"class":416},[320,1546,1269],{"class":420},[320,1548,1406],{"class":416},[320,1550,1542],{"class":420},[320,1552,1034],{"class":416},[320,1554,1120],{"class":783},[320,1556,1557],{"class":420},"{",[320,1559,1411],{"class":416},[320,1561,1414],{"class":783},[320,1563,1417],{"class":416},[320,1565,1425],{"class":783},[320,1567,1428],{"class":416},[320,1569,1435],{"class":783},[320,1571,421],{"class":420},[320,1573,1406],{"class":416},[320,1575,1345],{"class":420},[320,1577,1578],{"class":416}," T",[320,1580,1414],{"class":783},[320,1582,1417],{"class":416},[320,1584,1425],{"class":783},[320,1586,1428],{"class":416},[320,1588,1435],{"class":783},[320,1590,421],{"class":420},[320,1592,1342],{"class":347},[320,1594,1345],{"class":420},[320,1596,754],{"class":420},[320,1598,1599],{"class":771}," else",[320,1601,1602],{"class":771}," if",[320,1604,1109],{"class":783},[320,1606,1607],{"class":416},"u",[320,1609,1542],{"class":420},[320,1611,1034],{"class":416},[320,1613,1120],{"class":783},[320,1615,1557],{"class":420},[320,1617,1411],{"class":416},[320,1619,1414],{"class":783},[320,1621,1417],{"class":416},[320,1623,1425],{"class":783},[320,1625,1428],{"class":416},[320,1627,1435],{"class":783},[320,1629,421],{"class":420},[320,1631,1478],{"class":416},[320,1633,1345],{"class":420},[320,1635,1578],{"class":416},[320,1637,1414],{"class":783},[320,1639,1417],{"class":416},[320,1641,1425],{"class":783},[320,1643,1428],{"class":416},[320,1645,1435],{"class":783},[320,1647,421],{"class":420},[320,1649,1310],{"class":347},[320,1651,1345],{"class":420},[320,1653,754],{"class":420},[320,1655,1599],{"class":771},[320,1657,733],{"class":420},[320,1659,1411],{"class":416},[320,1661,1414],{"class":783},[320,1663,1417],{"class":416},[320,1665,1425],{"class":783},[320,1667,1428],{"class":416},[320,1669,1435],{"class":783},[320,1671,421],{"class":420},[320,1673,1034],{"class":416},[320,1675,1345],{"class":420},[320,1677,1578],{"class":416},[320,1679,1414],{"class":783},[320,1681,1417],{"class":416},[320,1683,1425],{"class":783},[320,1685,1428],{"class":416},[320,1687,1435],{"class":783},[320,1689,421],{"class":420},[320,1691,1250],{"class":347},[320,1693,1345],{"class":420},[320,1695,1696],{"class":420}," }\n",[320,1698,1699],{"class":322,"line":538},[320,1700,1701],{"class":420},"  }\n",[320,1703,1704],{"class":322,"line":544},[320,1705,1706],{"class":326},"\u002F\u002F walk back: matched words take whisper's times; the rest (\"Three\") are\n",[320,1708,1709],{"class":322,"line":553},[320,1710,1711],{"class":326},"\u002F\u002F interpolated between their matched neighbours\n",[238,1713,1714],{},"Step 2, forced alignment on the vocal stem:",[310,1716,1720],{"className":1717,"code":1718,"language":1719,"meta":315,"style":315},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# scripts\u002Fforced-align.py: tighten each line on the isolated vocal\nsep = get_model(\"htdemucs\")                         # 1. demucs pulls the vocal stem\nbundle = torchaudio.pipelines.MMS_FA                # 2. CTC emissions, 20 ms frames\nfor i, line in enumerate(lines):                    # 3. per line, near whisper's guess\n    lo, hi = ws[0][\"s\"] - PAD, ws[-1][\"e\"] + PAD\n    tokens = [\"*\"] + [spell(w[\"w\"]) for w in ws] + [\"*\"]   # \"30\" -> \"three zero\"\n    spans = aligner(em[a:b], tokenizer(tokens))[1:-1]\n    score = float(np.mean([s.score for sp in spans for s in sp]))\n    if score \u003C 0.2:                                 # shouted or vocoded: keep whisper\n        kept.append(ws); continue\n    for w, sp in zip(ws, spans):\n        w[\"s\"], w[\"e\"] = (a + sp[0].start) \u002F fps, (a + sp[-1].end) \u002F fps\n# whisper runs early by a fairly constant amount, so nudge the lines we couldn't refine\n","python",[317,1721,1722,1727,1732,1737,1742,1747,1752,1757,1762,1767,1772,1777,1782],{"__ignoreMap":315},[320,1723,1724],{"class":322,"line":323},[320,1725,1726],{},"# scripts\u002Fforced-align.py: tighten each line on the isolated vocal\n",[320,1728,1729],{"class":322,"line":330},[320,1730,1731],{},"sep = get_model(\"htdemucs\")                         # 1. demucs pulls the vocal stem\n",[320,1733,1734],{"class":322,"line":362},[320,1735,1736],{},"bundle = torchaudio.pipelines.MMS_FA                # 2. CTC emissions, 20 ms frames\n",[320,1738,1739],{"class":322,"line":368},[320,1740,1741],{},"for i, line in enumerate(lines):                    # 3. per line, near whisper's guess\n",[320,1743,1744],{"class":322,"line":375},[320,1745,1746],{},"    lo, hi = ws[0][\"s\"] - PAD, ws[-1][\"e\"] + PAD\n",[320,1748,1749],{"class":322,"line":387},[320,1750,1751],{},"    tokens = [\"*\"] + [spell(w[\"w\"]) for w in ws] + [\"*\"]   # \"30\" -> \"three zero\"\n",[320,1753,1754],{"class":322,"line":413},[320,1755,1756],{},"    spans = aligner(em[a:b], tokenizer(tokens))[1:-1]\n",[320,1758,1759],{"class":322,"line":523},[320,1760,1761],{},"    score = float(np.mean([s.score for sp in spans for s in sp]))\n",[320,1763,1764],{"class":322,"line":529},[320,1765,1766],{},"    if score \u003C 0.2:                                 # shouted or vocoded: keep whisper\n",[320,1768,1769],{"class":322,"line":538},[320,1770,1771],{},"        kept.append(ws); continue\n",[320,1773,1774],{"class":322,"line":544},[320,1775,1776],{},"    for w, sp in zip(ws, spans):\n",[320,1778,1779],{"class":322,"line":553},[320,1780,1781],{},"        w[\"s\"], w[\"e\"] = (a + sp[0].start) \u002F fps, (a + sp[-1].end) \u002F fps\n",[320,1783,1784],{"class":322,"line":562},[320,1785,1786],{},"# whisper runs early by a fairly constant amount, so nudge the lines we couldn't refine\n",[287,1788,1790],{"id":1789},"_4-film-the-video-is-a-function-of-time","4. Film: the video is a function of time",[238,1792,1793,1794,1796,1797,1800],{},"Nothing in the video is animated in the usual sense. Every object, light, caption and overlay is computed from ",[317,1795,829],{},", the current audio time, plus the word timings and the bass and loudness at that frame. Any frame can be drawn on its own, so you can scrub, jump with ",[317,1798,1799],{},"?t=12",", or have a headless browser render frame 4,512 without playing the first 4,511.",[280,1802],{"part":1803,"src":284,"title":1805},[1804],"film","A toy frame of The Steep Part at t = 15.2 s: a neon exponential curve over a pulsing grid, an 'autopilot · hands off' clock at 00:28, and the karaoke caption 'Now my Tesla drives me thirty minutes, hands off, no strain'",[267,1807,1809],{"icon":1808},"i-lucide-volume-2",[238,1810,1811],{},"Press play for sound, or drag the slider. The toy frame is drawn from the real Steep Part data, with the real audio from the opening through \"Linear brain on an exponential plane\".",[238,1813,1814,1815,1818,1819,1822,1823,1826],{},"Overlays are timed in words, not seconds. ",[317,1816,1817],{},"L(1)"," is when line 1 starts, ",[317,1820,1821],{},"E(1)"," when it ends, and ",[317,1824,1825],{},"W(5, \u002Fthrust\u002F)"," is the moment \"thrust\" is sung. Retime the song and every overlay follows.",[310,1828,1832],{"className":1829,"code":1830,"language":1831,"meta":315,"style":315},"language-html shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C!-- video\u002F24-the-steep-part\u002Fsong.ts -->\n\u003Cdiv data-show=\"L1+0.1|L2-0.1\">\n  autopilot · hands off\n  \u003Cbig data-count=\"0,30\"\n       data-at=\"L1+0.1\" data-until=\"E1\"\n       data-fmt=\"clock\">00:00\u003C\u002Fbig>\n\u003C\u002Fdiv>\n","html",[317,1833,1834,1839,1862,1867,1888,1914,1940],{"__ignoreMap":315},[320,1835,1836],{"class":322,"line":323},[320,1837,1838],{"class":326},"\u003C!-- video\u002F24-the-steep-part\u002Fsong.ts -->\n",[320,1840,1841,1844,1847,1850,1852,1854,1857,1859],{"class":322,"line":330},[320,1842,1843],{"class":420},"\u003C",[320,1845,1846],{"class":783},"div",[320,1848,1849],{"class":687}," data-show",[320,1851,421],{"class":420},[320,1853,860],{"class":420},[320,1855,1856],{"class":337},"L1+0.1|L2-0.1",[320,1858,860],{"class":420},[320,1860,1861],{"class":420},">\n",[320,1863,1864],{"class":322,"line":362},[320,1865,1866],{"class":416},"  autopilot · hands off\n",[320,1868,1869,1872,1875,1878,1880,1882,1885],{"class":322,"line":368},[320,1870,1871],{"class":420},"  \u003C",[320,1873,1874],{"class":783},"big",[320,1876,1877],{"class":687}," data-count",[320,1879,421],{"class":420},[320,1881,860],{"class":420},[320,1883,1884],{"class":337},"0,30",[320,1886,1887],{"class":420},"\"\n",[320,1889,1890,1893,1895,1897,1900,1902,1905,1907,1909,1912],{"class":322,"line":375},[320,1891,1892],{"class":687},"       data-at",[320,1894,421],{"class":420},[320,1896,860],{"class":420},[320,1898,1899],{"class":337},"L1+0.1",[320,1901,860],{"class":420},[320,1903,1904],{"class":687}," data-until",[320,1906,421],{"class":420},[320,1908,860],{"class":420},[320,1910,1911],{"class":337},"E1",[320,1913,1887],{"class":420},[320,1915,1916,1919,1921,1923,1926,1928,1930,1933,1936,1938],{"class":322,"line":387},[320,1917,1918],{"class":687},"       data-fmt",[320,1920,421],{"class":420},[320,1922,860],{"class":420},[320,1924,1925],{"class":337},"clock",[320,1927,860],{"class":420},[320,1929,1264],{"class":420},[320,1931,1932],{"class":416},"00:00",[320,1934,1935],{"class":420},"\u003C\u002F",[320,1937,1874],{"class":783},[320,1939,1861],{"class":420},[320,1941,1942,1944,1946],{"class":322,"line":413},[320,1943,1935],{"class":420},[320,1945,1846],{"class":783},[320,1947,1861],{"class":420},[238,1949,1950],{},"The music reaches into the scene:",[310,1952,1954],{"className":673,"code":1953,"language":675,"meta":315,"style":315},"\u002F\u002F video\u002Ftl.ts\nbass(t)   \u002F\u002F smoothed bass energy, 0..1\nrms(t)    \u002F\u002F loudness at this frame\nbeat(t)   \u002F\u002F 1 on the beat, decays to 0\nvis(t, a, b)  \u002F\u002F fade in at a, out at b\nrand(n)   \u002F\u002F seeded, so frames repeat\n",[317,1955,1956,1961,1972,1983,1993,2013],{"__ignoreMap":315},[320,1957,1958],{"class":322,"line":323},[320,1959,1960],{"class":326},"\u002F\u002F video\u002Ftl.ts\n",[320,1962,1963,1966,1969],{"class":322,"line":330},[320,1964,1965],{"class":694},"bass",[320,1967,1968],{"class":416},"(t)   ",[320,1970,1971],{"class":326},"\u002F\u002F smoothed bass energy, 0..1\n",[320,1973,1974,1977,1980],{"class":322,"line":362},[320,1975,1976],{"class":694},"rms",[320,1978,1979],{"class":416},"(t)    ",[320,1981,1982],{"class":326},"\u002F\u002F loudness at this frame\n",[320,1984,1985,1988,1990],{"class":322,"line":368},[320,1986,1987],{"class":694},"beat",[320,1989,1968],{"class":416},[320,1991,1992],{"class":326},"\u002F\u002F 1 on the beat, decays to 0\n",[320,1994,1995,1998,2001,2003,2005,2007,2010],{"class":322,"line":375},[320,1996,1997],{"class":694},"vis",[320,1999,2000],{"class":416},"(t",[320,2002,711],{"class":420},[320,2004,1256],{"class":416},[320,2006,711],{"class":420},[320,2008,2009],{"class":416}," b)  ",[320,2011,2012],{"class":326},"\u002F\u002F fade in at a, out at b\n",[320,2014,2015,2018,2021],{"class":322,"line":387},[320,2016,2017],{"class":694},"rand",[320,2019,2020],{"class":416},"(n)   ",[320,2022,2023],{"class":326},"\u002F\u002F seeded, so frames repeat\n",[238,2025,2026],{},"Each song gets its own world: a neon curve for The Steep Part, a watercolor church for Read the Diff, a Broadway stage for Data Here, Data There. A shared film kit adds depth of field, bloom, grain and a letterbox.",[238,2028,2029,2030,2032],{},"A world's shots cut on lyric lines and are lit by the bass. The camera, sky, bloom and every prop are set from ",[317,2031,829],{}," on each frame; nothing carries over from the frame before.",[310,2034,2036],{"className":673,"code":2035,"language":675,"meta":315,"style":315},"\u002F\u002F video\u002F24-the-steep-part\u002Fworld.ts: the shot list is written in lyric lines\nconst shots = () => [\n  { name: \"intro\",   at: 0,            label: \"intro\" },\n  { name: \"prius\",   at: L(0) - 0.08,  label: \"the prius\" },\n  { name: \"tesla\",   at: L(1) - 0.08,  label: \"hands off\" },\n  { name: \"engine\",  at: L(4) - 0.08,  label: \"one shot\" },\n  { name: \"agents\",  at: L(10) - 0.08, label: \"forty agents\" },\n  { name: \"standup\", at: L(14) - 0.08, label: \"the standup\" },\n  \u002F\u002F …\n];\n\nupdate(t: number) {\n  const shot = shotAt(t), b = bass(t), bt = beat(t);\n  this.bloom.strength = 0.85 + b * 0.55;           \u002F\u002F the bass blooms the lights\n  this.sky.material.uniforms.pulse.value = b;\n  switch (shot.name) {\n    case \"prius\": case \"tesla\": this.roadShot(t, shot, look, b); break;\n    case \"engine\": this.engineShot(t, shot.at, look, b); break;\n    case \"triple\":\n      \u002F\u002F \"Terrified. Thrilled. Tentative.\": one cut per word\n      if (t \u003C W(51, \u002F^thrilled\u002Fi) - 0.05) this.curveShot(t, shot, look, b, bt);\n      else if (t \u003C W(51, \u002F^tentative\u002Fi) - 0.05) this.engineShot(t, W(51, \u002F^thrilled\u002Fi) - 2.5, look, b);\n      else this.roomShot(t, shot, look, bt);\n  }\n}\n",[317,2037,2038,2043,2060,2103,2155,2203,2253,2303,2353,2358,2364,2368,2378,2429,2463,2496,2516,2573,2620,2635,2641,2715,2803,2833,2838],{"__ignoreMap":315},[320,2039,2040],{"class":322,"line":323},[320,2041,2042],{"class":326},"\u002F\u002F video\u002F24-the-steep-part\u002Fworld.ts: the shot list is written in lyric lines\n",[320,2044,2045,2047,2050,2052,2055,2057],{"class":322,"line":330},[320,2046,1206],{"class":687},[320,2048,2049],{"class":416}," shots ",[320,2051,421],{"class":420},[320,2053,2054],{"class":420}," ()",[320,2056,789],{"class":687},[320,2058,2059],{"class":416}," [\n",[320,2061,2062,2065,2068,2070,2072,2075,2077,2079,2082,2084,2087,2089,2092,2094,2096,2098,2100],{"class":322,"line":362},[320,2063,2064],{"class":420},"  {",[320,2066,2067],{"class":783}," name",[320,2069,705],{"class":420},[320,2071,854],{"class":420},[320,2073,2074],{"class":337},"intro",[320,2076,860],{"class":420},[320,2078,711],{"class":420},[320,2080,2081],{"class":783},"   at",[320,2083,705],{"class":420},[320,2085,2086],{"class":347}," 0",[320,2088,711],{"class":420},[320,2090,2091],{"class":783},"            label",[320,2093,705],{"class":420},[320,2095,854],{"class":420},[320,2097,2074],{"class":337},[320,2099,860],{"class":420},[320,2101,2102],{"class":420}," },\n",[320,2104,2105,2107,2109,2111,2113,2116,2118,2120,2122,2124,2127,2129,2132,2134,2136,2139,2141,2144,2146,2148,2151,2153],{"class":322,"line":368},[320,2106,2064],{"class":420},[320,2108,2067],{"class":783},[320,2110,705],{"class":420},[320,2112,854],{"class":420},[320,2114,2115],{"class":337},"prius",[320,2117,860],{"class":420},[320,2119,711],{"class":420},[320,2121,2081],{"class":783},[320,2123,705],{"class":420},[320,2125,2126],{"class":694}," L",[320,2128,698],{"class":416},[320,2130,2131],{"class":347},"0",[320,2133,1120],{"class":416},[320,2135,1420],{"class":420},[320,2137,2138],{"class":347}," 0.08",[320,2140,711],{"class":420},[320,2142,2143],{"class":783},"  label",[320,2145,705],{"class":420},[320,2147,854],{"class":420},[320,2149,2150],{"class":337},"the prius",[320,2152,860],{"class":420},[320,2154,2102],{"class":420},[320,2156,2157,2159,2161,2163,2165,2168,2170,2172,2174,2176,2178,2180,2182,2184,2186,2188,2190,2192,2194,2196,2199,2201],{"class":322,"line":375},[320,2158,2064],{"class":420},[320,2160,2067],{"class":783},[320,2162,705],{"class":420},[320,2164,854],{"class":420},[320,2166,2167],{"class":337},"tesla",[320,2169,860],{"class":420},[320,2171,711],{"class":420},[320,2173,2081],{"class":783},[320,2175,705],{"class":420},[320,2177,2126],{"class":694},[320,2179,698],{"class":416},[320,2181,1318],{"class":347},[320,2183,1120],{"class":416},[320,2185,1420],{"class":420},[320,2187,2138],{"class":347},[320,2189,711],{"class":420},[320,2191,2143],{"class":783},[320,2193,705],{"class":420},[320,2195,854],{"class":420},[320,2197,2198],{"class":337},"hands off",[320,2200,860],{"class":420},[320,2202,2102],{"class":420},[320,2204,2205,2207,2209,2211,2213,2216,2218,2220,2223,2225,2227,2229,2232,2234,2236,2238,2240,2242,2244,2246,2249,2251],{"class":322,"line":387},[320,2206,2064],{"class":420},[320,2208,2067],{"class":783},[320,2210,705],{"class":420},[320,2212,854],{"class":420},[320,2214,2215],{"class":337},"engine",[320,2217,860],{"class":420},[320,2219,711],{"class":420},[320,2221,2222],{"class":783},"  at",[320,2224,705],{"class":420},[320,2226,2126],{"class":694},[320,2228,698],{"class":416},[320,2230,2231],{"class":347},"4",[320,2233,1120],{"class":416},[320,2235,1420],{"class":420},[320,2237,2138],{"class":347},[320,2239,711],{"class":420},[320,2241,2143],{"class":783},[320,2243,705],{"class":420},[320,2245,854],{"class":420},[320,2247,2248],{"class":337},"one shot",[320,2250,860],{"class":420},[320,2252,2102],{"class":420},[320,2254,2255,2257,2259,2261,2263,2266,2268,2270,2272,2274,2276,2278,2281,2283,2285,2287,2289,2292,2294,2296,2299,2301],{"class":322,"line":413},[320,2256,2064],{"class":420},[320,2258,2067],{"class":783},[320,2260,705],{"class":420},[320,2262,854],{"class":420},[320,2264,2265],{"class":337},"agents",[320,2267,860],{"class":420},[320,2269,711],{"class":420},[320,2271,2222],{"class":783},[320,2273,705],{"class":420},[320,2275,2126],{"class":694},[320,2277,698],{"class":416},[320,2279,2280],{"class":347},"10",[320,2282,1120],{"class":416},[320,2284,1420],{"class":420},[320,2286,2138],{"class":347},[320,2288,711],{"class":420},[320,2290,2291],{"class":783}," label",[320,2293,705],{"class":420},[320,2295,854],{"class":420},[320,2297,2298],{"class":337},"forty agents",[320,2300,860],{"class":420},[320,2302,2102],{"class":420},[320,2304,2305,2307,2309,2311,2313,2316,2318,2320,2323,2325,2327,2329,2332,2334,2336,2338,2340,2342,2344,2346,2349,2351],{"class":322,"line":523},[320,2306,2064],{"class":420},[320,2308,2067],{"class":783},[320,2310,705],{"class":420},[320,2312,854],{"class":420},[320,2314,2315],{"class":337},"standup",[320,2317,860],{"class":420},[320,2319,711],{"class":420},[320,2321,2322],{"class":783}," at",[320,2324,705],{"class":420},[320,2326,2126],{"class":694},[320,2328,698],{"class":416},[320,2330,2331],{"class":347},"14",[320,2333,1120],{"class":416},[320,2335,1420],{"class":420},[320,2337,2138],{"class":347},[320,2339,711],{"class":420},[320,2341,2291],{"class":783},[320,2343,705],{"class":420},[320,2345,854],{"class":420},[320,2347,2348],{"class":337},"the standup",[320,2350,860],{"class":420},[320,2352,2102],{"class":420},[320,2354,2355],{"class":322,"line":529},[320,2356,2357],{"class":326},"  \u002F\u002F …\n",[320,2359,2360,2362],{"class":322,"line":538},[320,2361,1457],{"class":416},[320,2363,766],{"class":420},[320,2365,2366],{"class":322,"line":544},[320,2367,372],{"emptyLinePlaceholder":371},[320,2369,2370,2373,2376],{"class":322,"line":553},[320,2371,2372],{"class":694},"update",[320,2374,2375],{"class":416},"(t: number) ",[320,2377,492],{"class":420},[320,2379,2380,2382,2385,2387,2390,2392,2394,2396,2398,2400,2402,2405,2407,2409,2411,2413,2416,2418,2421,2423,2425,2427],{"class":322,"line":562},[320,2381,730],{"class":687},[320,2383,2384],{"class":416}," shot",[320,2386,338],{"class":420},[320,2388,2389],{"class":694}," shotAt",[320,2391,698],{"class":783},[320,2393,829],{"class":416},[320,2395,722],{"class":783},[320,2397,711],{"class":420},[320,2399,1225],{"class":416},[320,2401,338],{"class":420},[320,2403,2404],{"class":694}," bass",[320,2406,698],{"class":783},[320,2408,829],{"class":416},[320,2410,722],{"class":783},[320,2412,711],{"class":420},[320,2414,2415],{"class":416}," bt",[320,2417,338],{"class":420},[320,2419,2420],{"class":694}," beat",[320,2422,698],{"class":783},[320,2424,829],{"class":416},[320,2426,722],{"class":783},[320,2428,766],{"class":420},[320,2430,2431,2434,2437,2439,2442,2444,2447,2450,2452,2455,2458,2460],{"class":322,"line":568},[320,2432,2433],{"class":420},"  this.",[320,2435,2436],{"class":416},"bloom",[320,2438,255],{"class":420},[320,2440,2441],{"class":416},"strength",[320,2443,338],{"class":420},[320,2445,2446],{"class":347}," 0.85",[320,2448,2449],{"class":420}," +",[320,2451,1225],{"class":416},[320,2453,2454],{"class":420}," *",[320,2456,2457],{"class":347}," 0.55",[320,2459,1345],{"class":420},[320,2461,2462],{"class":326},"           \u002F\u002F the bass blooms the lights\n",[320,2464,2465,2467,2470,2472,2475,2477,2480,2482,2485,2487,2490,2492,2494],{"class":322,"line":574},[320,2466,2433],{"class":420},[320,2468,2469],{"class":416},"sky",[320,2471,255],{"class":420},[320,2473,2474],{"class":416},"material",[320,2476,255],{"class":420},[320,2478,2479],{"class":416},"uniforms",[320,2481,255],{"class":420},[320,2483,2484],{"class":416},"pulse",[320,2486,255],{"class":420},[320,2488,2489],{"class":416},"value",[320,2491,338],{"class":420},[320,2493,1225],{"class":416},[320,2495,766],{"class":420},[320,2497,2499,2502,2504,2507,2509,2512,2514],{"class":322,"line":2498},16,[320,2500,2501],{"class":771},"  switch",[320,2503,1109],{"class":783},[320,2505,2506],{"class":416},"shot",[320,2508,255],{"class":420},[320,2510,2511],{"class":416},"name",[320,2513,1120],{"class":783},[320,2515,492],{"class":420},[320,2517,2519,2522,2524,2526,2528,2530,2533,2535,2537,2539,2541,2544,2547,2549,2551,2553,2555,2557,2560,2562,2564,2566,2568,2571],{"class":322,"line":2518},17,[320,2520,2521],{"class":771},"    case",[320,2523,854],{"class":420},[320,2525,2115],{"class":337},[320,2527,860],{"class":420},[320,2529,705],{"class":420},[320,2531,2532],{"class":771}," case",[320,2534,854],{"class":420},[320,2536,2167],{"class":337},[320,2538,860],{"class":420},[320,2540,705],{"class":420},[320,2542,2543],{"class":420}," this.",[320,2545,2546],{"class":694},"roadShot",[320,2548,698],{"class":783},[320,2550,829],{"class":416},[320,2552,711],{"class":420},[320,2554,2384],{"class":416},[320,2556,711],{"class":420},[320,2558,2559],{"class":416}," look",[320,2561,711],{"class":420},[320,2563,1225],{"class":416},[320,2565,722],{"class":783},[320,2567,1345],{"class":420},[320,2569,2570],{"class":771}," break",[320,2572,766],{"class":420},[320,2574,2576,2578,2580,2582,2584,2586,2588,2591,2593,2595,2597,2599,2601,2604,2606,2608,2610,2612,2614,2616,2618],{"class":322,"line":2575},18,[320,2577,2521],{"class":771},[320,2579,854],{"class":420},[320,2581,2215],{"class":337},[320,2583,860],{"class":420},[320,2585,705],{"class":420},[320,2587,2543],{"class":420},[320,2589,2590],{"class":694},"engineShot",[320,2592,698],{"class":783},[320,2594,829],{"class":416},[320,2596,711],{"class":420},[320,2598,2384],{"class":416},[320,2600,255],{"class":420},[320,2602,2603],{"class":416},"at",[320,2605,711],{"class":420},[320,2607,2559],{"class":416},[320,2609,711],{"class":420},[320,2611,1225],{"class":416},[320,2613,722],{"class":783},[320,2615,1345],{"class":420},[320,2617,2570],{"class":771},[320,2619,766],{"class":420},[320,2621,2623,2625,2627,2630,2632],{"class":322,"line":2622},19,[320,2624,2521],{"class":771},[320,2626,854],{"class":420},[320,2628,2629],{"class":337},"triple",[320,2631,860],{"class":420},[320,2633,2634],{"class":420},":\n",[320,2636,2638],{"class":322,"line":2637},20,[320,2639,2640],{"class":326},"      \u002F\u002F \"Terrified. Thrilled. Tentative.\": one cut per word\n",[320,2642,2644,2647,2649,2651,2654,2657,2659,2662,2664,2666,2669,2672,2674,2676,2678,2680,2683,2685,2688,2691,2693,2695,2697,2699,2701,2703,2705,2707,2709,2711,2713],{"class":322,"line":2643},21,[320,2645,2646],{"class":771},"      if",[320,2648,1109],{"class":783},[320,2650,829],{"class":416},[320,2652,2653],{"class":420}," \u003C",[320,2655,2656],{"class":694}," W",[320,2658,698],{"class":783},[320,2660,2661],{"class":347},"51",[320,2663,711],{"class":420},[320,2665,351],{"class":420},[320,2667,2668],{"class":771},"^",[320,2670,2671],{"class":337},"thrilled",[320,2673,1300],{"class":420},[320,2675,1417],{"class":347},[320,2677,1120],{"class":783},[320,2679,1420],{"class":420},[320,2681,2682],{"class":347}," 0.05",[320,2684,1120],{"class":783},[320,2686,2687],{"class":420},"this.",[320,2689,2690],{"class":694},"curveShot",[320,2692,698],{"class":783},[320,2694,829],{"class":416},[320,2696,711],{"class":420},[320,2698,2384],{"class":416},[320,2700,711],{"class":420},[320,2702,2559],{"class":416},[320,2704,711],{"class":420},[320,2706,1225],{"class":416},[320,2708,711],{"class":420},[320,2710,2415],{"class":416},[320,2712,722],{"class":783},[320,2714,766],{"class":420},[320,2716,2718,2721,2723,2725,2727,2729,2731,2733,2735,2737,2739,2741,2744,2746,2748,2750,2752,2754,2756,2758,2760,2762,2764,2766,2768,2770,2772,2774,2776,2778,2780,2782,2784,2786,2788,2791,2793,2795,2797,2799,2801],{"class":322,"line":2717},22,[320,2719,2720],{"class":771},"      else",[320,2722,1602],{"class":771},[320,2724,1109],{"class":783},[320,2726,829],{"class":416},[320,2728,2653],{"class":420},[320,2730,2656],{"class":694},[320,2732,698],{"class":783},[320,2734,2661],{"class":347},[320,2736,711],{"class":420},[320,2738,351],{"class":420},[320,2740,2668],{"class":771},[320,2742,2743],{"class":337},"tentative",[320,2745,1300],{"class":420},[320,2747,1417],{"class":347},[320,2749,1120],{"class":783},[320,2751,1420],{"class":420},[320,2753,2682],{"class":347},[320,2755,1120],{"class":783},[320,2757,2687],{"class":420},[320,2759,2590],{"class":694},[320,2761,698],{"class":783},[320,2763,829],{"class":416},[320,2765,711],{"class":420},[320,2767,2656],{"class":694},[320,2769,698],{"class":783},[320,2771,2661],{"class":347},[320,2773,711],{"class":420},[320,2775,351],{"class":420},[320,2777,2668],{"class":771},[320,2779,2671],{"class":337},[320,2781,1300],{"class":420},[320,2783,1417],{"class":347},[320,2785,1120],{"class":783},[320,2787,1420],{"class":420},[320,2789,2790],{"class":347}," 2.5",[320,2792,711],{"class":420},[320,2794,2559],{"class":416},[320,2796,711],{"class":420},[320,2798,1225],{"class":416},[320,2800,722],{"class":783},[320,2802,766],{"class":420},[320,2804,2806,2808,2810,2813,2815,2817,2819,2821,2823,2825,2827,2829,2831],{"class":322,"line":2805},23,[320,2807,2720],{"class":771},[320,2809,2543],{"class":420},[320,2811,2812],{"class":694},"roomShot",[320,2814,698],{"class":783},[320,2816,829],{"class":416},[320,2818,711],{"class":420},[320,2820,2384],{"class":416},[320,2822,711],{"class":420},[320,2824,2559],{"class":416},[320,2826,711],{"class":420},[320,2828,2415],{"class":416},[320,2830,722],{"class":783},[320,2832,766],{"class":420},[320,2834,2836],{"class":322,"line":2835},24,[320,2837,1701],{"class":420},[320,2839,2841],{"class":322,"line":2840},25,[320,2842,577],{"class":420},[287,2844,2846],{"id":2845},"_5-ship-check-the-stills-then-build-the-pages","5. Ship: check the stills, then build the pages",[446,2848,2849,2859,2865,2875],{},[449,2850,2851,2854,2855,2858],{},[273,2852,2853],{},"Render stills."," Playwright and headless Chrome call ",[317,2856,2857],{},"__renderAt(t)"," once per shot and tile the frames into a contact sheet.",[449,2860,2861,2864],{},[273,2862,2863],{},"Flag problems."," The script flags blown-out, too-dark and repeated frames. Claude looks at the sheet for clipped text and overlays covering the scene.",[449,2866,2867,2870,2871,2874],{},[273,2868,2869],{},"Build."," ",[317,2872,2873],{},"bun catalog"," writes the album page and one page per video into this blog: the song's timings inlined, its MP3 and poster beside it, and one shared player bundle for every video.",[449,2876,2877,2880],{},[273,2878,2879],{},"Publish."," The pages ship with the blog. A hash of the code, timings and audio says when a YouTube upload is out of date.",[310,2882,2884],{"className":673,"code":2883,"language":675,"meta":315,"style":315},"\u002F\u002F src\u002Fstills.ts: look at a video without watching it\nawait page.goto(`http:\u002F\u002F127.0.0.1:4321\u002Fvideo\u002F${slug}\u002F${clipId}?render`);\nfor (const [i, p] of picks.entries()) {           \u002F\u002F one pick from the middle of every shot\n  await page.evaluate(t => window.__renderAt(t), p.t);\n  const png = await page.screenshot();\n  \u002F\u002F measure luminance: >0.97 counts as clipped, \u003C0.02 as crushed,\n  \u002F\u002F and a tiny diff from the previous frame flags a shot that didn't change\n}\n",[317,2885,2886,2891,2936,2974,3017,3040,3045,3050],{"__ignoreMap":315},[320,2887,2888],{"class":322,"line":323},[320,2889,2890],{"class":326},"\u002F\u002F src\u002Fstills.ts: look at a video without watching it\n",[320,2892,2893,2896,2899,2901,2904,2906,2908,2911,2913,2916,2918,2920,2922,2925,2927,2930,2932,2934],{"class":322,"line":330},[320,2894,2895],{"class":771},"await",[320,2897,2898],{"class":416}," page",[320,2900,255],{"class":420},[320,2902,2903],{"class":694},"goto",[320,2905,698],{"class":416},[320,2907,1134],{"class":420},[320,2909,2910],{"class":337},"http:\u002F\u002F127.0.0.1:4321\u002Fvideo\u002F",[320,2912,1140],{"class":420},[320,2914,2915],{"class":416},"slug",[320,2917,1146],{"class":420},[320,2919,1300],{"class":337},[320,2921,1140],{"class":420},[320,2923,2924],{"class":416},"clipId",[320,2926,1146],{"class":420},[320,2928,2929],{"class":337},"?render",[320,2931,1134],{"class":420},[320,2933,722],{"class":416},[320,2935,766],{"class":420},[320,2937,2938,2940,2942,2944,2946,2948,2950,2953,2955,2958,2961,2963,2966,2969,2971],{"class":322,"line":362},[320,2939,1329],{"class":771},[320,2941,1109],{"class":416},[320,2943,1206],{"class":687},[320,2945,792],{"class":420},[320,2947,1417],{"class":416},[320,2949,711],{"class":420},[320,2951,2952],{"class":416}," p",[320,2954,1457],{"class":420},[320,2956,2957],{"class":420}," of",[320,2959,2960],{"class":416}," picks",[320,2962,255],{"class":420},[320,2964,2965],{"class":694},"entries",[320,2967,2968],{"class":416},"()) ",[320,2970,1557],{"class":420},[320,2972,2973],{"class":326},"           \u002F\u002F one pick from the middle of every shot\n",[320,2975,2976,2978,2980,2982,2985,2987,2989,2991,2994,2996,2999,3001,3003,3005,3007,3009,3011,3013,3015],{"class":322,"line":368},[320,2977,772],{"class":771},[320,2979,2898],{"class":416},[320,2981,255],{"class":420},[320,2983,2984],{"class":694},"evaluate",[320,2986,698],{"class":783},[320,2988,829],{"class":701},[320,2990,789],{"class":687},[320,2992,2993],{"class":416}," window",[320,2995,255],{"class":420},[320,2997,2998],{"class":694},"__renderAt",[320,3000,698],{"class":783},[320,3002,829],{"class":416},[320,3004,722],{"class":783},[320,3006,711],{"class":420},[320,3008,2952],{"class":416},[320,3010,255],{"class":420},[320,3012,829],{"class":416},[320,3014,722],{"class":783},[320,3016,766],{"class":420},[320,3018,3019,3021,3024,3026,3029,3031,3033,3036,3038],{"class":322,"line":375},[320,3020,730],{"class":687},[320,3022,3023],{"class":416}," png",[320,3025,338],{"class":420},[320,3027,3028],{"class":771}," await",[320,3030,2898],{"class":416},[320,3032,255],{"class":420},[320,3034,3035],{"class":694},"screenshot",[320,3037,786],{"class":783},[320,3039,766],{"class":420},[320,3041,3042],{"class":322,"line":387},[320,3043,3044],{"class":326},"  \u002F\u002F measure luminance: >0.97 counts as clipped, \u003C0.02 as crushed,\n",[320,3046,3047],{"class":322,"line":413},[320,3048,3049],{"class":326},"  \u002F\u002F and a tiny diff from the previous frame flags a shot that didn't change\n",[320,3051,3052],{"class":322,"line":523},[320,3053,577],{"class":420},[310,3055,3057],{"className":673,"code":3056,"language":675,"meta":315,"style":315},"\u002F\u002F src\u002Fcatalog.ts: one page per video, sharing one player\nfor (const c of cards) {\n  const data = videoData(c.slug, c.clipId, `${BASE}\u002Faudio\u002F${c.name}.mp3`);\n  copyFileSync(c.audio, join(siteDir, \"audio\", `${c.name}.mp3`));\n  writeFileSync(join(siteDir, `${c.name}.html`), page(`\u003Ctitle>${c.title} · I Can't Clap\u003C\u002Ftitle>\n${fonts}\n\u003Clink rel=\"stylesheet\" href=\"${BASE}\u002Fplayer.css\">\n${analytics}`, `${stage}\n\u003Cscript>window.__SONG__ = ${JSON.stringify(data)};\u003C\u002Fscript>\n\u003Cscript type=\"module\" src=\"${BASE}\u002Fplayer.js\">\u003C\u002Fscript>`));\n}\n",[317,3058,3059,3064,3083,3145,3198,3256,3265,3279,3298,3321,3341],{"__ignoreMap":315},[320,3060,3061],{"class":322,"line":323},[320,3062,3063],{"class":326},"\u002F\u002F src\u002Fcatalog.ts: one page per video, sharing one player\n",[320,3065,3066,3068,3070,3072,3075,3078,3081],{"class":322,"line":330},[320,3067,1329],{"class":771},[320,3069,1109],{"class":416},[320,3071,1206],{"class":687},[320,3073,3074],{"class":416}," c ",[320,3076,3077],{"class":420},"of",[320,3079,3080],{"class":416}," cards) ",[320,3082,492],{"class":420},[320,3084,3085,3087,3090,3092,3095,3097,3100,3102,3104,3106,3109,3111,3113,3115,3118,3121,3123,3126,3128,3130,3132,3134,3136,3139,3141,3143],{"class":322,"line":362},[320,3086,730],{"class":687},[320,3088,3089],{"class":416}," data",[320,3091,338],{"class":420},[320,3093,3094],{"class":694}," videoData",[320,3096,698],{"class":783},[320,3098,3099],{"class":416},"c",[320,3101,255],{"class":420},[320,3103,2915],{"class":416},[320,3105,711],{"class":420},[320,3107,3108],{"class":416}," c",[320,3110,255],{"class":420},[320,3112,2924],{"class":416},[320,3114,711],{"class":420},[320,3116,3117],{"class":420}," `${",[320,3119,3120],{"class":416},"BASE",[320,3122,1146],{"class":420},[320,3124,3125],{"class":337},"\u002Faudio\u002F",[320,3127,1140],{"class":420},[320,3129,3099],{"class":416},[320,3131,255],{"class":420},[320,3133,2511],{"class":416},[320,3135,1146],{"class":420},[320,3137,3138],{"class":337},".mp3",[320,3140,1134],{"class":420},[320,3142,722],{"class":783},[320,3144,766],{"class":420},[320,3146,3147,3150,3152,3154,3156,3159,3161,3164,3166,3169,3171,3173,3175,3177,3179,3181,3183,3185,3187,3189,3191,3193,3196],{"class":322,"line":368},[320,3148,3149],{"class":694},"  copyFileSync",[320,3151,698],{"class":783},[320,3153,3099],{"class":416},[320,3155,255],{"class":420},[320,3157,3158],{"class":416},"audio",[320,3160,711],{"class":420},[320,3162,3163],{"class":694}," join",[320,3165,698],{"class":783},[320,3167,3168],{"class":416},"siteDir",[320,3170,711],{"class":420},[320,3172,854],{"class":420},[320,3174,3158],{"class":337},[320,3176,860],{"class":420},[320,3178,711],{"class":420},[320,3180,3117],{"class":420},[320,3182,3099],{"class":416},[320,3184,255],{"class":420},[320,3186,2511],{"class":416},[320,3188,1146],{"class":420},[320,3190,3138],{"class":337},[320,3192,1134],{"class":420},[320,3194,3195],{"class":783},"))",[320,3197,766],{"class":420},[320,3199,3200,3203,3205,3208,3210,3212,3214,3216,3218,3220,3222,3224,3227,3229,3231,3233,3235,3237,3239,3242,3244,3246,3248,3251,3253],{"class":322,"line":375},[320,3201,3202],{"class":694},"  writeFileSync",[320,3204,698],{"class":783},[320,3206,3207],{"class":694},"join",[320,3209,698],{"class":783},[320,3211,3168],{"class":416},[320,3213,711],{"class":420},[320,3215,3117],{"class":420},[320,3217,3099],{"class":416},[320,3219,255],{"class":420},[320,3221,2511],{"class":416},[320,3223,1146],{"class":420},[320,3225,3226],{"class":337},".html",[320,3228,1134],{"class":420},[320,3230,722],{"class":783},[320,3232,711],{"class":420},[320,3234,2898],{"class":694},[320,3236,698],{"class":783},[320,3238,1134],{"class":420},[320,3240,3241],{"class":337},"\u003Ctitle>",[320,3243,1140],{"class":420},[320,3245,3099],{"class":416},[320,3247,255],{"class":420},[320,3249,3250],{"class":416},"title",[320,3252,1146],{"class":420},[320,3254,3255],{"class":337}," · I Can't Clap\u003C\u002Ftitle>\n",[320,3257,3258,3260,3263],{"class":322,"line":387},[320,3259,1140],{"class":420},[320,3261,3262],{"class":416},"fonts",[320,3264,577],{"class":420},[320,3266,3267,3270,3272,3274,3276],{"class":322,"line":413},[320,3268,3269],{"class":337},"\u003Clink rel=\"stylesheet\" href=\"",[320,3271,1140],{"class":420},[320,3273,3120],{"class":416},[320,3275,1146],{"class":420},[320,3277,3278],{"class":337},"\u002Fplayer.css\">\n",[320,3280,3281,3283,3286,3289,3291,3293,3296],{"class":322,"line":523},[320,3282,1140],{"class":420},[320,3284,3285],{"class":416},"analytics",[320,3287,3288],{"class":420},"}`",[320,3290,711],{"class":420},[320,3292,3117],{"class":420},[320,3294,3295],{"class":416},"stage",[320,3297,577],{"class":420},[320,3299,3300,3303,3305,3308,3310,3313,3316,3318],{"class":322,"line":529},[320,3301,3302],{"class":337},"\u003Cscript>window.__SONG__ = ",[320,3304,1140],{"class":420},[320,3306,3307],{"class":416},"JSON",[320,3309,255],{"class":420},[320,3311,3312],{"class":694},"stringify",[320,3314,3315],{"class":416},"(data)",[320,3317,1146],{"class":420},[320,3319,3320],{"class":337},";\u003C\u002Fscript>\n",[320,3322,3323,3326,3328,3330,3332,3335,3337,3339],{"class":322,"line":538},[320,3324,3325],{"class":337},"\u003Cscript type=\"module\" src=\"",[320,3327,1140],{"class":420},[320,3329,3120],{"class":416},[320,3331,1146],{"class":420},[320,3333,3334],{"class":337},"\u002Fplayer.js\">\u003C\u002Fscript>",[320,3336,1134],{"class":420},[320,3338,3195],{"class":783},[320,3340,766],{"class":420},[320,3342,3343],{"class":322,"line":544},[320,3344,577],{"class":420},[441,3346,3348],{"id":3347},"why-they-live-here","Why they live here",[238,3350,3351],{},"Every video, and the first version of this post, started as a Claude artifact: one self-contained HTML file and a link I could share. But an artifact gives you no metrics. I couldn't tell how many people opened one, or whether anyone watched past the first chorus.",[238,3353,3354],{},"So they live on this blog now. The pages count visits and send a few events: when a video starts, when it reaches 25, 50 and 75%, and when it finishes. Same player, just hosted where I can see whether anyone's listening.",[287,3356,3358],{"id":3357},"the-videos","The videos",[3360,3361],"album-videos",{},[287,3363,3365],{"id":3364},"by-the-numbers","By the numbers",[3367,3368,3369,3375,3381,3387],"ul",{},[449,3370,3371,3374],{},[273,3372,3373],{},"26"," songs written",[449,3376,3377,3380],{},[273,3378,3379],{},"113"," judge reviews",[449,3382,3383,3386],{},[273,3384,3385],{},"5"," videos published",[449,3388,3389,3392],{},[273,3390,3391],{},"5,458"," lines of three.js across the five worlds",[238,3394,3395],{},"Built with Claude Code and Opus 5.5, Suno, Bun and TypeScript, faster-whisper, demucs, the torchaudio MMS aligner, three.js, Playwright and uv. The timings, loudness and judge scores in this post are the real files from The Steep Part.",[3397,3398,3399],"style",{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":315,"searchDepth":330,"depth":330,"links":3401},[3402,3405,3406,3407,3408,3411,3412],{"id":289,"depth":330,"text":290,"children":3403},[3404],{"id":443,"depth":362,"text":444},{"id":643,"depth":330,"text":644},{"id":1171,"depth":330,"text":1172},{"id":1789,"depth":330,"text":1790},{"id":2845,"depth":330,"text":2846,"children":3409},[3410],{"id":3347,"depth":362,"text":3348},{"id":3357,"depth":330,"text":3358},{"id":3364,"depth":330,"text":3365},"2026-10-21","Claude Code agents write the songs, Suno sings them, a GPU script times every word, and each video is a three.js scene drawn from the audio clock. The whole chain, with real data from The Steep Part.","md",{"src":3417,"alt":3418},"\u002Fimages\u002Fblog\u002Fi-cant-clap-how\u002Fcover.gif","From the video for The Steep Part: a wireframe Prius drifts across a neon highway under the caption \"Three years back my Prius barely held its lane\"",{},"---\ntitle: \"How the “I Can't Clap” Videos Get Made\"\ndescription: 'Claude Code agents write the songs, Suno sings them, a GPU script times every word, and each video is a three.js scene drawn from the audio clock. The whole chain, with real data from The Steep Part.'\ndate: '2026-10-21'\nimage:\n  src: \u002Fimages\u002Fblog\u002Fi-cant-clap-how\u002Fcover.gif\n  alt: 'From the video for The Steep Part: a wireframe Prius drifts across a neon highway under the caption \"Three years back my Prius barely held its lane\"'\nauthors:\n  - name: Chris Towles\n    to: https:\u002F\u002Ftwitter.com\u002FChris_Towles\n    avatar:\n      src: \u002Fimages\u002Fctowles-profile-512x512.png\nbadge:\n  label: 'AI'\n---\n\nPeople keep asking how the videos for my album [*I Can't Clap*](\u002Fi-cant-clap){target=\"\\_blank\"} get made. Claude Code agents write the songs, Suno sings them, a GPU script times every word, and each video is a three.js scene drawn from the audio clock. I pick the premise, leave notes on lines, choose takes, and click Create and Download. Here is the whole chain, using real data from [The Steep Part](\u002Fi-cant-clap\u002Fthe-steep-part){target=\"\\_blank\"}.\n\nIt started with my kids. After hearing a few AI-generated songs that weren't half bad, I began making spelling-word and reading songs for them while I played with [Farwonder](https:\u002F\u002Ffarwonder.app){target=\"\\_blank\"}, the app I'm building for them. The ones I post here are the grown-up side of that: humorous songs about working with AI.\n\n:::callout{icon=\"i-lucide-play\"}\n**[Watch The Steep Part →](\u002Fi-cant-clap\u002Fthe-steep-part){target=\"\\_blank\"}** It's the one this post takes apart: the lyrics, the judge rounds, the word timings and the video are all its real files.\n:::\n\n::demo-embed{src=\"\u002Fdemos\u002Fhow-i-cant-clap-gets-made\u002Findex.html\" part=\"chain\" title=\"Five stages: Write (Claude Code), Sing (Suno, I pick and download), Time (GPU, about 5 minutes), Film (Claude Code) and Ship (pages on this blog), with my notes and an early sketch take feeding back into Write\"}\n::\n\n## 1. Write: a writer agent and a judge it can't sway\n\nEach song starts as a one-line premise: an AI or IT ideal against the messy reality. One writer agent reads a shared brief, the two earlier songs, my blog posts, and a Suno style guide, brainstorms about 25 gags, keeps the best dozen, and writes the song. Then it spawns a _new_ judge agent every round. The judge scores the song against a rubric, quotes the weak lines, and says APPROVED or REVISE. A fresh judge each round means no one grades their own homework twice. Approval takes 8.5 or better with no rubric item below 7, in five rounds at most.\n\n::demo-embed{src=\"\u002Fdemos\u002Fhow-i-cant-clap-gets-made\u002Findex.html\" part=\"judge\" title=\"The Steep Part's judge scores by round: 7.1, 8.2, 8.2, then 8.6 approved, against a threshold of 8.5\"}\n::\n\nRound 1 caught rap lines running 17 to 19 syllables and flat last words. By round 4 it passed at 8.6. Then I read it in the songbook app and left 15 line notes, and the writer applied them one by one.\n\nI want songs under 3:30. An early song ran about 650 words and came back 4:07, so every draft now has to print OK before a judge sees it:\n\n```bash\n# Suno sings roughly one line per two bars\nseconds = lines × 480 \u002F BPM × k\n# k = 1.0 sung, 0.8 patter, 0.5 for rap lines\n\n$ count.py songs\u002F24-the-steep-part.md\n110 BPM lines=54 rap=32 words=436\nest=2:45 OK  (target ≤ 3:10)\n```\n\n### Then I read it, edit it, and hear it early\n\n1. **Read it in the songbook.** A small Bun app shows every song. I tap a line and mark it Love it, Cut or Change, with a note, and give the song a status: Keeper, Needs work or Drop.\n2. **Sketch it in Suno.** Before many edits, I load a rough draft into Suno just to hear the style. Does the genre land, does the hook stick, can you hear the joke? If not, I change the style or drop the song while it's still cheap.\n3. **The writer applies my notes.** Claude reads my notes file, revises the lyrics line by line, marks each note resolved and replies under it. The open page refreshes by itself.\n4. **Repeat until it's a keeper.** Read, sketch, edit again. Only a keeper gets a final take, timings and a video.\n\nMy notes, as the writer sees them:\n\n```jsonc\n\u002F\u002F feedback\u002F24-the-steep-part.json: what the songbook app saves when I tap a line\n{\n  \"status\": \"rework\", \u002F\u002F keeper | rework | drop\n  \"pin\": \"working\",\n  \"note\": \"Push verse 2 harder\",\n  \"lines\": [\n    {\n      \"line\": \"So I made my loop look busy. Boss said \\\"clever\\\"\",\n      \"kind\": \"cut\", \u002F\u002F love | cut | change\n      \"note\": \"too slow\",\n      \"resolved\": false, \u002F\u002F the writer sets true once it's fixed…\n      \"reply\": \"\", \u002F\u002F …and answers here, under my note\n    },\n  ],\n}\n```\n\nAn early sketch take. Hearing it first is the cheapest test; a clever lyric in the wrong genre is still a dud.\n\n```bash\n# a rough draft goes to Suno before the polishing rounds\n$ bun suno 24-the-steep-part --fill     # fill the form, I click Create and listen\n# does the genre land? does the hook stick? is the joke audible at this tempo?\n# if not, change the Style or the premise now, while it's still cheap\n```\n\nThe judge prompt and the log it leaves. The writer may push back on a fix that would hurt a joke, but it has to say why in the log.\n\n```text\n# research\u002Fwriter-process.md, step 4: the writer spawns this judge every round\n\"You are the judge for a comedic Suno song. Read research\u002Fbrief.md\n (the 'Judge rubric' section is your job), skim research\u002Fsuno-guide.md\n sections 1-3, and read the song at \u003Csong path>. Run count.py \u003Csong path>.\n Score each rubric item 1-10, then give a verdict: APPROVED or REVISE,\n with up to 6 prioritized, concrete fixes that quote weak lines. Be tough;\n earlier rounds' reviews are at \u003Cprior review paths>, so check whether\n those fixes landed. Write your review to reviews\u002F\u003Cslug>.r\u003Cround>.md, and\n end with 'VERDICT: APPROVED (score X)' or 'VERDICT: REVISE (score X)'.\n Do not edit the song.\"\n\n# songs\u002F24-the-steep-part.md, the Judge log the writer keeps\n- Round 1: REVISE (7.1) — V2 rap lines ran 17-19 syllables, weak last words…\n- Round 4: APPROVED (score 8.6\u002F10) — Listen-for on Suno: if V2 rushes,\n  drop the (Approved?) ad-lib first; check \"Q3\" and \"S-O-C two\" pronunciation\n```\n\n> I burst into the standup: \"Guys, the singularity's here!\"\u003Cbr>\n> Somebody said, \"Great. File a ticket. We'll talk next year.\"\n\n## 2. Sing: Suno sings it, I pick the take\n\nEach song file carries a Style prompt, an Exclude list, and lyrics with section tags like `[Verse 1: rap, male]` or `[Bridge: spoken, male, sparse]`. A script drives my own logged-in Chrome over the DevTools protocol, fills Suno's Advanced form, clicks Create, and records both clips it returns. I listen, pick the take I like, and download the MP3 myself.\n\nWhat Suno gets for The Steep Part:\n\n```text\nStyle   aggressive hardcore pop-rap anthem, 110 BPM, booming hard-hitting drums\n        and heavy 808s, dark minor piano riff, gritty distorted synth stabs,\n        intense rapid-fire snarling male rap with clear diction, soaring\n        haunting female pop hook, stadium build-ups, punchy loud mix\nExclude country, comedic voices, cheerful, autotune, guitar solo,\n        long instrumental, sung verses, lo-fi, mellow, jazz\n```\n\nExclude matters as much as Style. \"comedic voices\" keeps Suno from singing the jokes in a goofy voice, which kills them.\n\nThe script reads the form back before clicking Create, because a paste that silently fails still spends a generation:\n\n```ts\n\u002F\u002F src\u002Fsuno.ts: drive my logged-in Chrome over the DevTools protocol\nasync function fillForm(tab: Tab, song: Song) {\n  const { title, style, exclude, lyrics } = song.final;\n  await tab.eval(() => [...document.querySelectorAll('[role=\"tab\"]')]\n    .find(t => t.textContent?.trim() === \"Advanced\")?.click());\n  await tab.waitFor('[aria-label=\"Lyrics editor\"]');\n  \u002F\u002F The lyrics box is a Lexical editor; one paragraph per line is exactly what typing produces.\n  await tab.eval(lyrics => { \u002F* set the editor state, one paragraph per line *\u002F }, lyrics);\n  await tab.typeInto('textarea:not([aria-label]):not([placeholder^=\"Describe\"])', style);\n  await tab.typeInto('input[placeholder=\"Exclude styles\"]', exclude);\n  \u002F\u002F read the form back: a silent paste failure would waste a generation\n  const want = lyrics.split(\"\\n\").filter(l => l.trim()).length;\n  const got = shown.lyrics.split(\"\\n\").filter(l => l.trim()).length;\n  if (want !== got) throw new Error(`Lyrics box shows ${got} lines, expected ${want}. Check the form in Chrome.`);\n}\n```\n\n## 3. Time: find every word to the millisecond\n\nThe video has to know exactly when each word lands. faster-whisper transcribes the MP3, but under a loud mix it mishears and its stamps drift. Since we already know the lyrics, the script aligns whisper's words to the real ones. Then demucs pulls out the vocal stem and a forced aligner (torchaudio's MMS) places each line's known words on it, searching only near where whisper put the line. A last pass measures loudness, bass and the beat grid at 30 frames a second.\n\n::demo-embed{src=\"\u002Fdemos\u002Fhow-i-cant-clap-gets-made\u002Findex.html\" part=\"timing\" title=\"The first four lines of The Steep Part: each word, with what whisper heard struck through; below, loudness on top, whisper's word stamps with misheard words in red, and the final aligned stamps\"}\n::\n\nWhisper missed \"Three\" entirely and heard \"Prius\" as \"priest\" and \"gain\" as \"game\". Aligning to the known lyrics fixes the words; the forced aligner fixes the times.\n\nStep 1, align to the known lyrics:\n\n```ts\n\u002F\u002F src\u002Falign.ts: line up what whisper heard with the lyrics we already know.\n\u002F\u002F A Needleman-Wunsch alignment: exact match 3, close spelling 2, anything else -1.\nconst score = (a: string, b: string) =>\n  a === b ? 3 : a.length > 3 && lev(a, b) \u003C= Math.floor(a.length \u002F 4) ? 2 : -1;\n\nfor (let i = 1; i \u003C= n; i++)\n  for (let j = 1; j \u003C= m; j++) {\n    const d = S[i-1][j-1] + score(A[i-1], B[j-1]), u = S[i-1][j] + GAP, l = S[i][j-1] + GAP;\n    if (d >= u && d >= l) { S[i][j] = d; T[i][j] = 1; } else if (u >= l) { S[i][j] = u; T[i][j] = 2; } else { S[i][j] = l; T[i][j] = 3; }\n  }\n\u002F\u002F walk back: matched words take whisper's times; the rest (\"Three\") are\n\u002F\u002F interpolated between their matched neighbours\n```\n\nStep 2, forced alignment on the vocal stem:\n\n```python\n# scripts\u002Fforced-align.py: tighten each line on the isolated vocal\nsep = get_model(\"htdemucs\")                         # 1. demucs pulls the vocal stem\nbundle = torchaudio.pipelines.MMS_FA                # 2. CTC emissions, 20 ms frames\nfor i, line in enumerate(lines):                    # 3. per line, near whisper's guess\n    lo, hi = ws[0][\"s\"] - PAD, ws[-1][\"e\"] + PAD\n    tokens = [\"*\"] + [spell(w[\"w\"]) for w in ws] + [\"*\"]   # \"30\" -> \"three zero\"\n    spans = aligner(em[a:b], tokenizer(tokens))[1:-1]\n    score = float(np.mean([s.score for sp in spans for s in sp]))\n    if score \u003C 0.2:                                 # shouted or vocoded: keep whisper\n        kept.append(ws); continue\n    for w, sp in zip(ws, spans):\n        w[\"s\"], w[\"e\"] = (a + sp[0].start) \u002F fps, (a + sp[-1].end) \u002F fps\n# whisper runs early by a fairly constant amount, so nudge the lines we couldn't refine\n```\n\n## 4. Film: the video is a function of time\n\nNothing in the video is animated in the usual sense. Every object, light, caption and overlay is computed from `t`, the current audio time, plus the word timings and the bass and loudness at that frame. Any frame can be drawn on its own, so you can scrub, jump with `?t=12`, or have a headless browser render frame 4,512 without playing the first 4,511.\n\n::demo-embed{src=\"\u002Fdemos\u002Fhow-i-cant-clap-gets-made\u002Findex.html\" part=\"film\" title=\"A toy frame of The Steep Part at t = 15.2 s: a neon exponential curve over a pulsing grid, an 'autopilot · hands off' clock at 00:28, and the karaoke caption 'Now my Tesla drives me thirty minutes, hands off, no strain'\"}\n::\n\n:::callout{icon=\"i-lucide-volume-2\"}\nPress play for sound, or drag the slider. The toy frame is drawn from the real Steep Part data, with the real audio from the opening through \"Linear brain on an exponential plane\".\n:::\n\nOverlays are timed in words, not seconds. `L(1)` is when line 1 starts, `E(1)` when it ends, and `W(5, \u002Fthrust\u002F)` is the moment \"thrust\" is sung. Retime the song and every overlay follows.\n\n```html\n\u003C!-- video\u002F24-the-steep-part\u002Fsong.ts -->\n\u003Cdiv data-show=\"L1+0.1|L2-0.1\">\n  autopilot · hands off\n  \u003Cbig data-count=\"0,30\"\n       data-at=\"L1+0.1\" data-until=\"E1\"\n       data-fmt=\"clock\">00:00\u003C\u002Fbig>\n\u003C\u002Fdiv>\n```\n\nThe music reaches into the scene:\n\n```ts\n\u002F\u002F video\u002Ftl.ts\nbass(t)   \u002F\u002F smoothed bass energy, 0..1\nrms(t)    \u002F\u002F loudness at this frame\nbeat(t)   \u002F\u002F 1 on the beat, decays to 0\nvis(t, a, b)  \u002F\u002F fade in at a, out at b\nrand(n)   \u002F\u002F seeded, so frames repeat\n```\n\nEach song gets its own world: a neon curve for The Steep Part, a watercolor church for Read the Diff, a Broadway stage for Data Here, Data There. A shared film kit adds depth of field, bloom, grain and a letterbox.\n\nA world's shots cut on lyric lines and are lit by the bass. The camera, sky, bloom and every prop are set from `t` on each frame; nothing carries over from the frame before.\n\n```ts\n\u002F\u002F video\u002F24-the-steep-part\u002Fworld.ts: the shot list is written in lyric lines\nconst shots = () => [\n  { name: \"intro\",   at: 0,            label: \"intro\" },\n  { name: \"prius\",   at: L(0) - 0.08,  label: \"the prius\" },\n  { name: \"tesla\",   at: L(1) - 0.08,  label: \"hands off\" },\n  { name: \"engine\",  at: L(4) - 0.08,  label: \"one shot\" },\n  { name: \"agents\",  at: L(10) - 0.08, label: \"forty agents\" },\n  { name: \"standup\", at: L(14) - 0.08, label: \"the standup\" },\n  \u002F\u002F …\n];\n\nupdate(t: number) {\n  const shot = shotAt(t), b = bass(t), bt = beat(t);\n  this.bloom.strength = 0.85 + b * 0.55;           \u002F\u002F the bass blooms the lights\n  this.sky.material.uniforms.pulse.value = b;\n  switch (shot.name) {\n    case \"prius\": case \"tesla\": this.roadShot(t, shot, look, b); break;\n    case \"engine\": this.engineShot(t, shot.at, look, b); break;\n    case \"triple\":\n      \u002F\u002F \"Terrified. Thrilled. Tentative.\": one cut per word\n      if (t \u003C W(51, \u002F^thrilled\u002Fi) - 0.05) this.curveShot(t, shot, look, b, bt);\n      else if (t \u003C W(51, \u002F^tentative\u002Fi) - 0.05) this.engineShot(t, W(51, \u002F^thrilled\u002Fi) - 2.5, look, b);\n      else this.roomShot(t, shot, look, bt);\n  }\n}\n```\n\n## 5. Ship: check the stills, then build the pages\n\n1. **Render stills.** Playwright and headless Chrome call `__renderAt(t)` once per shot and tile the frames into a contact sheet.\n2. **Flag problems.** The script flags blown-out, too-dark and repeated frames. Claude looks at the sheet for clipped text and overlays covering the scene.\n3. **Build.** `bun catalog` writes the album page and one page per video into this blog: the song's timings inlined, its MP3 and poster beside it, and one shared player bundle for every video.\n4. **Publish.** The pages ship with the blog. A hash of the code, timings and audio says when a YouTube upload is out of date.\n\n```ts\n\u002F\u002F src\u002Fstills.ts: look at a video without watching it\nawait page.goto(`http:\u002F\u002F127.0.0.1:4321\u002Fvideo\u002F${slug}\u002F${clipId}?render`);\nfor (const [i, p] of picks.entries()) {           \u002F\u002F one pick from the middle of every shot\n  await page.evaluate(t => window.__renderAt(t), p.t);\n  const png = await page.screenshot();\n  \u002F\u002F measure luminance: >0.97 counts as clipped, \u003C0.02 as crushed,\n  \u002F\u002F and a tiny diff from the previous frame flags a shot that didn't change\n}\n```\n\n```ts\n\u002F\u002F src\u002Fcatalog.ts: one page per video, sharing one player\nfor (const c of cards) {\n  const data = videoData(c.slug, c.clipId, `${BASE}\u002Faudio\u002F${c.name}.mp3`);\n  copyFileSync(c.audio, join(siteDir, \"audio\", `${c.name}.mp3`));\n  writeFileSync(join(siteDir, `${c.name}.html`), page(`\u003Ctitle>${c.title} · I Can't Clap\u003C\u002Ftitle>\n${fonts}\n\u003Clink rel=\"stylesheet\" href=\"${BASE}\u002Fplayer.css\">\n${analytics}`, `${stage}\n\u003Cscript>window.__SONG__ = ${JSON.stringify(data)};\u003C\u002Fscript>\n\u003Cscript type=\"module\" src=\"${BASE}\u002Fplayer.js\">\u003C\u002Fscript>`));\n}\n```\n\n### Why they live here\n\nEvery video, and the first version of this post, started as a Claude artifact: one self-contained HTML file and a link I could share. But an artifact gives you no metrics. I couldn't tell how many people opened one, or whether anyone watched past the first chorus.\n\nSo they live on this blog now. The pages count visits and send a few events: when a video starts, when it reaches 25, 50 and 75%, and when it finishes. Same player, just hosted where I can see whether anyone's listening.\n\n## The videos\n\n::album-videos\n::\n\n## By the numbers\n\n- **26** songs written\n- **113** judge reviews\n- **5** videos published\n- **5,458** lines of three.js across the five worlds\n\nBuilt with Claude Code and Opus 5.5, Suno, Bun and TypeScript, faster-whisper, demucs, the torchaudio MMS aligner, three.js, Playwright and uv. The timings, loudness and judge scores in this post are the real files from The Steep Part.\n",{"title":221,"description":3414},"published","JQUjt-cTLwC8oxTNB2HpseAoTfo9-tDpYtuz3itrJk4",[3425,3427],{"title":217,"path":218,"stem":219,"description":3426,"status":3422,"children":-1},"I asked Claude to recreate a jet engine demo from a video clip. A day later, all from my phone, I had a true-scale GEnx-1B with a cycle model, a borescope and a digital twin. Opus 5.5 is good enough and cheap enough to just keep running.",null,1792641967802]