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,152 @@
1
+ ---
2
+ name: audio-downtempo
3
+ description: Downtempo / Trip-Hop — dark cinematic groove, Portishead/Massive Attack vibes, breakbeat swing, vinyl texture, sub bass
4
+ ---
5
+
6
+ # TuneFrames — Downtempo / Trip-Hop
7
+
8
+ ## Genre Profile
9
+ - BPM range: 70-90 (always swung — 16th notes have a lazy late feel)
10
+ - Key characteristics: heavy breakbeat (kick NOT on every beat — syncopated), vinyl noise texture, sub bass movement on root notes, eerie/minor melodic line, gritty filter on everything
11
+ - Typical instruments: MembraneSynth kick, NoiseSynth snare + crackle, MonoSynth sub bass with lowpass filter, AMSynth for eerie melody, BitCrusher for lo-fi grit
12
+ - Mood: gritty, hypnotic, paranoid, late-night cinematic
13
+
14
+ ## Core Pattern
15
+
16
+ ```js
17
+ // Trip-hop breakbeat — NOT four-on-the-floor
18
+ // Kick: 0, 3, 5 (in 8ths) — heavy, off-balance feel
19
+ // Snare: 2, 6 (on 2 and 4 but swung)
20
+ // BPM = 82, swung feel via manual timing offsets
21
+ Tone.Transport.bpm.value = 82;
22
+
23
+ // Lo-fi grit filter
24
+ const bitCrusher = new Tone.BitCrusher(6).toDestination();
25
+ const reverb = new Tone.Reverb({ decay: 4.0, wet: 0.6 });
26
+ reverb.toDestination();
27
+ const filter = new Tone.Filter({ frequency: 2000, type: 'lowpass', rolloff: -24 });
28
+ filter.connect(reverb);
29
+
30
+ // Sub bass — the backbone
31
+ const bass = new Tone.MonoSynth({
32
+ oscillator: { type: 'sine' },
33
+ filter: { Q: 2, frequency: 120, type: 'lowpass' },
34
+ envelope: { attack: 0.02, decay: 0.3, sustain: 0.6, release: 0.4 },
35
+ volume: -4,
36
+ }).toDestination();
37
+
38
+ // Eerie melody — AMSynth gives that trembling quality
39
+ const melody = new Tone.AMSynth({
40
+ harmonicity: 2.5,
41
+ envelope: { attack: 0.15, decay: 0.5, sustain: 0.3, release: 1.0 },
42
+ volume: -12,
43
+ });
44
+ melody.connect(filter);
45
+
46
+ // Breakbeat kick — syncopated
47
+ const kick = new Tone.MembraneSynth({
48
+ pitchDecay: 0.08, octaves: 10,
49
+ envelope: { attack: 0.001, decay: 0.5, sustain: 0, release: 0.1 },
50
+ volume: -2,
51
+ }).toDestination();
52
+
53
+ // Schedule kick on syncopated positions (seconds at 82 BPM, 1 beat = ~0.73s)
54
+ // Pattern over 2 bars (~5.85s): kick on beat 1, the "and" of 2, beat 3.5
55
+ const beatLen = 60 / 82;
56
+ [0, beatLen * 1.5, beatLen * 3, beatLen * 4, beatLen * 5.5, beatLen * 7].forEach(t => {
57
+ Tone.Transport.scheduleOnce(time => kick.triggerAttackRelease('C1', '8n', time), t);
58
+ });
59
+ ```
60
+
61
+ ## Instrument Configuration
62
+
63
+ ```js
64
+ // BitCrusher — vinyl artifact simulation
65
+ const bitCrusher = new Tone.BitCrusher(7); // lower = grittier
66
+ bitCrusher.toDestination();
67
+
68
+ // Dark reverb
69
+ const reverb = new Tone.Reverb({ decay: 5.0, preDelay: 0.03, wet: 0.65 });
70
+ await reverb.generate();
71
+ reverb.toDestination();
72
+
73
+ // Vinyl crackle — pink noise at very low volume, always running
74
+ const crackle = new Tone.NoiseSynth({
75
+ noise: { type: 'pink' },
76
+ envelope: { attack: 0.1, decay: 0.1, sustain: 1.0, release: 0.1 },
77
+ volume: -32,
78
+ }).toDestination();
79
+ crackle.triggerAttack(); // sustain forever
80
+
81
+ // Sub bass — pure sine, filtered hard
82
+ const bass = new Tone.MonoSynth({
83
+ oscillator: { type: 'sine' },
84
+ filter: { Q: 3, frequency: 100, type: 'lowpass', rolloff: -24 },
85
+ filterEnvelope: { attack: 0.05, decay: 0.5, sustain: 0.3, release: 0.8, baseFrequency: 60, octaves: 1.5 },
86
+ envelope: { attack: 0.02, decay: 0.2, sustain: 0.6, release: 0.5 },
87
+ volume: -3,
88
+ }).toDestination();
89
+
90
+ // Eerie lead — AMSynth with tremolo-like modulation
91
+ const lead = new Tone.AMSynth({
92
+ harmonicity: 3,
93
+ oscillator: { type: 'sawtooth' },
94
+ envelope: { attack: 0.2, decay: 0.8, sustain: 0.4, release: 2.0 },
95
+ modulation: { type: 'sine' },
96
+ modulationEnvelope: { attack: 0.5, decay: 0.0, sustain: 1.0, release: 0.5 },
97
+ volume: -14,
98
+ }).connect(reverb);
99
+
100
+ // Heavy kick
101
+ const kick = new Tone.MembraneSynth({
102
+ pitchDecay: 0.1, octaves: 12,
103
+ envelope: { attack: 0.001, decay: 0.6, sustain: 0, release: 0.15 },
104
+ volume: -1,
105
+ }).toDestination();
106
+
107
+ // Snare — layered noise (pink for body, white for crack)
108
+ const snare = new Tone.NoiseSynth({
109
+ noise: { type: 'pink' },
110
+ envelope: { attack: 0.001, decay: 0.18, sustain: 0, release: 0.12 },
111
+ volume: -8,
112
+ }).toDestination();
113
+ ```
114
+
115
+ ## Composition Structure
116
+
117
+ 1. **Crackle intro (0-2s):** Vinyl crackle only — establishes grit and atmosphere.
118
+ 2. **Bass enters (2-4s):** Sub bass plays the root. No other elements yet. Heavy.
119
+ 3. **Beat drop (4-6s):** Kick + snare syncopated breakbeat joins. Still just bass + beat.
120
+ 4. **Melody (6-12s):** AMSynth eerie melody over the groove. Should feel like it doesn't quite fit — that's correct.
121
+ 5. **Full loop (12s+):** All elements. Melody repeats with slight variation.
122
+
123
+ **Bass movement (minor feel):**
124
+ - Root: D2 → A2 → F2 → G2 (4 beats each)
125
+ - Melody follows but at half speed — slower, more ominous
126
+
127
+ ## Example Variations
128
+
129
+ ### Variation 1: Pure Portishead (drum + bass only)
130
+ ```js
131
+ // No melody — just kick, snare, bass, crackle
132
+ // Bass line has more movement: D2 → C2 → Bb1 → A1 (descending chromatic minor)
133
+ // Snare gets a slight BitCrusher: bitCrusher.connect(snare output)
134
+ // This is the most "hip-hop foundation" version
135
+ ```
136
+
137
+ ### Variation 2: Cinematic (add string texture)
138
+ ```js
139
+ // PolySynth triangle wave simulating strings
140
+ // Very long attack: 3.0s, release: 4.0s
141
+ // Notes: Dm chord sustaining throughout (D3, F3, A3)
142
+ // Volume: -20 — barely audible shimmer behind the groove
143
+ ```
144
+
145
+ ### Variation 3: Faster / more aggressive
146
+ ```js
147
+ Tone.Transport.bpm.value = 92;
148
+ // BitCrusher lower: 5 (crunchier)
149
+ // Kick volume: 0 (0dB — very present)
150
+ // Add 16th hi-hats at -24dB just for texture
151
+ // Lead melody shorter notes: 0.1s decay instead of 0.8s
152
+ ```
@@ -0,0 +1,164 @@
1
+ <div id="tuneframes" style="display:none">{"bpm":82,"duration":"12s"}</div>
2
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
3
+ <script>
4
+ // TuneFrames — Downtempo / Trip-Hop
5
+ // Portishead/Massive Attack: syncopated breakbeat, vinyl crackle,
6
+ // sub bass movement, AMSynth eerie melody, BitCrusher grit
7
+
8
+ async function main() {
9
+ await Tone.start();
10
+
11
+ const beat = 60 / 82; // one quarter note in seconds ≈ 0.732s
12
+ const eighth = beat / 2; // ≈ 0.366s
13
+ const sixteenth = beat / 4; // ≈ 0.183s
14
+
15
+ // ── Effects chain ──────────────────────────────────────────────────────────
16
+ const reverb = new Tone.Reverb({ decay: 5.5, preDelay: 0.03, wet: 0.65 });
17
+ await reverb.generate();
18
+ reverb.toDestination();
19
+
20
+ const filter = new Tone.Filter({ frequency: 2200, type: 'lowpass', rolloff: -24 });
21
+ filter.connect(reverb);
22
+
23
+ const bitCrusher = new Tone.BitCrusher(7);
24
+ bitCrusher.connect(reverb);
25
+
26
+ // ── Vinyl crackle — pink noise, always running ─────────────────────────────
27
+ // Constant sustain with no attack creates the "record playing" texture
28
+ const crackle = new Tone.NoiseSynth({
29
+ noise: { type: 'pink' },
30
+ envelope: { attack: 0.1, decay: 0.1, sustain: 1.0, release: 0.5 },
31
+ volume: -30,
32
+ }).toDestination();
33
+ // Start immediately in offline context
34
+ crackle.triggerAttack(0);
35
+
36
+ // ── Sub bass — pure sine, root movement ───────────────────────────────────
37
+ const bass = new Tone.MonoSynth({
38
+ oscillator: { type: 'sine' },
39
+ filter: { Q: 3, frequency: 110, type: 'lowpass', rolloff: -24 },
40
+ filterEnvelope: {
41
+ attack: 0.05, decay: 0.6, sustain: 0.3, release: 0.8,
42
+ baseFrequency: 60, octaves: 1.2,
43
+ },
44
+ envelope: { attack: 0.02, decay: 0.25, sustain: 0.65, release: 0.5 },
45
+ volume: -3,
46
+ }).toDestination();
47
+
48
+ // ── Eerie melody — AMSynth for trembling character ────────────────────────
49
+ const lead = new Tone.AMSynth({
50
+ harmonicity: 2.8,
51
+ oscillator: { type: 'sawtooth' },
52
+ envelope: { attack: 0.18, decay: 0.7, sustain: 0.35, release: 2.0 },
53
+ modulation: { type: 'sine' },
54
+ modulationEnvelope: {
55
+ attack: 0.4, decay: 0.0, sustain: 1.0, release: 0.8,
56
+ },
57
+ volume: -14,
58
+ });
59
+ lead.connect(filter);
60
+
61
+ // ── Kick — heavy, syncopated (NOT four-on-the-floor) ─────────────────────
62
+ const kick = new Tone.MembraneSynth({
63
+ pitchDecay: 0.1, octaves: 12,
64
+ envelope: { attack: 0.001, decay: 0.62, sustain: 0, release: 0.15 },
65
+ volume: -1,
66
+ }).toDestination();
67
+
68
+ // ── Snare — pink noise, sits late on 2 and 4 (swung feel) ────────────────
69
+ const snare = new Tone.NoiseSynth({
70
+ noise: { type: 'pink' },
71
+ envelope: { attack: 0.001, decay: 0.2, sustain: 0, release: 0.14 },
72
+ volume: -8,
73
+ });
74
+ snare.connect(bitCrusher);
75
+
76
+ // ── Hi-hat — sparse, off-beat 16th accents ────────────────────────────────
77
+ const hihat = new Tone.NoiseSynth({
78
+ noise: { type: 'white' },
79
+ envelope: { attack: 0.001, decay: 0.05, sustain: 0, release: 0.02 },
80
+ volume: -22,
81
+ }).toDestination();
82
+
83
+ // ── Schedule: bass enters at 0, beat at 2s, melody at 5s ─────────────────
84
+
85
+ // Bass line: D2 → A2 → F2 → G2 (minor feel, moves every 2 beats)
86
+ // Crackle plays from 0, bass from ~0, drums from 2s, melody from 5s
87
+ const bassNotes = [
88
+ { note: 'D2', t: 0 },
89
+ { note: 'A1', t: beat * 2 },
90
+ { note: 'F1', t: beat * 4 },
91
+ { note: 'G1', t: beat * 6 },
92
+ { note: 'D2', t: beat * 8 },
93
+ { note: 'A1', t: beat * 10 },
94
+ { note: 'F1', t: beat * 12 },
95
+ { note: 'G1', t: beat * 14 },
96
+ ];
97
+ bassNotes.forEach(({ note, t }) => {
98
+ if (t < 12) bass.triggerAttackRelease(note, '4n', t);
99
+ });
100
+
101
+ // Breakbeat kick pattern (syncopated — not on every beat)
102
+ // Pattern over 2 bars: beats 1, the-and-of-2, 3, the-and-of-4
103
+ // In 8ths at 82 BPM: positions 0, 3, 4, 7 out of 8
104
+ const kickPattern = [0, 3, 4, 7]; // 8th-note positions
105
+ for (let bar = 0; bar < 4; bar++) {
106
+ kickPattern.forEach((pos) => {
107
+ const t = 2.0 + bar * eighth * 8 + pos * eighth;
108
+ if (t < 12) kick.triggerAttackRelease('C1', '8n', t);
109
+ });
110
+ }
111
+
112
+ // Snare: beats 2 and 4 but slightly swung (pushed ~30ms late)
113
+ const snareSwing = 0.03;
114
+ const snarePattern = [1, 3, 5, 7]; // 8th positions = beats 2, 4 etc
115
+ for (let bar = 0; bar < 4; bar++) {
116
+ snarePattern.filter((_, i) => i % 2 === 0).forEach((pos) => {
117
+ const t = 2.0 + bar * eighth * 8 + pos * eighth + snareSwing;
118
+ if (t < 12) snare.triggerAttackRelease('8n', t);
119
+ });
120
+ }
121
+
122
+ // Sparse hi-hat: 16th-note accents on the off-offbeats
123
+ const hihatPositions = [1, 5, 9, 13]; // 16th positions
124
+ for (let bar = 0; bar < 4; bar++) {
125
+ hihatPositions.forEach((pos) => {
126
+ const t = 2.0 + bar * sixteenth * 16 + pos * sixteenth;
127
+ if (t < 12) hihat.triggerAttackRelease('16n', t);
128
+ });
129
+ }
130
+
131
+ // Eerie melody: enters at 5s, D minor descending line
132
+ // D4 → C4 → Bb3 → A3 → G3 → A3 (minor descent, last note back up)
133
+ const melodyNotes = [
134
+ { note: 'D4', dur: '4n', t: 5.0 },
135
+ { note: 'C4', dur: '4n', t: 5.0 + beat },
136
+ { note: 'Bb3', dur: '4n', t: 5.0 + beat * 2 },
137
+ { note: 'A3', dur: '2n', t: 5.0 + beat * 3 },
138
+ { note: 'G3', dur: '4n', t: 5.0 + beat * 5 },
139
+ { note: 'A3', dur: '2n', t: 5.0 + beat * 6 },
140
+ { note: 'D4', dur: '4n', t: 5.0 + beat * 8 },
141
+ { note: 'C4', dur: '4n', t: 5.0 + beat * 9 },
142
+ ];
143
+ melodyNotes.forEach(({ note, dur, t }) => {
144
+ if (t < 12) lead.triggerAttackRelease(note, dur, t);
145
+ });
146
+ }
147
+
148
+ window.renderComposition = async function(wavPath) {
149
+ let attempts = 0;
150
+ while (typeof Tone === 'undefined' && attempts < 50) {
151
+ await new Promise(r => setTimeout(r, 100));
152
+ attempts++;
153
+ }
154
+ if (typeof Tone === 'undefined') throw new Error('Tone not loaded');
155
+
156
+ const meta = JSON.parse(document.getElementById('tuneframes').textContent);
157
+ const durationSec = parseFloat(meta.duration) + 1.5;
158
+
159
+ const buffer = await Tone.Offline(async () => { await main(); }, durationSec, 1, 44100);
160
+ const wav = audioBufferToWav(buffer);
161
+ window.writeFile(wavPath, Array.from(new Uint8Array(wav)));
162
+ return wav.byteLength;
163
+ };
164
+ </script>
@@ -0,0 +1,139 @@
1
+ ---
2
+ name: audio-folk
3
+ description: Folk / Acoustic — fingerpicked guitar simulation (PolySynth pluck, alternating bass + chord tones), simple melodic line, bass root on downbeats, optional brushed snare. C/G/D major. Warm, wooden, human. Simon & Garfunkel simplicity.
4
+ ---
5
+
6
+ # TuneFrames — Folk / Acoustic
7
+
8
+ ## Genre Profile
9
+ - BPM range: 80–110 (comfortable walking pace)
10
+ - Key characteristics: Fingerpicking simulation via PolySynth with very fast attack, short decay, near-zero sustain (pluck envelope); thumb alternates root/5th bass notes while fingers pluck upper chord tones in between; simple diatonic melody; warm reverb, no chorus; near-dry bass; optional soft brushed snare
11
+ - Typical instruments: PolySynth/Synth (acoustic guitar pluck), Synth (bass), optional NoiseSynth (brushed snare)
12
+ - Mood: Intimate, warm, storytelling, human, wooden, unadorned
13
+
14
+ ## Core Pattern
15
+
16
+ ```js
17
+ // Folk fingerpicking — 92 BPM, G major
18
+ // Travis picking: thumb alternates G2/D2 (root/5th), fingers pluck upper chord tones
19
+ // Chord progression: G – C – D – Em (4-bar loop)
20
+
21
+ Tone.Transport.bpm.value = 92;
22
+
23
+ // Acoustic pluck: triangle oscillator, very short decay, no sustain
24
+ const guitarVerb = new Tone.Reverb({ decay: 1.5, wet: 0.2 }).toDestination();
25
+ const guitar = new Tone.PolySynth(Tone.Synth, {
26
+ oscillator: { type: "triangle" },
27
+ envelope: { attack: 0.003, decay: 0.22, sustain: 0.0, release: 0.5 },
28
+ volume: -6
29
+ }).connect(guitarVerb);
30
+
31
+ // Travis picking pattern for G major (8th notes, 1 bar):
32
+ // G2 (thumb-root), B3 (finger), D2 (thumb-5th), G3 (finger),
33
+ // G2 (thumb-root), D3 (finger), D2 (thumb-5th), B3 (finger)
34
+ const gBar = [
35
+ ["0:0:0","G2"], ["0:0:2","B3"], ["0:1:0","D2"], ["0:1:2","G3"],
36
+ ["0:2:0","G2"], ["0:2:2","D3"], ["0:3:0","D2"], ["0:3:2","B3"],
37
+ ];
38
+
39
+ const cBar = [
40
+ ["1:0:0","C2"], ["1:0:2","E3"], ["1:1:0","G2"], ["1:1:2","C3"],
41
+ ["1:2:0","C2"], ["1:2:2","G3"], ["1:3:0","G2"], ["1:3:2","E3"],
42
+ ];
43
+
44
+ const dBar = [
45
+ ["2:0:0","D2"], ["2:0:2","F#3"], ["2:1:0","A2"], ["2:1:2","D3"],
46
+ ["2:2:0","D2"], ["2:2:2","A3"], ["2:3:0","A2"], ["2:3:2","F#3"],
47
+ ];
48
+
49
+ const emBar = [
50
+ ["3:0:0","E2"], ["3:0:2","G3"], ["3:1:0","B2"], ["3:1:2","E3"],
51
+ ["3:2:0","E2"], ["3:2:2","B3"], ["3:3:0","B2"], ["3:3:2","G3"],
52
+ ];
53
+
54
+ const arpPart = new Tone.Part((time, note) => {
55
+ guitar.triggerAttackRelease(note, "8n", time);
56
+ }, [...gBar, ...cBar, ...dBar, ...emBar]);
57
+ arpPart.loopEnd = "4m";
58
+ arpPart.loop = true;
59
+ arpPart.start(0);
60
+
61
+ Tone.Transport.start();
62
+ ```
63
+
64
+ ## Instrument Configuration
65
+
66
+ ```js
67
+ // Acoustic guitar pluck — triangle oscillator, zero sustain is critical
68
+ const guitarVerb = new Tone.Reverb({ decay: 1.4, wet: 0.18 }).toDestination();
69
+ const guitar = new Tone.PolySynth(Tone.Synth, {
70
+ oscillator: { type: "triangle" },
71
+ envelope: { attack: 0.003, decay: 0.22, sustain: 0.0, release: 0.5 },
72
+ volume: -6
73
+ }).connect(guitarVerb);
74
+ // Note: sustain: 0.0 is what makes it "pluck" instead of "pad"
75
+ // Increase decay (0.3–0.5) for a slower, more resonant string feel
76
+
77
+ // Bass — sine oscillator, warm, simple root notes
78
+ const bass = new Tone.Synth({
79
+ oscillator: { type: "sine" },
80
+ envelope: { attack: 0.04, decay: 0.15, sustain: 0.6, release: 0.4 },
81
+ volume: -4
82
+ }).toDestination();
83
+
84
+ // Optional: brushed snare (very soft white noise, quiet)
85
+ const brushVerb = new Tone.Reverb({ decay: 0.6, wet: 0.3 }).toDestination();
86
+ const brushSnare = new Tone.NoiseSynth({
87
+ noise: { type: "white" },
88
+ envelope: { attack: 0.01, decay: 0.08, sustain: 0, release: 0.04 },
89
+ volume: -22 // very quiet — just a whisper of rhythm
90
+ }).connect(brushVerb);
91
+
92
+ // Optional: simple melody line (same pluck synth, higher octave)
93
+ const melody = new Tone.Synth({
94
+ oscillator: { type: "triangle" },
95
+ envelope: { attack: 0.005, decay: 0.3, sustain: 0.2, release: 0.6 },
96
+ volume: -8
97
+ }).connect(guitarVerb); // shares reverb with guitar
98
+ ```
99
+
100
+ ## Composition Structure
101
+
102
+ - **Bars 1–2:** Guitar fingerpicking alone — just the pattern, completely bare, let it breathe
103
+ - **Bars 3–4:** Add bass root notes on beat 1 of each bar — minimal, subtle
104
+ - **Bars 5–8:** Melody enters over the fingerpicking — simple 8th-note lines, diatonic
105
+ - **Bars 9–16:** Optional brushed snare very quietly on beats 2 and 4
106
+ - **Variation:** Pick one chord per 2 bars instead of 1 for a slower, more hymn-like feel
107
+ - **Outro:** Slow down BPM (use Transport.bpm ramp), fingerpicking thins to just root notes
108
+
109
+ Harmonic vocabulary: Stay strictly diatonic. G major scale only (G A B C D E F#). Common progressions: G–C–D–G (I–IV–V–I), G–Em–C–D (I–vi–IV–V), G–D–Em–C (the "axis" — works everywhere).
110
+
111
+ ## Example Variations
112
+
113
+ ### 1 — Capo feel (higher key)
114
+ ```js
115
+ // Transpose all notes up a perfect 4th (G → C, like a capo at fret 5)
116
+ // G major → C major: C D E F G A B
117
+ const gBar_capo = [
118
+ ["0:0:0","C3"], ["0:0:2","E4"], ["0:1:0","G2"], ["0:1:2","C4"],
119
+ ["0:2:0","C3"], ["0:2:2","G3"], ["0:3:0","G2"], ["0:3:2","E4"],
120
+ ];
121
+ ```
122
+
123
+ ### 2 — Arpeggio (no alternating bass, just chord tones ascending)
124
+ ```js
125
+ // Simpler pattern: just arpeggiate each chord upward
126
+ const gArp = ["G2","B2","D3","G3","D3","B2","G2","B2"];
127
+ let step = 0;
128
+ new Tone.Sequence((time) => {
129
+ guitar.triggerAttackRelease(gArp[step++ % gArp.length], "8n", time);
130
+ }, new Array(8).fill(1), "8n").start(0);
131
+ ```
132
+
133
+ ### 3 — Slower "hymn" tempo with whole-note chord pads
134
+ ```js
135
+ // One chord per 2 bars at 72 BPM — more contemplative
136
+ Tone.Transport.bpm.value = 72;
137
+ pad.triggerAttackRelease(["G3","B3","D4"], "2m", "0m");
138
+ pad.triggerAttackRelease(["C3","E3","G3"], "2m", "2m");
139
+ ```
@@ -0,0 +1,132 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>TuneFrames — Folk / Acoustic</title>
6
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
7
+ </head>
8
+ <body>
9
+ <!-- TuneFrames Metadata -->
10
+ <div id="tuneframes" style="display:none">{"bpm":92,"duration":"12s"}</div>
11
+
12
+ <script>
13
+ async function main() {
14
+ await Tone.start();
15
+ Tone.Transport.bpm.value = 92;
16
+
17
+ // --- MASTER BUS ---
18
+ const masterLimiter = new Tone.Limiter(-1).toDestination();
19
+ const masterGain = new Tone.Gain(0.88).connect(masterLimiter);
20
+
21
+ // --- GUITAR — triangle pluck, very short decay, near-zero sustain ---
22
+ const guitarVerb = new Tone.Reverb({ decay: 1.4, wet: 0.18 }).connect(masterGain);
23
+ await guitarVerb.ready;
24
+ const guitarGain = new Tone.Gain(0.65).connect(guitarVerb);
25
+ const guitar = new Tone.PolySynth(Tone.Synth, {
26
+ oscillator: { type: "triangle" },
27
+ envelope: { attack: 0.003, decay: 0.22, sustain: 0.0, release: 0.5 },
28
+ volume: 0
29
+ }).connect(guitarGain);
30
+
31
+ // --- MELODY — slightly warmer pluck, higher register ---
32
+ const melodyGain = new Tone.Gain(0.45).connect(guitarVerb); // shares reverb
33
+ const melody = new Tone.Synth({
34
+ oscillator: { type: "triangle" },
35
+ envelope: { attack: 0.005, decay: 0.3, sustain: 0.15, release: 0.6 },
36
+ volume: 0
37
+ }).connect(melodyGain);
38
+
39
+ // --- BASS — warm sine, root notes only ---
40
+ const bassGain = new Tone.Gain(0.55).connect(masterGain);
41
+ const bass = new Tone.Synth({
42
+ oscillator: { type: "sine" },
43
+ envelope: { attack: 0.04, decay: 0.15, sustain: 0.6, release: 0.4 },
44
+ volume: 0
45
+ }).connect(bassGain);
46
+
47
+ // --- BRUSHED SNARE — barely there, just rhythm shape ---
48
+ const brushVerb = new Tone.Reverb({ decay: 0.6, wet: 0.3 }).connect(masterGain);
49
+ await brushVerb.ready;
50
+ const brushGain = new Tone.Gain(0.12).connect(brushVerb);
51
+ const brush = new Tone.NoiseSynth({
52
+ noise: { type: "white" },
53
+ envelope: { attack: 0.01, decay: 0.08, sustain: 0, release: 0.04 }
54
+ }).connect(brushGain);
55
+
56
+ // --- TRAVIS PICKING — 4-bar G–C–D–Em loop ---
57
+ // Each bar: thumb on root/5th (low notes), finger on upper chord tones
58
+ // 8th note grid: bass note, chord tone, bass note, chord tone...
59
+ const pickingPattern = [
60
+ // Bar 0: G major — G2(thumb), B3(finger), D2(thumb-5th), G3(finger)...
61
+ ["0:0:0","G2"], ["0:0:2","B3"], ["0:1:0","D2"], ["0:1:2","G3"],
62
+ ["0:2:0","G2"], ["0:2:2","D3"], ["0:3:0","D2"], ["0:3:2","B3"],
63
+ // Bar 1: C major — C2(thumb), E3(finger), G2(thumb-5th), C3(finger)...
64
+ ["1:0:0","C2"], ["1:0:2","E3"], ["1:1:0","G2"], ["1:1:2","C3"],
65
+ ["1:2:0","C2"], ["1:2:2","G3"], ["1:3:0","G2"], ["1:3:2","E3"],
66
+ // Bar 2: D major — D2(thumb), F#3(finger), A2(thumb-5th), D3(finger)...
67
+ ["2:0:0","D2"], ["2:0:2","F#3"], ["2:1:0","A2"], ["2:1:2","D3"],
68
+ ["2:2:0","D2"], ["2:2:2","A3"], ["2:3:0","A2"], ["2:3:2","F#3"],
69
+ // Bar 3: E minor — E2(thumb), G3(finger), B2(thumb-5th), E3(finger)...
70
+ ["3:0:0","E2"], ["3:0:2","G3"], ["3:1:0","B2"], ["3:1:2","E3"],
71
+ ["3:2:0","E2"], ["3:2:2","B3"], ["3:3:0","B2"], ["3:3:2","G3"],
72
+ ];
73
+ const pickPart = new Tone.Part((time, note) => {
74
+ guitar.triggerAttackRelease(note, "8n", time);
75
+ }, pickingPattern);
76
+ pickPart.loopEnd = "4m";
77
+ pickPart.loop = true;
78
+ pickPart.start(0);
79
+
80
+ // --- BASS — root on beat 1 of each bar, enters bar 2 ---
81
+ const bassPart = new Tone.Part((time, note) => {
82
+ bass.triggerAttackRelease(note, "2n", time);
83
+ }, [
84
+ ["0:0:0","G2"],
85
+ ["1:0:0","C2"],
86
+ ["2:0:0","D2"],
87
+ ["3:0:0","E2"],
88
+ ]);
89
+ bassPart.loopEnd = "4m";
90
+ bassPart.loop = true;
91
+ bassPart.start("1m"); // bass enters bar 2
92
+
93
+ // --- MELODY — simple diatonic line over the chord progression, enters bar 3 ---
94
+ const melodyNotes = [
95
+ // Over G: D4 – B3 – G3 – A3
96
+ { time: "0:0:0", note: "D4", dur: "4n" },
97
+ { time: "0:1:0", note: "B3", dur: "4n" },
98
+ { time: "0:2:0", note: "G3", dur: "4n" },
99
+ { time: "0:3:0", note: "A3", dur: "4n" },
100
+ // Over C: G3 – E3 – C4 – D4
101
+ { time: "1:0:0", note: "G3", dur: "4n" },
102
+ { time: "1:1:0", note: "E3", dur: "4n" },
103
+ { time: "1:2:0", note: "C4", dur: "2n" },
104
+ // Over D: D4 – F#3 – A3 – G3
105
+ { time: "2:0:0", note: "D4", dur: "4n" },
106
+ { time: "2:1:0", note: "F#3", dur: "4n" },
107
+ { time: "2:2:0", note: "A3", dur: "4n" },
108
+ { time: "2:3:0", note: "G3", dur: "4n" },
109
+ // Over Em: E3 – G3 – B3 – G3
110
+ { time: "3:0:0", note: "E3", dur: "4n" },
111
+ { time: "3:1:0", note: "G3", dur: "4n" },
112
+ { time: "3:2:0", note: "B3", dur: "2n" },
113
+ ];
114
+ const melodyPart = new Tone.Part((time, val) => {
115
+ melody.triggerAttackRelease(val.note, val.dur, time);
116
+ }, melodyNotes.map(({ time, note, dur }) => [time, { note, dur }]));
117
+ melodyPart.loopEnd = "4m";
118
+ melodyPart.loop = true;
119
+ melodyPart.start("2m"); // melody enters bar 3
120
+
121
+ // --- BRUSHED SNARE — beats 2 and 4, very quiet, enters bar 3 ---
122
+ const brushSeq = new Tone.Sequence((time, val) => {
123
+ if (val) brush.triggerAttackRelease("8n", time);
124
+ }, [null,null,null,null,1,null,null,null,null,null,null,null,1,null,null,null], "16n");
125
+ brushSeq.start("2m");
126
+
127
+ }
128
+
129
+ main();
130
+ </script>
131
+ </body>
132
+ </html>