tuneframes 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +166 -166
  2. package/README.md +158 -55
  3. package/examples/example-ai-dj-chill.html +167 -0
  4. package/examples/example-ai-dj-dark.html +167 -0
  5. package/examples/example-ai-dj-energetic.html +167 -0
  6. package/examples/example-ai-dj-happy.html +167 -0
  7. package/examples/example-ai-dj.html +167 -0
  8. package/examples/example-ambient.html +63 -63
  9. package/examples/example-ambient.mp3 +0 -0
  10. package/examples/example-bass.html +47 -47
  11. package/examples/example-lofi.html +45 -45
  12. package/examples/example-lofi.mp3 +0 -0
  13. package/examples/example-minimal.html +20 -20
  14. package/examples/example-orchestral.html +67 -67
  15. package/examples/example-orchestral.mp3 +0 -0
  16. package/examples/example-piano.html +52 -52
  17. package/examples/example-piano.mp3 +0 -0
  18. package/examples/example-techno.html +69 -69
  19. package/examples/example-techno.mp3 +0 -0
  20. package/package.json +42 -37
  21. package/registry/presets/bass-electric.html +67 -0
  22. package/registry/presets/bass-saw.html +22 -0
  23. package/registry/presets/chord-progression.html +22 -0
  24. package/registry/presets/drums-808.html +85 -0
  25. package/registry/presets/drums-lofi.html +35 -0
  26. package/registry/presets/lead-piano.html +26 -0
  27. package/registry/presets/piano-salamander.html +69 -0
  28. package/registry/presets/reverb-warm.html +11 -0
  29. package/registry/samples.json +226 -0
  30. package/skills/audio-ambient/SKILL.md +141 -0
  31. package/skills/audio-ambient/example.html +113 -0
  32. package/skills/audio-boss-battle/SKILL.md +157 -0
  33. package/skills/audio-boss-battle/example.html +185 -0
  34. package/skills/audio-boss-battle/example.mp3 +0 -0
  35. package/skills/audio-chillwave/SKILL.md +142 -0
  36. package/skills/audio-chillwave/example.html +144 -0
  37. package/skills/audio-cinematic/SKILL.md +147 -0
  38. package/skills/audio-cinematic/example.html +123 -0
  39. package/skills/audio-classical/SKILL.md +138 -0
  40. package/skills/audio-classical/example.html +145 -0
  41. package/skills/audio-dnb/SKILL.md +142 -0
  42. package/skills/audio-dnb/example.html +124 -0
  43. package/skills/audio-downtempo/SKILL.md +152 -0
  44. package/skills/audio-downtempo/example.html +164 -0
  45. package/skills/audio-folk/SKILL.md +139 -0
  46. package/skills/audio-folk/example.html +132 -0
  47. package/skills/audio-funk/SKILL.md +149 -0
  48. package/skills/audio-funk/example.html +144 -0
  49. package/skills/audio-future-bass/SKILL.md +163 -0
  50. package/skills/audio-future-bass/example.html +164 -0
  51. package/skills/audio-hip-hop/SKILL.md +133 -0
  52. package/skills/audio-hip-hop/example.html +129 -0
  53. package/skills/audio-house/SKILL.md +147 -0
  54. package/skills/audio-house/example.html +128 -0
  55. package/skills/audio-indie-pop/SKILL.md +150 -0
  56. package/skills/audio-indie-pop/example.html +121 -0
  57. package/skills/audio-jazz/SKILL.md +141 -0
  58. package/skills/audio-jazz/example.html +146 -0
  59. package/skills/audio-lofi/SKILL.md +140 -0
  60. package/skills/audio-lofi/example.html +135 -0
  61. package/skills/audio-minimal/SKILL.md +155 -0
  62. package/skills/audio-minimal/example.html +118 -0
  63. package/skills/audio-orchestral/SKILL.md +156 -0
  64. package/skills/audio-orchestral/example.html +140 -0
  65. package/skills/audio-r-and-b/SKILL.md +134 -0
  66. package/skills/audio-r-and-b/example.html +154 -0
  67. package/skills/audio-r-and-b/example.mp3 +0 -0
  68. package/skills/audio-techno/SKILL.md +140 -0
  69. package/skills/audio-techno/example.html +125 -0
  70. package/skills/audio-trap/SKILL.md +123 -0
  71. package/skills/audio-trap/example.html +119 -0
  72. package/skills/render-retest/example.html +115 -0
  73. package/skills/tuneframes/SKILL.md +221 -0
  74. package/skills/tuneframes-cli/SKILL.md +46 -0
  75. package/skills/verify/example.html +104 -0
  76. package/src/cli.js +260 -151
  77. package/src/render.js +134 -7
  78. package/examples/example-bass.mp3 +0 -0
  79. package/examples/example-demo-beat.wav +0 -0
  80. package/examples/example-orchestral.wav +0 -0
