npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tuneframes

v0.3.0

Published

Agent-native music generation. Write Tone.js, render to audio.

Downloads

312

Readme

TuneFrames

Write music in HTML. Render to MP3 in one command.

Built for AI agents — Claude writes a composition, TuneFrames renders it.

npm GitHub stars Discord

Demos: Chrome — Synthwave 118 BPM · Titan — Dark Cinematic 72 BPM · Velvet — Neo-Soul 88 BPM


Quickstart

npm install -g tuneframes
tuneframes init my-track
tuneframes render my-track/composition.html --output track.mp3

For AI Agents

The HTML file is the source code. An agent writes it. TuneFrames renders it. No API, no credits, no black box.

Claude writes composition.html  →  tuneframes render  →  track.mp3

Every render is deterministic — the same HTML produces the same MP3, byte for byte. Fork a composition, diff it, remix it with another agent, ship it to CI. Music is code now.

  • No per-render fees. No streaming API. Fully local.
  • Version-controlled compositions. Branch, merge, and iterate the same way you iterate code.
  • Works everywhere. Claude, GPT-4, Cursor, Copilot — any tool that writes code writes music.

MCP Setup

Give Claude native music abilities with one config change:

// ~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "tuneframes": {
      "command": "npx",
      "args": ["tuneframes-mcp"]
    }
  }
}

After restarting Claude Desktop, Claude gains a render_music() tool it calls directly. Try: "Write me a jazz trio at 120 BPM."


How It Works

An HTML file with Tone.js code goes into a headless Chromium instance. Tone.Offline() renders the full composition to an AudioBuffer with sample-accurate timing — no audio hardware, no real-time playback. That buffer converts to WAV via audioBufferToWav(), then FFmpeg encodes it to MP3. The result is identical on every machine, every run.

HTML + Tone.js  →  Chromium (headless)  →  Tone.Offline()  →  WAV  →  FFmpeg  →  MP3

Composition Format

Every composition is a self-contained HTML file with three required pieces: a metadata block, a Tone.js script tag, and an async function main().

<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/[email protected]/build/Tone.js"></script>
</head>
<body>
  <div id="tuneframes" style="display:none">{"bpm":120,"duration":"16s"}</div>
  <script>
    async function main() {
      await Tone.start();

      const pad = new Tone.PolySynth(Tone.Synth, {
        oscillator: { type: 'triangle' },
        envelope: { attack: 0.3, decay: 0.1, sustain: 0.7, release: 1.5 }
      }).toDestination();

      // Dm9 – Bbmaj7 – Fmaj7 – C7sus4  (2s per chord at 120 BPM)
      pad.triggerAttackRelease(['D3','F3','A3','C4','E4'], '1.9s', 0);
      pad.triggerAttackRelease(['Bb2','D3','F3','A3'],     '1.9s', 2);
      pad.triggerAttackRelease(['F2','A2','C3','E3'],      '1.9s', 4);
      pad.triggerAttackRelease(['C3','F3','G3','Bb3'],     '1.9s', 6);
      pad.triggerAttackRelease(['D3','F3','A3','C4','E4'], '1.9s', 8);
      pad.triggerAttackRelease(['Bb2','D3','F3','A3'],     '1.9s', 10);
      pad.triggerAttackRelease(['F2','A2','C3','E3'],      '1.9s', 12);
      pad.triggerAttackRelease(['C3','F3','G3','Bb3'],     '1.9s', 14);
    }
  </script>
</body>
</html>

Rules:

  • <div id="tuneframes"> with valid JSON is required — the renderer reads BPM and duration from it
  • duration is literal seconds only"16s" not "16n" (see Gotchas)
  • async function main() must start with await Tone.start()
  • Schedule all notes at absolute times in seconds (or convert with Tone.Time('2n').toSeconds())

Skill Gallery

Install the full skill pack with: npx skills add shepherd217/tuneframes

Each skill ships with a BPM range, characteristic chord progressions, drum patterns, and a verified example composition.

| Genre | BPM Range | |-------|-----------| | ambient | 50–70 | | boss-battle | 150–185 | | chillwave | 90–110 | | cinematic | 60–90 | | classical | 80–140 | | dnb | 165–180 | | downtempo | 70–90 | | folk | 80–110 | | funk | 95–115 | | future-bass | 140–160 | | hip-hop | 80–95 | | house | 120–128 | | indie-pop | 115–140 | | jazz | 60–240 | | lofi | 70–90 | | minimal | 128–135 | | orchestral | 60–120 | | r-and-b | 70–95 | | techno | 130–145 | | trap | 130–160 |


Critical Gotchas

Four things that silently break agent-generated compositions:

1. Duration is literal seconds, not note values. "duration":"4n" in the metadata block is not 4 beats. In Tone.js, 4n means "quarter note fraction" — at 120 BPM that's 0.5 seconds. Always write "duration":"16s".

2. Reverb, Freeverb, BitCrusher, and Chebyshev are stubbed. These effects use AudioWorklet, which fails in headless Chromium. The renderer replaces them with passthrough Gain nodes automatically — sound plays through dry, wet effect is silently dropped. Use FeedbackDelay or Chorus instead.

3. Schedule notes chronologically per instrument. Tone.js's StateTimeline requires that triggerAttackRelease calls on the same instrument are in non-decreasing time order. Scheduling t=1.5 after t=2.0 on the same synth throws:

Error: The time must be greater than or equal to the last scheduled time

Collect all hit times in an array, sort ascending, then schedule.

4. CDN samples require the TUNEFRAMES_READY pattern. The renderer awaits window.TUNEFRAMES_READY before calling Tone.Offline(). When using Tone.Sampler with CDN URLs, set this to a Promise that resolves once samples are buffered:

window.TUNEFRAMES_READY = (async () => {
  // pre-fetch and decode samples into window._myBuffers
  // then wrap with new Tone.ToneAudioBuffer(audioBuffer) inside Offline
})();

Do not use await Tone.loaded() inside Tone.Offline() — it checks a list that may be empty at call time, causing a race condition that silently drops samples.


CLI Reference

tuneframes render <file.html>    Render composition to MP3
tuneframes init <name>           Initialize a new project with a lofi starter composition
tuneframes preview <file.html>   Open in browser for live preview
tuneframes validate <file.html>  Headless test render — confirms audio output > 5 KB
tuneframes lint <file.html>      Static HTML analysis — no render needed
tuneframes instruments           List all 128 GM instruments available via the gleitz CDN
tuneframes install <pack>        Show setup guide for drum/piano/bass sample packs

render options:

tuneframes render track.html --output track.mp3
tuneframes render track.html --output track.wav --format wav
tuneframes render track.html --timeout 120   # seconds (default: 60)

Requirements

  • Node.js 18+
  • FFmpeg — brew install ffmpeg or apt install ffmpeg

Playwright Chromium is bundled — no separate browser install needed.


Contributing

To add a new genre skill:

  1. Create skills/audio-<genre>/SKILL.md with BPM range, characteristic progressions, and instrument config
  2. Create skills/audio-<genre>/example.html — a complete, renderable composition
  3. Validate it: tuneframes validate skills/audio-<genre>/example.html
  4. Submit a PR

The example.html must pass validate (render to > 5 KB) before the PR can merge. That's the only gate.


See Also

  • Hyperframes — the video counterpart: write HTML with GSAP/Three.js, render to MP4

License

Apache 2.0