tuneframes 0.1.0 → 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 (78) hide show
  1. package/LICENSE +167 -199
  2. package/README.md +150 -97
  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 +48 -0
  11. package/examples/example-lofi.html +45 -45
  12. package/examples/example-lofi.mp3 +0 -0
  13. package/examples/example-minimal.html +21 -19
  14. package/examples/example-minimal.mp3 +0 -0
  15. package/examples/example-orchestral.html +67 -67
  16. package/examples/example-orchestral.mp3 +0 -0
  17. package/examples/example-piano.html +53 -0
  18. package/examples/example-piano.mp3 +0 -0
  19. package/examples/example-techno.html +69 -69
  20. package/examples/example-techno.mp3 +0 -0
  21. package/package.json +42 -24
  22. package/registry/presets/bass-electric.html +67 -0
  23. package/registry/presets/bass-saw.html +22 -0
  24. package/registry/presets/chord-progression.html +22 -0
  25. package/registry/presets/drums-808.html +85 -0
  26. package/registry/presets/drums-lofi.html +35 -0
  27. package/registry/presets/lead-piano.html +26 -0
  28. package/registry/presets/piano-salamander.html +69 -0
  29. package/registry/presets/reverb-warm.html +11 -0
  30. package/registry/samples.json +226 -0
  31. package/skills/audio-ambient/SKILL.md +141 -0
  32. package/skills/audio-ambient/example.html +113 -0
  33. package/skills/audio-boss-battle/SKILL.md +157 -0
  34. package/skills/audio-boss-battle/example.html +185 -0
  35. package/skills/audio-boss-battle/example.mp3 +0 -0
  36. package/skills/audio-chillwave/SKILL.md +142 -0
  37. package/skills/audio-chillwave/example.html +144 -0
  38. package/skills/audio-cinematic/SKILL.md +147 -0
  39. package/skills/audio-cinematic/example.html +123 -0
  40. package/skills/audio-classical/SKILL.md +138 -0
  41. package/skills/audio-classical/example.html +145 -0
  42. package/skills/audio-dnb/SKILL.md +142 -0
  43. package/skills/audio-dnb/example.html +124 -0
  44. package/skills/audio-downtempo/SKILL.md +152 -0
  45. package/skills/audio-downtempo/example.html +164 -0
  46. package/skills/audio-folk/SKILL.md +139 -0
  47. package/skills/audio-folk/example.html +132 -0
  48. package/skills/audio-funk/SKILL.md +149 -0
  49. package/skills/audio-funk/example.html +144 -0
  50. package/skills/audio-future-bass/SKILL.md +163 -0
  51. package/skills/audio-future-bass/example.html +164 -0
  52. package/skills/audio-hip-hop/SKILL.md +133 -0
  53. package/skills/audio-hip-hop/example.html +129 -0
  54. package/skills/audio-house/SKILL.md +147 -0
  55. package/skills/audio-house/example.html +128 -0
  56. package/skills/audio-indie-pop/SKILL.md +150 -0
  57. package/skills/audio-indie-pop/example.html +121 -0
  58. package/skills/audio-jazz/SKILL.md +141 -0
  59. package/skills/audio-jazz/example.html +146 -0
  60. package/skills/audio-lofi/SKILL.md +140 -0
  61. package/skills/audio-lofi/example.html +135 -0
  62. package/skills/audio-minimal/SKILL.md +155 -0
  63. package/skills/audio-minimal/example.html +118 -0
  64. package/skills/audio-orchestral/SKILL.md +156 -0
  65. package/skills/audio-orchestral/example.html +140 -0
  66. package/skills/audio-r-and-b/SKILL.md +134 -0
  67. package/skills/audio-r-and-b/example.html +154 -0
  68. package/skills/audio-r-and-b/example.mp3 +0 -0
  69. package/skills/audio-techno/SKILL.md +140 -0
  70. package/skills/audio-techno/example.html +125 -0
  71. package/skills/audio-trap/SKILL.md +123 -0
  72. package/skills/audio-trap/example.html +119 -0
  73. package/skills/render-retest/example.html +115 -0
  74. package/skills/tuneframes/SKILL.md +221 -0
  75. package/skills/tuneframes-cli/SKILL.md +46 -0
  76. package/skills/verify/example.html +104 -0
  77. package/src/cli.js +261 -89
  78. package/src/render.js +134 -7