@@ -0,0 +1,221 @@
1
+ ---
2
+ name: tuneframes
3
+ description: Write music compositions in HTML using Tone.js. Use when asked to create music, audio, sound effects, beats, or any audio generation task.
4
+ ---
5
+
6
+ # TuneFrames
7
+
8
+ Open-source music generation: write HTML with Tone.js, render to MP3 with one CLI command. Built for AI agents — no native audio code, no per-render fees, fully deterministic.
9
+
10
+ ## When to Use This Skill
11
+
12
+ - User asks for music, a beat, soundtrack, sound effects, or audio
13
+ - User describes a musical mood (lofi, ambient, techno, orchestral) and wants output
14
+ - User wants audio for a video, game, or application
15
+ - User wants to generate music programmatically in an automated pipeline
16
+
17
+ ## Core Authoring Pattern
18
+
19
+ Every TuneFrames composition is a single HTML file:
20
+
21
+ ```html
22
+ <!DOCTYPE html>
23
+ <html>
24
+ <head>
25
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
26
+ </head>
27
+ <body>
28
+ <div id="tuneframes" style="display:none">{"bpm":120,"duration":"4s"}</div>
29
+ <script>
30
+ async function main() {
31
+ await Tone.start();
32
+ const synth = new Tone.Synth().toDestination();
33
+ synth.triggerAttackRelease('C4', '4n', 0);
34
+ }
35
+ </script>
36
+ </body>
37
+ </html>
38
+ ```
39
+
40
+ ## Metadata Block
41
+
42
+ ```html
43
+ <div id="tuneframes" style="display:none">{"bpm":120,"duration":"4s"}</div>
44
+ ```
45
+
46
+ - **bpm**: beats per minute (default: 120)
47
+ - **duration**: render length in **seconds** — use `"4s"`, `"10s"`, etc. Do NOT use Tone.js note values like `"4n"` for duration (see warning below)
48
+
49
+ ## The `main()` Function Rules
50
+
51
+ 1. Must be `async function main()`
52
+ 2. Must start with `await Tone.start()`
53
+ 3. All notes are scheduled relative to transport time 0
54
+ 4. Tone.Offline renders deterministically — same HTML = identical MP3 every time
55
+
56
+ ## Instruments
57
+
58
+ ```js
59
+ // Basic tone generation
60
+ const synth = new Tone.Synth().toDestination();
61
+ const mono = new Tone.MonoSynth().toDestination();
62
+ const poly = new Tone.PolySynth(Tone.Synth).toDestination();
63
+
64
+ // Drums
65
+ const kick = new Tone.MembraneSynth().toDestination(); // Kick drum
66
+ const noise = new Tone.NoiseSynth().toDestination(); // Hi-hats, snares
67
+
68
+ // Effects
69
+ const reverb = new Tone.Reverb({ decay: 2.5, wet: 0.3 }).toDestination();
70
+ const delay = new Tone.FeedbackDelay('8n', 0.4).toDestination();
71
+ const comp = new Tone.Compressor(-12, 2).toDestination();
72
+ const chorus = new Tone.Chorus(4, 2.5, 0.5).toDestination();
73
+ ```
74
+
75
+ ## Common Patterns
76
+
77
+ **Note timing:**
78
+ ```js
79
+ const beat = Tone.Time('4n').toSeconds();
80
+ synth.triggerAttackRelease('C4', '4n', 0); // Beat 1
81
+ synth.triggerAttackRelease('E4', '4n', beat); // Beat 2
82
+ synth.triggerAttackRelease('G4', '4n', beat * 2); // Beat 3
83
+ synth.triggerAttackRelease('C5', '4n', beat * 3); // Beat 4
84
+ ```
85
+
86
+ **Chord progression:**
87
+ ```js
88
+ const pad = new Tone.PolySynth(Tone.Synth).toDestination();
89
+ const chords = [
90
+ ['D3','F3','A3'], // Dm
91
+ ['C3','E3','G3'], // C
92
+ ['Bb2','D3','F3'], // Bb
93
+ ['A2','C3','E3'], // Am
94
+ ];
95
+ const measure = Tone.Time('1n').toSeconds();
96
+ chords.forEach((chord, i) => {
97
+ pad.triggerAttackRelease(chord, '1n', i * measure);
98
+ });
99
+ ```
100
+
101
+ **Drum pattern:**
102
+ ```js
103
+ const kick = new Tone.MembraneSynth().toDestination();
104
+ const snare = new Tone.NoiseSynth().toDestination();
105
+ const beat = Tone.Time('4n').toSeconds();
106
+
107
+ for (let i = 0; i < 4; i++) {
108
+ kick.triggerAttackRelease('C1', '4n', i * beat); // Kick on 1-4
109
+ snare.triggerAttackRelease('C3', '4n', (i + 0.5) * beat); // Snare on 2,4
110
+ }
111
+ ```
112
+
113
+ **Lofi beat (full example):**
114
+ ```js
115
+ const kick = new Tone.MembraneSynth().toDestination();
116
+ const snare = new Tone.NoiseSynth().toDestination();
117
+ const hihat = new Tone.NoiseSynth().toDestination();
118
+ const piano = new Tone.PolySynth(Tone.Synth).toDestination();
119
+ const reverb = new Tone.Reverb({ decay: 1.5, wet: 0.4 }).toDestination();
120
+
121
+ const beat = Tone.Time('4n').toSeconds();
122
+ const bar = beat * 4;
123
+
124
+ // Kick: four on the floor
125
+ for (let i = 0; i < 4; i++) kick.triggerAttackRelease('C1', '4n', i * beat);
126
+
127
+ // Snare: 2 and 4
128
+ [1, 3].forEach(i => snare.triggerAttackRelease('C3', '4n', i * beat));
129
+
130
+ // Hi-hat: eighth notes, quiet
131
+ hihat.volume.value = -12;
132
+ for (let i = 0; i < 8; i++) hihat.triggerAttackRelease('16n', i * beat * 0.5);
133
+
134
+ // Piano: chord progression
135
+ const progression = [['D3','F3','A3'],['C3','E3','G3']];
136
+ progression.forEach((chord, i) => {
137
+ piano.connect(reverb);
138
+ piano.triggerAttackRelease(chord, '2n', i * bar);
139
+ });
140
+ ```
141
+
142
+ ## ⚠️ Duration Warning
143
+
144
+ **Use literal seconds (`"4s"`, `"10s"`) for the metadata duration.**
145
+
146
+ Tone.js note notation (`4n`, `2n`, `1n`) is a fraction of a whole note at the given BPM:
147
+ - `1n` = 1 whole note = 4 beats → at 120 BPM, 1n = 2.0s
148
+ - `2n` = 1/2 whole note = 2 beats → at 120 BPM, 2n = 1.0s
149
+ - `4n` = 1/4 whole note = 1 beat → at 120 BPM, 4n = 0.5s
150
+ - `8n` = 1/8 whole note = 1/2 beat → at 120 BPM, 8n = 0.25s
151
+
152
+ This is a common source of clipped renders. Always use seconds in the metadata block.
153
+
154
+ ## CLI Reference
155
+
156
+ ```bash
157
+ tuneframes render <file.html> [--output <out.mp3>] # Render to MP3
158
+ tuneframes render <file.html> --format wav [--output <out.wav>]
159
+ tuneframes preview <file.html> # Live preview in browser
160
+ tuneframes init <name> # Scaffold new project
161
+ ```
162
+
163
+ ---
164
+
165
+ # TuneFrames — Sample Instruments
166
+
167
+ Extends the core TuneFrames skill with real instrument samples via CDN.
168
+
169
+ ## Sample Pattern (Tone.Sampler + gleitz CDN)
170
+
171
+ Every sample composition must:
172
+ 1. Create Tone.Sampler instances with CDN baseUrl
173
+ 2. Call `await Tone.loaded()` before scheduling any notes
174
+ 3. Then schedule all notes using the loaded sampler
175
+
176
+ ```js
177
+ async function main() {
178
+ await Tone.start();
179
+
180
+ // Load piano from CDN
181
+ const piano = new Tone.Sampler({
182
+ urls: { A4: 'A4.mp3', C4: 'C4.mp3', 'F#4': 'Fs4.mp3', A5: 'A5.mp3' },
183
+ baseUrl: 'https://gleitz.github.io/midi-js-soundfonts/FluidR3_GM/acoustic_grand_piano-mp3/'
184
+ }).toDestination();
185
+
186
+ // CRITICAL: wait for samples to load before scheduling
187
+ await Tone.loaded();
188
+
189
+ // Now schedule notes
190
+ piano.triggerAttackRelease('C4', '4n', 0);
191
+ piano.triggerAttackRelease('E4', '4n', '4n');
192
+ piano.triggerAttackRelease('G4', '2n', '2n');
193
+ }
194
+ ```
195
+
196
+ ## Available Instruments (gleitz FluidR3_GM)
197
+
198
+ Full registry: `registry/samples.json` — includes all URL mappings and per-instrument notes.
199
+
200
+ | Key | CDN path suffix | Category | Best range |
201
+ |-----|----------------|----------|------------|
202
+ | `acoustic_grand_piano` | `acoustic_grand_piano-mp3` | Piano | A0–C8 |
203
+ | `acoustic_bass` | `acoustic_bass-mp3` | Bass | C1–C4 |
204
+ | `string_ensemble_1` | `string_ensemble_1-mp3` | Strings | C2–C7 |
205
+ | `brass_section` | `brass_section-mp3` | Brass | C2–C6 |
206
+ | `acoustic_guitar_nylon` | `acoustic_guitar_nylon-mp3` | Guitar | Fs2–C6 |
207
+ | `vibraphone` | `vibraphone-mp3` | Mallet | C3–C7 |
208
+ | `flute` | `flute-mp3` | Woodwind | C4–A7 |
209
+ | `choir_aahs` | `choir_aahs-mp3` | Choir | C3–G5 |
210
+
211
+ CDN base: `https://gleitz.github.io/midi-js-soundfonts/FluidR3_GM/{instrument}-mp3/`
212
+
213
+ Sharp notes use `s` suffix in filenames: F#4 → `Fs4.mp3`, D#3 → `Ds3.mp3`.
214
+
215
+ ## Presets
216
+
217
+ Ready-to-render compositions in `registry/presets/`:
218
+
219
+ - `piano-salamander.html` — Am–F–C–G chord progression, acoustic grand piano, 8s
220
+ - `drums-808.html` — Trap 808 pattern, MembraneSynth + MetalSynth (no CDN), 7s
221
+ - `bass-electric.html` — Walking bass line in C minor, acoustic bass, 6s
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: tuneframes-cli
3
+ description: CLI commands for TuneFrames — render, preview, init, validate. Load this skill when working with the tuneframes CLI.
4
+ ---
5
+
6
+ # TuneFrames CLI
7
+
8
+ ## Commands
9
+
10
+ ```bash
11
+ # Render composition to MP3
12
+ tuneframes render <file.html> [--output <out.mp3>]
13
+
14
+ # Render to WAV (no re-encoding)
15
+ tuneframes render <file.html> --format wav [--output <out.wav>]
16
+
17
+ # Preview in browser (live reload)
18
+ tuneframes preview <file.html>
19
+
20
+ # Scaffold a new project
21
+ tuneframes init my-track
22
+
23
+ # Validate a composition (headless render test)
24
+ tuneframes validate <file.html>
25
+ ```
26
+
27
+ ## Rendering Pipeline
28
+
29
+ ```
30
+ HTML + Tone.js → Chromium (headless, Tone.Offline)
31
+ → WAV (PCM 44.1kHz mono)
32
+ → FFmpeg
33
+ → MP3 192kbps
34
+ ```
35
+
36
+ Tone.Offline renders without audio hardware. Same input HTML = identical output MP3. This is what makes TuneFrames safe for automated pipelines.
37
+
38
+ ## Requirements
39
+
40
+ - Node.js >= 18
41
+ - FFmpeg (install via: `apt install ffmpeg` or `brew install ffmpeg`)
42
+
43
+ ## Output Locations
44
+
45
+ Default: input file's directory with same basename, `.mp3` extension.
46
+ `--output` flag overrides: `tuneframes render in.html --output /tmp/out.mp3`
@@ -0,0 +1,104 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>TuneFrames — Verify</title>
6
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
7
+ </head>
8
+ <body>
9
+ <div id="tuneframes" style="display:none">{"bpm":120,"duration":"10s"}</div>
10
+ <script>
11
+ // Verify — upbeat electronic, C major, I-vi-IV-V-I arc. "System checked out: PASS."
12
+ //
13
+ // Instrument choices follow confirmed-working pattern (techno/lofi examples):
14
+ // - Tone.Reverb without await — generate() resolves before startRendering() inside Offline
15
+ // - Tone.MembraneSynth kick — confirmed working in example-techno.html
16
+ // - Tone.NoiseSynth hi-hat — confirmed working; MetalSynth fails inside Tone.Offline
17
+ // (MetalSynth's FM oscillators use sustain:0 early-stop scheduling which creates
18
+ // a state-timeline ordering conflict on re-trigger in the Offline context)
19
+ // - main() NOT called from HTML — render.js calls it inside Tone.Offline()
20
+
21
+ async function main() {
22
+ await Tone.start();
23
+
24
+ const beat = Tone.Time('4n').toSeconds(); // 0.5s at 120 BPM
25
+ const bar = beat * 4; // 2.0s
26
+
27
+ // Effects
28
+ const reverb = new Tone.Reverb({ decay: 2.2, wet: 0.28 }).toDestination();
29
+ const delay = new Tone.FeedbackDelay({ delayTime: beat, feedback: 0.28, wet: 0.22 }).toDestination();
30
+
31
+ // Chord pad — PolySynth(Synth) → reverb
32
+ const pad = new Tone.PolySynth(Tone.Synth, {
33
+ oscillator: { type: 'triangle' },
34
+ envelope: { attack: 0.08, decay: 0.40, sustain: 0.60, release: 1.20 }
35
+ }).connect(reverb);
36
+ pad.volume.value = -10;
37
+
38
+ // Melody — Synth → delay
39
+ const mel = new Tone.Synth({
40
+ oscillator: { type: 'sawtooth' },
41
+ envelope: { attack: 0.01, decay: 0.15, sustain: 0.40, release: 0.30 }
42
+ }).connect(delay);
43
+ mel.volume.value = -9;
44
+
45
+ // Kick — MembraneSynth
46
+ const kick = new Tone.MembraneSynth({
47
+ pitchDecay: 0.05,
48
+ octaves: 6,
49
+ oscillator: { type: 'sine' },
50
+ envelope: { attack: 0.001, decay: 0.28, sustain: 0, release: 0.08 }
51
+ }).toDestination();
52
+ kick.volume.value = -5;
53
+
54
+ // Hi-hat — NoiseSynth (white noise burst, similar timbre to metallic hat)
55
+ // NoiseSynth works inside Tone.Offline; MetalSynth does not in v14.8.49
56
+ const hihat = new Tone.NoiseSynth({
57
+ noise: { type: 'white' },
58
+ envelope: { attack: 0.001, decay: 0.05, sustain: 0, release: 0.01 }
59
+ }).toDestination();
60
+ hihat.volume.value = -16;
61
+
62
+ // Kick: 4-on-the-floor, 20 hits across 10s
63
+ for (var i = 0; i < 20; i++) {
64
+ kick.triggerAttackRelease('C1', '8n', i * beat);
65
+ }
66
+
67
+ // Hi-hat: off-beat 8th notes, 19 hits
68
+ for (var j = 0; j < 19; j++) {
69
+ var vel = j % 2 === 0 ? 0.60 : 0.32;
70
+ hihat.triggerAttackRelease('16n', beat * 0.5 + j * beat, vel);
71
+ }
72
+
73
+ // Chords: I-vi-IV-V-I (C-Am-F-G-C), one per bar
74
+ [
75
+ { notes: ['C4', 'E4', 'G4'], t: bar * 0 },
76
+ { notes: ['A3', 'C4', 'E4'], t: bar * 1 },
77
+ { notes: ['F3', 'A3', 'C4'], t: bar * 2 },
78
+ { notes: ['G3', 'B3', 'D4'], t: bar * 3 },
79
+ { notes: ['C4', 'E4', 'G4'], t: bar * 4 },
80
+ ].forEach(function(c) {
81
+ pad.triggerAttackRelease(c.notes, '1n', c.t);
82
+ });
83
+
84
+ // Melody: question phrase (bars 2-3), answer resolves to tonic (bars 4-5)
85
+ var e = beat * 0.5; // eighth-note duration
86
+ [
87
+ [bar + beat * 0, 'E5', e],
88
+ [bar + beat * 1, 'G5', e],
89
+ [bar + beat * 2, 'A5', beat],
90
+ [bar * 2 + beat * 0, 'G5', e],
91
+ [bar * 2 + beat * 1, 'F5', e],
92
+ [bar * 2 + beat * 2, 'E5', beat],
93
+ [bar * 3 + beat * 0, 'D5', e],
94
+ [bar * 3 + beat * 1, 'E5', e],
95
+ [bar * 3 + beat * 2, 'F5', e],
96
+ [bar * 3 + beat * 3, 'G5', e],
97
+ [bar * 4, 'C5', beat * 3],
98
+ ].forEach(function(n) {
99
+ mel.triggerAttackRelease(n[1], n[2], n[0]);
100
+ });
101
+ }
102
+ </script>
103
+ </body>
104
+ </html>