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
Binary file
@@ -1,53 +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>
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
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":"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>
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,37 +1,42 @@
1
- {
2
- "name": "tuneframes",
3
- "version": "0.1.1",
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
- ],
17
- "keywords": [
18
- "tonejs",
19
- "music",
20
- "audio",
21
- "generation",
22
- "agents",
23
- "web-audio"
24
- ],
25
- "author": "Nathan Shepherd",
26
- "license": "Apache-2.0",
27
- "repository": {
28
- "type": "git",
29
- "url": "https://github.com/Shepherd217/TuneFrames"
30
- },
31
- "engines": {
32
- "node": ">=18"
33
- },
34
- "dependencies": {
35
- "puppeteer": "^22.0.0"
36
- }
37
- }
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>
@@ -0,0 +1,85 @@
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
+ <!-- Classic trap 808 pattern — synthesis only, no CDN required -->
8
+ <!-- 2 bars at 140 BPM: 1 bar = 4 beats × (60/140)s ≈ 1.714s → 2 bars ≈ 3.43s -->
9
+ <div id="tuneframes" style="display:none">{"bpm":140,"duration":"7s"}</div>
10
+ <script>
11
+ async function main() {
12
+ await Tone.start();
13
+
14
+ // --- 808 Kick: pitched MembraneSynth with long pitch fall ---
15
+ const kick808 = new Tone.MembraneSynth({
16
+ pitchDecay: 0.5,
17
+ octaves: 8,
18
+ envelope: { attack: 0.001, decay: 0.8, sustain: 0, release: 0.4 }
19
+ }).toDestination();
20
+ kick808.volume.value = 2;
21
+
22
+ // --- Snare: layered noise (body) + click (transient) ---
23
+ const snareNoise = new Tone.NoiseSynth({
24
+ noise: { type: 'white' },
25
+ envelope: { attack: 0.001, decay: 0.18, sustain: 0, release: 0.05 }
26
+ });
27
+ const snareFilter = new Tone.Filter({ frequency: 3500, type: 'highpass' }).toDestination();
28
+ snareNoise.connect(snareFilter);
29
+ snareNoise.volume.value = -4;
30
+
31
+ // --- Hi-hat: MetalSynth for crisp metallic transient ---
32
+ const hihat = new Tone.MetalSynth({
33
+ frequency: 400,
34
+ envelope: { attack: 0.001, decay: 0.04, release: 0.01 },
35
+ harmonicity: 5.1,
36
+ modulationIndex: 32,
37
+ resonance: 4000,
38
+ octaves: 1.5
39
+ }).toDestination();
40
+ hihat.volume.value = -10;
41
+
42
+ // Open hi-hat: longer decay
43
+ const openHat = new Tone.MetalSynth({
44
+ frequency: 500,
45
+ envelope: { attack: 0.001, decay: 0.3, release: 0.1 },
46
+ harmonicity: 5.1,
47
+ modulationIndex: 32,
48
+ resonance: 4000,
49
+ octaves: 1.5
50
+ }).toDestination();
51
+ openHat.volume.value = -14;
52
+
53
+ const beat = Tone.Time('4n').toSeconds(); // one beat in seconds at 140 BPM
54
+ const bar = beat * 4;
55
+
56
+ // Render 2 bars of trap pattern
57
+ for (let b = 0; b < 2; b++) {
58
+ const o = b * bar;
59
+
60
+ // --- 808 kick: beats 1, 2.5, 3 (classic trap syncopation) ---
61
+ kick808.triggerAttackRelease('C1', '8n', o);
62
+ kick808.triggerAttackRelease('C1', '8n', o + beat * 1.5);
63
+ kick808.triggerAttackRelease('C1', '8n', o + beat * 2);
64
+
65
+ // --- Snare: beat 3 (half-time feel) ---
66
+ snareNoise.triggerAttackRelease('8n', o + beat * 2);
67
+
68
+ // --- Hi-hats: 16th notes throughout, open hat on beat 3-and ---
69
+ const sixteenth = beat / 4;
70
+ for (let s = 0; s < 16; s++) {
71
+ // Skip the snare slot (beat 3) on closed hats so open hat rings
72
+ if (s === 8) {
73
+ openHat.triggerAttackRelease('C5', '8n', o + s * sixteenth);
74
+ } else {
75
+ // Accent downbeats slightly
76
+ const vel = (s % 4 === 0) ? 1.0 : (s % 2 === 0 ? 0.6 : 0.35);
77
+ hihat.volume.value = -10 + (vel - 1) * 4;
78
+ hihat.triggerAttackRelease('C5', '32n', o + s * sixteenth);
79
+ }
80
+ }
81
+ }
82
+ }
83
+ </script>
84
+ </body>
85
+ </html>
@@ -0,0 +1,35 @@
1
+ <!-- TuneFrames preset: drums-lofi
2
+ Kick + snare + hi-hat loop. Paste inside <script> in your composition. -->
3
+ <script>
4
+ const beat = Tone.Time('4n').toSeconds();
5
+
6
+ const kick = new Tone.MembraneSynth({
7
+ pitchDecay: 0.05, octaves: 6,
8
+ oscillator: { type: 'sine' },
9
+ envelope: { attack: 0.001, decay: 0.4, sustain: 0, release: 0.1 }
10
+ }).toDestination();
11
+
12
+ const snare = new Tone.NoiseSynth({
13
+ noise: { type: 'white' },
14
+ envelope: { attack: 0.001, decay: 0.2, sustain: 0, release: 0.1 }
15
+ }).toDestination();
16
+
17
+ const hihat = new Tone.NoiseSynth({
18
+ noise: { type: 'white' },
19
+ envelope: { attack: 0.001, decay: 0.04, sustain: 0, release: 0.01 }
20
+ }).toDestination();
21
+ hihat.volume.value = -14;
22
+
23
+ // Kick on 1, 2, 3, 4
24
+ for (let i = 0; i < 4; i++) {
25
+ kick.triggerAttackRelease('C1', '4n', i * beat);
26
+ }
27
+
28
+ // Snare on 2 and 4
29
+ [1, 3].forEach(i => snare.triggerAttackRelease('C3', '4n', i * beat));
30
+
31
+ // Hi-hat on every 8th note
32
+ for (let i = 0; i < 8; i++) {
33
+ hihat.triggerAttackRelease('16n', i * beat * 0.5);
34
+ }
35
+ </script>
@@ -0,0 +1,26 @@
1
+ <!-- TuneFrames preset: lead-piano
2
+ Simple 8-bar piano melody. Paste inside <script>. -->
3
+ <script>
4
+ const beat = Tone.Time('4n').toSeconds();
5
+
6
+ const piano = new Tone.PolySynth(Tone.Synth, {
7
+ oscillator: { type: 'triangle' },
8
+ envelope: { attack: 0.005, decay: 0.3, sustain: 0.4, release: 0.8 }
9
+ }).toDestination();
10
+ piano.volume.value = -8;
11
+
12
+ const melody = [
13
+ { note: 'C5', time: 0 },
14
+ { note: 'E5', time: 1 },
15
+ { note: 'G5', time: 2 },
16
+ { note: 'E5', time: 3 },
17
+ { note: 'D5', time: 4 },
18
+ { note: 'F5', time: 5 },
19
+ { note: 'A5', time: 6 },
20
+ { note: 'F5', time: 7 },
21
+ ];
22
+
23
+ melody.forEach(({ note, time }) => {
24
+ piano.triggerAttackRelease(note, '4n', time * beat);
25
+ });
26
+ </script>
@@ -0,0 +1,69 @@
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
+ <!-- Am - F - C - G chord progression, 4 chords × 2s = 8s -->
8
+ <div id="tuneframes" style="display:none">{"bpm":120,"duration":"9s"}</div>
9
+ <script>
10
+ async function main() {
11
+ await Tone.start();
12
+
13
+ // Load acoustic grand piano samples from gleitz FluidR3_GM CDN
14
+ const piano = new Tone.Sampler({
15
+ urls: {
16
+ A0: 'A0.mp3',
17
+ C2: 'C2.mp3',
18
+ Ds2: 'Ds2.mp3',
19
+ Fs2: 'Fs2.mp3',
20
+ A2: 'A2.mp3',
21
+ C3: 'C3.mp3',
22
+ Ds3: 'Ds3.mp3',
23
+ Fs3: 'Fs3.mp3',
24
+ A3: 'A3.mp3',
25
+ C4: 'C4.mp3',
26
+ Ds4: 'Ds4.mp3',
27
+ Fs4: 'Fs4.mp3',
28
+ A4: 'A4.mp3',
29
+ C5: 'C5.mp3',
30
+ Ds5: 'Ds5.mp3',
31
+ Fs5: 'Fs5.mp3',
32
+ A5: 'A5.mp3',
33
+ C6: 'C6.mp3',
34
+ A6: 'A6.mp3',
35
+ C7: 'C7.mp3',
36
+ C8: 'C8.mp3'
37
+ },
38
+ baseUrl: 'https://gleitz.github.io/midi-js-soundfonts/FluidR3_GM/acoustic_grand_piano-mp3/'
39
+ });
40
+
41
+ // Light reverb for hall ambience
42
+ const reverb = new Tone.Reverb({ decay: 2.5, wet: 0.25 }).toDestination();
43
+ piano.connect(reverb);
44
+
45
+ // CRITICAL: wait for all samples to load before scheduling any notes
46
+ await Tone.loaded();
47
+
48
+ // Am - F - C - G, 2 seconds each, 8 seconds total
49
+ // Voicings are close-position triads with a bass note for warmth
50
+ const chords = [
51
+ { notes: ['A2', 'E3', 'A3', 'C4', 'E4'], label: 'Am' },
52
+ { notes: ['F2', 'C3', 'F3', 'A3', 'C4'], label: 'F' },
53
+ { notes: ['C3', 'G3', 'C4', 'E4', 'G4'], label: 'C' },
54
+ { notes: ['G2', 'D3', 'G3', 'B3', 'D4'], label: 'G' }
55
+ ];
56
+
57
+ const chordDuration = 2; // seconds per chord
58
+ const noteDuration = '1n'; // sustain each chord for a whole note
59
+
60
+ chords.forEach((chord, i) => {
61
+ const t = i * chordDuration;
62
+ // Arpeggiate the bass note very slightly ahead of the chord for a natural feel
63
+ piano.triggerAttackRelease(chord.notes[0], noteDuration, t);
64
+ piano.triggerAttackRelease(chord.notes.slice(1), noteDuration, t + 0.04);
65
+ });
66
+ }
67
+ </script>
68
+ </body>
69
+ </html>