@@ -1,45 +1,45 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>TuneFrames — Lofi Example</title>
5
- <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
6
- </head>
7
- <body>
8
- <div id="tuneframes" style="display:none">{"bpm":80,"duration":"1m"}</div>
9
- <script>
10
- async function main() {
11
- await Tone.start();
12
-
13
- const reverb = new Tone.Reverb({ decay: 4, wet: 0.6 }).toDestination();
14
- const pad = new Tone.PolySynth(Tone.Synth, {
15
- oscillator: { type: 'triangle' },
16
- envelope: { attack: 0.5, decay: 0.3, sustain: 0.8, release: 2 }
17
- }).connect(reverb);
18
-
19
- // Am - F - C - G chord progression
20
- const chords = [
21
- ['A3', 'C4', 'E4'],
22
- ['F3', 'A3', 'C4'],
23
- ['C3', 'E3', 'G3'],
24
- ['G3', 'B3', 'D4'],
25
- ];
26
-
27
- // Play each chord for 2 beats at 80 BPM
28
- for (let i = 0; i < chords.length; i++) {
29
- pad.triggerAttackRelease(chords[i], '2n', i * Tone.Time('2n').toSeconds());
30
- }
31
-
32
- // Simple melody
33
- const melody = new Tone.Synth({
34
- oscillator: { type: 'sine' },
35
- envelope: { attack: 0.01, decay: 0.1, sustain: 0.3, release: 0.5 }
36
- }).toDestination();
37
-
38
- melody.triggerAttackRelease('C5', '8n', Tone.Time('2n').toSeconds() * 0.5);
39
- melody.triggerAttackRelease('E5', '8n', Tone.Time('2n').toSeconds() * 1.5);
40
- melody.triggerAttackRelease('G5', '8n', Tone.Time('2n').toSeconds() * 2.5);
41
- melody.triggerAttackRelease('C5', '4n', Tone.Time('2n').toSeconds() * 3.5);
42
- }
43
- </script>
44
- </body>
45
- </html>
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>TuneFrames — Lofi Example</title>
5
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
6
+ </head>
7
+ <body>
8
+ <div id="tuneframes" style="display:none">{"bpm":80,"duration":"10s"}</div>
9
+ <script>
10
+ async function main() {
11
+ await Tone.start();
12
+
13
+ const reverb = new Tone.Reverb({ decay: 4, wet: 0.6 }).toDestination();
14
+ const pad = new Tone.PolySynth(Tone.Synth, {
15
+ oscillator: { type: 'triangle' },
16
+ envelope: { attack: 0.5, decay: 0.3, sustain: 0.8, release: 2 }
17
+ }).connect(reverb);
18
+
19
+ // Am - F - C - G chord progression
20
+ const chords = [
21
+ ['A3', 'C4', 'E4'],
22
+ ['F3', 'A3', 'C4'],
23
+ ['C3', 'E3', 'G3'],
24
+ ['G3', 'B3', 'D4'],
25
+ ];
26
+
27
+ // Play each chord for 2 beats at 80 BPM
28
+ for (let i = 0; i < chords.length; i++) {
29
+ pad.triggerAttackRelease(chords[i], '2n', i * Tone.Time('2n').toSeconds());
30
+ }
31
+
32
+ // Simple melody
33
+ const melody = new Tone.Synth({
34
+ oscillator: { type: 'sine' },
35
+ envelope: { attack: 0.01, decay: 0.1, sustain: 0.3, release: 0.5 }
36
+ }).toDestination();
37
+
38
+ melody.triggerAttackRelease('C5', '8n', Tone.Time('2n').toSeconds() * 0.5);
39
+ melody.triggerAttackRelease('E5', '8n', Tone.Time('2n').toSeconds() * 1.5);
40
+ melody.triggerAttackRelease('G5', '8n', Tone.Time('2n').toSeconds() * 2.5);
41
+ melody.triggerAttackRelease('C5', '4n', Tone.Time('2n').toSeconds() * 3.5);
42
+ }
43
+ </script>
44
+ </body>
45
+ </html>
Binary file
@@ -1,19 +1,21 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>TuneFrames — Minimal Test</title>
5
- <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
6
- </head>
7
- <body>
8
- <script>
9
- // This must be a named async function for the renderer to call
10
- async function main() {
11
- // BPM set in #tuneframes metadata
12
- await Tone.start();
13
- const synth = new Tone.Synth().toDestination();
14
- // Explicitly schedule at transport time 0
15
- synth.triggerAttackRelease('C4', '4n', 0);
16
- }
17
- </script>
18
- </body>
19
- </html>
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>TuneFrames — Minimal</title>
5
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
6
+ </head>
7
+ <body>
8
+ <div id="tuneframes" style="display:none">{"bpm":120,"duration":"2s"}</div>
9
+ <script>
10
+ async function main() {
11
+ await Tone.start();
12
+ const synth = new Tone.Synth().toDestination();
13
+ const beat = Tone.Time('4n').toSeconds();
14
+ // C major arpeggio three notes, clean and simple
15
+ synth.triggerAttackRelease('C4', '4n', 0);
16
+ synth.triggerAttackRelease('E4', '4n', beat);
17
+ synth.triggerAttackRelease('G4', '4n', beat * 2);
18
+ }
19
+ </script>
20
+ </body>
21
+ </html>
Binary file
@@ -1,67 +1,67 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>TuneFrames — Orchestral</title>
5
- <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
6
- </head>
7
- <body>
8
- <div id="tuneframes" style="display:none">{"bpm":72,"duration":"1m"}</div>
9
- <script>
10
- async function main() {
11
- await Tone.start();
12
-
13
- const verb = new Tone.Reverb({ decay: 6, wet: 0.5 }).toDestination();
14
-
15
- // Strings section
16
- const strings = new Tone.PolySynth(Tone.Synth, {
17
- oscillator: { type: 'sawtooth' },
18
- envelope: { attack: 1.5, decay: 0.3, sustain: 0.9, release: 3 }
19
- }).connect(verb);
20
- strings.volume.value = -12;
21
-
22
- // Brass
23
- const brass = new Tone.PolySynth(Tone.Synth, {
24
- oscillator: { type: 'sawtooth' },
25
- envelope: { attack: 0.3, decay: 0.2, sustain: 0.7, release: 1.5 }
26
- }).connect(verb);
27
- brass.volume.value = -10;
28
-
29
- // Timpani
30
- const timpani = new Tone.MembraneSynth({
31
- pitchDecay: 0.05,
32
- octaves: 4,
33
- oscillator: { type: 'sine' },
34
- envelope: { attack: 0.001, decay: 1, sustain: 0, release: 2 }
35
- }).toDestination();
36
- timpani.volume.value = -8;
37
-
38
- const beat = Tone.Time('1n').toSeconds();
39
-
40
- // Strings: swell on every bar
41
- const stringChords = [
42
- ['D3', 'F3', 'A3', 'C4'], // Dm7
43
- ['C3', 'E3', 'G3', 'B3'], // Cmaj7
44
- ['Bb2', 'D3', 'F3', 'A3'], // Bbmaj7
45
- ['A2', 'C3', 'E3', 'G3'], // Am7
46
- ];
47
- stringChords.forEach((chord, i) => {
48
- strings.triggerAttackRelease(chord, '1n', i * beat);
49
- });
50
-
51
- // Brass: enters on bar 3 with fanfare
52
- const fanfares = [
53
- ['D4', 'F4', 'A4'],
54
- ['C4', 'E4', 'G4'],
55
- ];
56
- fanfares.forEach((chord, i) => {
57
- brass.triggerAttackRelease(chord, '2n', (i + 2) * beat);
58
- });
59
-
60
- // Timpani: tonic hits on bars 1 and 3
61
- timpani.triggerAttackRelease('D2', '1n', 0);
62
- timpani.triggerAttackRelease('D2', '1n', beat * 2);
63
- timpani.triggerAttackRelease('A1', '2n', beat * 3.5);
64
- }
65
- </script>
66
- </body>
67
- </html>
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>TuneFrames — Orchestral</title>
5
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
6
+ </head>
7
+ <body>
8
+ <div id="tuneframes" style="display:none">{"bpm":72,"duration":"14s"}</div>
9
+ <script>
10
+ async function main() {
11
+ await Tone.start();
12
+
13
+ const verb = new Tone.Reverb({ decay: 6, wet: 0.5 }).toDestination();
14
+
15
+ // Strings section
16
+ const strings = new Tone.PolySynth(Tone.Synth, {
17
+ oscillator: { type: 'sawtooth' },
18
+ envelope: { attack: 1.5, decay: 0.3, sustain: 0.9, release: 3 }
19
+ }).connect(verb);
20
+ strings.volume.value = -12;
21
+
22
+ // Brass
23
+ const brass = new Tone.PolySynth(Tone.Synth, {
24
+ oscillator: { type: 'sawtooth' },
25
+ envelope: { attack: 0.3, decay: 0.2, sustain: 0.7, release: 1.5 }
26
+ }).connect(verb);
27
+ brass.volume.value = -10;
28
+
29
+ // Timpani
30
+ const timpani = new Tone.MembraneSynth({
31
+ pitchDecay: 0.05,
32
+ octaves: 4,
33
+ oscillator: { type: 'sine' },
34
+ envelope: { attack: 0.001, decay: 1, sustain: 0, release: 2 }
35
+ }).toDestination();
36
+ timpani.volume.value = -8;
37
+
38
+ const beat = Tone.Time('1n').toSeconds();
39
+
40
+ // Strings: swell on every bar
41
+ const stringChords = [
42
+ ['D3', 'F3', 'A3', 'C4'], // Dm7
43
+ ['C3', 'E3', 'G3', 'B3'], // Cmaj7
44
+ ['Bb2', 'D3', 'F3', 'A3'], // Bbmaj7
45
+ ['A2', 'C3', 'E3', 'G3'], // Am7
46
+ ];
47
+ stringChords.forEach((chord, i) => {
48
+ strings.triggerAttackRelease(chord, '1n', i * beat);
49
+ });
50
+
51
+ // Brass: enters on bar 3 with fanfare
52
+ const fanfares = [
53
+ ['D4', 'F4', 'A4'],
54
+ ['C4', 'E4', 'G4'],
55
+ ];
56
+ fanfares.forEach((chord, i) => {
57
+ brass.triggerAttackRelease(chord, '2n', (i + 2) * beat);
58
+ });
59
+
60
+ // Timpani: tonic hits on bars 1 and 3
61
+ timpani.triggerAttackRelease('D2', '1n', 0);
62
+ timpani.triggerAttackRelease('D2', '1n', beat * 2);
63
+ timpani.triggerAttackRelease('A1', '2n', beat * 3.5);
64
+ }
65
+ </script>
66
+ </body>
67
+ </html>
Binary file
@@ -0,0 +1,53 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>TuneFrames — Piano</title>
5
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
6
+ </head>
7
+ <body>
8
+ <div id="tuneframes" style="display:none">{"bpm":100,"duration":"5s"}</div>
9
+ <script>
10
+ async function main() {
11
+ await Tone.start();
12
+ const beat = Tone.Time('4n').toSeconds();
13
+
14
+ const piano = new Tone.PolySynth(Tone.Synth, {
15
+ oscillator: { type: 'triangle' },
16
+ envelope: { attack: 0.005, decay: 0.3, sustain: 0.3, release: 1.2 }
17
+ }).toDestination();
18
+ piano.volume.value = -8;
19
+
20
+ // Dm - Am - Bb - F chord progression, 2 bars each
21
+ const chords = [
22
+ ['D3', 'F3', 'A3'], // Dm
23
+ ['A2', 'C3', 'E3'], // Am
24
+ ['Bb2', 'D3', 'F3'], // Bb
25
+ ['F2', 'A2', 'C3'] // F
26
+ ];
27
+
28
+ chords.forEach((chord, i) => {
29
+ piano.triggerAttackRelease(chord, '2n', i * 2 * beat);
30
+ });
31
+
32
+ // Melodic line: simple pentatonic over the changes
33
+ const melody = [
34
+ { note: 'D4', time: 0 },
35
+ { note: 'F4', time: beat },
36
+ { note: 'A4', time: beat * 2 },
37
+ { note: 'C4', time: 2 * beat },
38
+ { note: 'E4', time: 2.5 * beat },
39
+ { note: 'D4', time: 3 * beat },
40
+ { note: 'F4', time: 4 * beat },
41
+ { note: 'G4', time: 4.5 * beat },
42
+ { note: 'A4', time: 5 * beat },
43
+ { note: 'Bb4', time: 6 * beat },
44
+ { note: 'F4', time: 7 * beat }
45
+ ];
46
+
47
+ melody.forEach(({ note, time }) => {
48
+ if (time < 5) piano.triggerAttackRelease(note, '8n', time);
49
+ });
50
+ }
51
+ </script>
52
+ </body>
53
+ </html>
Binary file
@@ -1,69 +1,69 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>TuneFrames - Techno</title>
5
- <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
6
- </head>
7
- <body>
8
- <div id="tuneframes" style="display:none">{"bpm":130,"duration":"2n"}</div>
9
- <script>
10
- async function main() {
11
- await Tone.start();
12
- const beat = Tone.Time('4n').toSeconds();
13
-
14
- const comp = new Tone.Compressor(-18, 5).toDestination();
15
- const verb = new Tone.Reverb({ decay: 1, wet: 0.3 }).toDestination();
16
-
17
- // Kick - 4-on-the-floor
18
- const kick = new Tone.MembraneSynth({
19
- pitchDecay: 0.05, octaves: 6,
20
- oscillator: { type: 'sine' },
21
- envelope: { attack: 0.001, decay: 0.3, sustain: 0, release: 0.1 }
22
- }).connect(comp);
23
-
24
- // Hi-hat: NoiseSynth (white noise + filter envelope)
25
- const hihat = new Tone.NoiseSynth({
26
- noise: { type: 'white' },
27
- envelope: { attack: 0.001, decay: 0.03, sustain: 0, release: 0.01 }
28
- }).toDestination();
29
- hihat.volume.value = -10;
30
-
31
- // Bass: detuned saws
32
- const bass = new Tone.MonoSynth({
33
- oscillator: { type: 'sawtooth' },
34
- filter: { Q: 3, type: 'lowpass', rolloff: -24 },
35
- envelope: { attack: 0.005, decay: 0.15, sustain: 0.5, release: 0.1 },
36
- filterEnvelope: { attack: 0.001, decay: 0.1, sustain: 0.3, release: 0.2, baseFrequency: 80, octaves: 4 }
37
- }).connect(comp);
38
-
39
- // Pad: parallel saw chords
40
- const pad = new Tone.PolySynth(Tone.Synth, {
41
- oscillator: { type: 'sawtooth' },
42
- envelope: { attack: 0.2, decay: 0.4, sustain: 0.7, release: 0.8 }
43
- }).connect(verb);
44
- pad.volume.value = -14;
45
-
46
- // Kick: 4-on-the-floor every beat
47
- for (let i = 0; i < 8; i++) {
48
- kick.triggerAttackRelease('C1', '8n', i * beat);
49
- }
50
-
51
- // Hi-hat: offbeat 16ths (16 events)
52
- for (let i = 0; i < 16; i++) {
53
- hihat.triggerAttackRelease('16n', (i + 0.5) * beat * 0.5);
54
- }
55
-
56
- // Bass: syncopated
57
- const bassPat = ['E1', 'E1', null, 'G1', 'A1', null, 'B1', 'A1', 'G1', null, 'E1', 'E1'];
58
- bassPat.forEach((note, i) => {
59
- if (note) bass.triggerAttackRelease(note, '8n', i * beat * 0.5);
60
- });
61
-
62
- // Pad: slow chord stabs
63
- pad.triggerAttackRelease(['E2', 'G2', 'B2'], '2n', 0);
64
- pad.triggerAttackRelease(['C2', 'E2', 'G2'], '2n', beat * 4);
65
- pad.triggerAttackRelease(['D2', 'F2', 'A2'], '2n', beat * 6);
66
- }
67
- </script>
68
- </body>
69
- </html>
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>TuneFrames - Techno</title>
5
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
6
+ </head>
7
+ <body>
8
+ <div id="tuneframes" style="display:none">{"bpm":130,"duration":"4s"}</div>
9
+ <script>
10
+ async function main() {
11
+ await Tone.start();
12
+ const beat = Tone.Time('4n').toSeconds();
13
+
14
+ const comp = new Tone.Compressor(-18, 5).toDestination();
15
+ const verb = new Tone.Reverb({ decay: 1, wet: 0.3 }).toDestination();
16
+
17
+ // Kick - 4-on-the-floor
18
+ const kick = new Tone.MembraneSynth({
19
+ pitchDecay: 0.05, octaves: 6,
20
+ oscillator: { type: 'sine' },
21
+ envelope: { attack: 0.001, decay: 0.3, sustain: 0, release: 0.1 }
22
+ }).connect(comp);
23
+
24
+ // Hi-hat: NoiseSynth (white noise + filter envelope)
25
+ const hihat = new Tone.NoiseSynth({
26
+ noise: { type: 'white' },
27
+ envelope: { attack: 0.001, decay: 0.03, sustain: 0, release: 0.01 }
28
+ }).toDestination();
29
+ hihat.volume.value = -10;
30
+
31
+ // Bass: detuned saws
32
+ const bass = new Tone.MonoSynth({
33
+ oscillator: { type: 'sawtooth' },
34
+ filter: { Q: 3, type: 'lowpass', rolloff: -24 },
35
+ envelope: { attack: 0.005, decay: 0.15, sustain: 0.5, release: 0.1 },
36
+ filterEnvelope: { attack: 0.001, decay: 0.1, sustain: 0.3, release: 0.2, baseFrequency: 80, octaves: 4 }
37
+ }).connect(comp);
38
+
39
+ // Pad: parallel saw chords
40
+ const pad = new Tone.PolySynth(Tone.Synth, {
41
+ oscillator: { type: 'sawtooth' },
42
+ envelope: { attack: 0.2, decay: 0.4, sustain: 0.7, release: 0.8 }
43
+ }).connect(verb);
44
+ pad.volume.value = -14;
45
+
46
+ // Kick: 4-on-the-floor every beat
47
+ for (let i = 0; i < 8; i++) {
48
+ kick.triggerAttackRelease('C1', '8n', i * beat);
49
+ }
50
+
51
+ // Hi-hat: offbeat 16ths (16 events)
52
+ for (let i = 0; i < 16; i++) {
53
+ hihat.triggerAttackRelease('16n', (i + 0.5) * beat * 0.5);
54
+ }
55
+
56
+ // Bass: syncopated
57
+ const bassPat = ['E1', 'E1', null, 'G1', 'A1', null, 'B1', 'A1', 'G1', null, 'E1', 'E1'];
58
+ bassPat.forEach((note, i) => {
59
+ if (note) bass.triggerAttackRelease(note, '8n', i * beat * 0.5);
60
+ });
61
+
62
+ // Pad: slow chord stabs
63
+ pad.triggerAttackRelease(['E2', 'G2', 'B2'], '2n', 0);
64
+ pad.triggerAttackRelease(['C2', 'E2', 'G2'], '2n', beat * 4);
65
+ pad.triggerAttackRelease(['D2', 'F2', 'A2'], '2n', beat * 6);
66
+ }
67
+ </script>
68
+ </body>
69
+ </html>
Binary file
package/package.json CHANGED
@@ -1,24 +1,42 @@
1
- {
2
- "name": "tuneframes",
3
- "version": "0.1.0",
4
- "description": "Write HTML with Tone.js. Render music to MP3/WAV with one CLI command. Built for AI agents.",
5
- "repository": "github:cusidoc/tuneframes",
6
- "homepage": "https://github.com/cusidoc/tuneframes#readme",
7
- "author": "Nick Shepherd <nick@moltos.org>",
8
- "main": "src/cli.js",
9
- "bin": {
10
- "tuneframes": "src/cli.js"
11
- },
12
- "scripts": {
13
- "render": "node src/render.js",
14
- "test": "node src/cli.js render examples/example-minimal.html"
15
- },
16
- "keywords": ["music", "tone.js", "agent", "hyperframes", "ai"],
17
- "license": "Apache-2.0",
18
- "dependencies": {
19
- "playwright": "^1.40.0"
20
- },
21
- "engines": {
22
- "node": ">=18"
23
- }
24
- }
1
+ {
2
+ "name": "tuneframes",
3
+ "version": "0.2.0",
4
+ "description": "Agent-native music generation. Write Tone.js, render to audio.",
5
+ "main": "src/cli.js",
6
+ "bin": {
7
+ "tuneframes": "src/cli.js"
8
+ },
9
+ "scripts": {
10
+ "render": "node src/cli.js render",
11
+ "preview": "node src/cli.js preview"
12
+ },
13
+ "files": [
14
+ "src",
15
+ "examples",
16
+ "registry",
17
+ "skills"
18
+ ],
19
+ "keywords": [
20
+ "tonejs",
21
+ "music",
22
+ "audio",
23
+ "generation",
24
+ "agents",
25
+ "web-audio",
26
+ "soundfont",
27
+ "sampler",
28
+ "instruments"
29
+ ],
30
+ "author": "Nathan Shepherd",
31
+ "license": "Apache-2.0",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/Shepherd217/TuneFrames.git"
35
+ },
36
+ "engines": {
37
+ "node": ">=18"
38
+ },
39
+ "dependencies": {
40
+ "playwright": "^1.44.0"
41
+ }
42
+ }
@@ -0,0 +1,67 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
5
+ </head>
6
+ <body>
7
+ <!-- Walking bass line in C minor — acoustic_bass from gleitz CDN -->
8
+ <!-- 2 bars at 100 BPM: 1 beat = 0.6s, 8 quarter notes = 4.8s -->
9
+ <div id="tuneframes" style="display:none">{"bpm":100,"duration":"6s"}</div>
10
+ <script>
11
+ async function main() {
12
+ await Tone.start();
13
+
14
+ // Load acoustic bass samples from gleitz FluidR3_GM CDN
15
+ const bass = new Tone.Sampler({
16
+ urls: {
17
+ C1: 'C1.mp3',
18
+ Ds1: 'Ds1.mp3',
19
+ Fs1: 'Fs1.mp3',
20
+ A1: 'A1.mp3',
21
+ C2: 'C2.mp3',
22
+ Ds2: 'Ds2.mp3',
23
+ Fs2: 'Fs2.mp3',
24
+ A2: 'A2.mp3',
25
+ C3: 'C3.mp3',
26
+ Ds3: 'Ds3.mp3',
27
+ Fs3: 'Fs3.mp3',
28
+ A3: 'A3.mp3',
29
+ C4: 'C4.mp3'
30
+ },
31
+ baseUrl: 'https://gleitz.github.io/midi-js-soundfonts/FluidR3_GM/acoustic_bass-mp3/'
32
+ });
33
+
34
+ // Subtle compression to even out the pluck dynamics
35
+ const comp = new Tone.Compressor({ threshold: -18, ratio: 3 }).toDestination();
36
+ bass.connect(comp);
37
+
38
+ // CRITICAL: wait for samples to load before scheduling any notes
39
+ await Tone.loaded();
40
+
41
+ const beat = Tone.Time('4n').toSeconds(); // 0.6s at 100 BPM
42
+ const dur = '4n'; // each walking note lasts one quarter note
43
+
44
+ // Walking bass in C minor — 2 bars, 4 notes per bar
45
+ // Bar 1: root → approach from below → fifth → seventh (classic walking move)
46
+ // Bar 2: resolve back down with chromatic approach to the root
47
+ const line = [
48
+ // Bar 1 — Cm feel
49
+ 'C2', // root
50
+ 'Eb2', // minor third
51
+ 'G2', // fifth
52
+ 'Bb2', // minor seventh
53
+
54
+ // Bar 2 — Fm → G7 turnaround, walking back to root
55
+ 'Ab2', // flat-six (Fm)
56
+ 'G2', // fifth (G dominant)
57
+ 'F2', // approach
58
+ 'Eb2' // land on minor third, implies return to Cm
59
+ ];
60
+
61
+ line.forEach((note, i) => {
62
+ bass.triggerAttackRelease(note, dur, i * beat);
63
+ });
64
+ }
65
+ </script>
66
+ </body>
67
+ </html>
@@ -0,0 +1,22 @@
1
+ <!-- TuneFrames preset: bass-saw
2
+ Detuned saw MonoSynth bass. Paste inside <script>.
3
+ Set duration to cover the number of beats you've scheduled. -->
4
+ <script>
5
+ const beat = Tone.Time('4n').toSeconds();
6
+
7
+ const bass = new Tone.MonoSynth({
8
+ oscillator: { type: 'sawtooth' },
9
+ filter: { Q: 4, type: 'lowpass', rolloff: -24 },
10
+ envelope: { attack: 0.005, decay: 0.2, sustain: 0.6, release: 0.15 },
11
+ filterEnvelope: {
12
+ attack: 0.001, decay: 0.1, sustain: 0.4, release: 0.2,
13
+ baseFrequency: 80, octaves: 4
14
+ }
15
+ }).toDestination();
16
+ bass.volume.value = -6;
17
+
18
+ const pattern = ['E1', null, 'E1', 'G1', 'A1', null, 'B1', 'A1', 'G1', null, 'E1', 'E1'];
19
+ pattern.forEach((note, i) => {
20
+ if (note) bass.triggerAttackRelease(note, '8n', i * beat * 0.5);
21
+ });
22
+ </script>
@@ -0,0 +1,22 @@
1
+ <!-- TuneFrames preset: chord-progression
2
+ Dm → C → Bb → Am, 1 measure each.
3
+ Paste inside <script>. Set metadata duration to "4s" per chord (16s total for all 4). -->
4
+ <script>
5
+ const measure = Tone.Time('1n').toSeconds();
6
+
7
+ const pad = new Tone.PolySynth(Tone.Synth, {
8
+ oscillator: { type: 'sawtooth' },
9
+ envelope: { attack: 0.5, decay: 0.3, sustain: 0.7, release: 1.5 }
10
+ }).toDestination();
11
+
12
+ const progression = [
13
+ { chord: ['D3', 'F3', 'A3'], name: 'Dm' },
14
+ { chord: ['C3', 'E3', 'G3'], name: 'C' },
15
+ { chord: ['Bb2', 'D3', 'F3'], name: 'Bb' },
16
+ { chord: ['A2', 'C3', 'E3'], name: 'Am' },
17
+ ];
18
+
19
+ progression.forEach(({ chord }, i) => {
20
+ pad.triggerAttackRelease(chord, '1n', i * measure);
21
+ });
22
+ </script>