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
@@ -0,0 +1,141 @@
1
+ ---
2
+ name: audio-jazz
3
+ description: Jazz — ii-V-I changes, extended chords, walking bass, swing feel, bebop melody
4
+ ---
5
+
6
+ # TuneFrames — Jazz
7
+
8
+ ## Genre Profile
9
+ - BPM range: 100–180 (ballad 60–90, bebop 180–240)
10
+ - Key characteristics: ii-V-I progressions, extended/altered chords (maj7, min7, dom7, dim7), swing 8th notes, call-and-response phrasing
11
+ - Typical instruments: Piano (PolySynth), walking upright bass (Synth sine), ride cymbal (MetalSynth), brush snare (NoiseSynth), optional vibraphone
12
+ - Mood: Sophisticated, improvisatory, warm, intellectually alive
13
+
14
+ ## Core Pattern
15
+
16
+ ```js
17
+ // Jazz core: 120 BPM swing, Dm7–G7–Cmaj7 (ii-V-I in C major)
18
+ async function main() {
19
+ await Tone.start();
20
+ Tone.Transport.bpm.value = 120;
21
+
22
+ const reverb = new Tone.Reverb({ decay: 1.8, wet: 0.3 }).toDestination();
23
+
24
+ // ── Piano voicings (shell voicings: root + 3rd + 7th) ─────────────────
25
+ const piano = new Tone.PolySynth(Tone.Synth, {
26
+ oscillator: { type: 'triangle' },
27
+ envelope: { attack: 0.01, decay: 0.4, sustain: 0.5, release: 1.2 }
28
+ }).connect(reverb);
29
+ piano.volume.value = -10;
30
+
31
+ const bar = Tone.Time('1n').toSeconds(); // 1 bar in seconds
32
+
33
+ // ii–V–I–I in C (shell voicings)
34
+ const changes = [
35
+ ['D3','F3','C4'], // Dm7 (ii)
36
+ ['G3','B3','F4'], // G7 (V)
37
+ ['C3','E3','B3'], // Cmaj7 (I)
38
+ ['C3','E3','G3','B3'], // Cmaj7 full (I hold)
39
+ ];
40
+ changes.forEach((ch, i) => piano.triggerAttackRelease(ch, '1n', i * bar));
41
+
42
+ // ── Walking bass (quarter notes, chromatic approach notes) ────────────
43
+ const bass = new Tone.Synth({
44
+ oscillator: { type: 'sine' },
45
+ envelope: { attack: 0.01, decay: 0.1, sustain: 0.6, release: 0.2 }
46
+ }).connect(reverb);
47
+ bass.volume.value = -8;
48
+
49
+ // Classic walking line: D–F–A–C / G–B–D–F / C–E–G–B / C–E–G–C
50
+ const walk = ['D2','F2','A2','C3','G2','B2','D3','F3','C2','E2','G2','B2','C2','E2','G2','C3'];
51
+ const q = Tone.Time('4n').toSeconds();
52
+ walk.forEach((n, i) => bass.triggerAttackRelease(n, '4n', i * q));
53
+
54
+ // ── Ride cymbal (swing pattern: 1–2–ah–3–4–ah) ────────────────────────
55
+ const ride = new Tone.MetalSynth({
56
+ frequency: 250, harmonicity: 5.1, modulationIndex: 16,
57
+ resonance: 4000, octaves: 1.5,
58
+ envelope: { attack: 0.001, decay: 0.3, release: 0.1 }
59
+ }).connect(reverb);
60
+ ride.volume.value = -22;
61
+
62
+ // Swing ride: beats 1, 2 triplet-late, 3, 4 triplet-late
63
+ const swingOffset = q * 0.67; // triplet-swing "ah"
64
+ for (let i = 0; i < 4; i++) {
65
+ ride.triggerAttackRelease('8n', i * bar);
66
+ ride.triggerAttackRelease('8n', i * bar + swingOffset);
67
+ ride.triggerAttackRelease('8n', i * bar + q * 2);
68
+ ride.triggerAttackRelease('8n', i * bar + q * 2 + swingOffset);
69
+ }
70
+ }
71
+ ```
72
+
73
+ ## Instrument Configuration
74
+
75
+ ```js
76
+ // Jazz piano — triangle wave (mellow, not harsh), moderate decay
77
+ const piano = new Tone.PolySynth(Tone.Synth, {
78
+ oscillator: { type: 'triangle' },
79
+ envelope: { attack: 0.01, decay: 0.4, sustain: 0.5, release: 1.2 }
80
+ });
81
+
82
+ // Upright bass feel — pure sine, tight decay, no sustain tail
83
+ const bass = new Tone.Synth({
84
+ oscillator: { type: 'sine' },
85
+ envelope: { attack: 0.01, decay: 0.08, sustain: 0.55, release: 0.15 }
86
+ });
87
+
88
+ // Ride cymbal — low frequency MetalSynth, long decay
89
+ const ride = new Tone.MetalSynth({
90
+ frequency: 250, harmonicity: 5.1, modulationIndex: 16,
91
+ resonance: 4000, octaves: 1.5,
92
+ envelope: { attack: 0.001, decay: 0.3, release: 0.1 }
93
+ });
94
+
95
+ // Brush snare — bandpass-filtered white noise
96
+ const snare = new Tone.NoiseSynth({
97
+ noise: { type: 'white' },
98
+ envelope: { attack: 0.005, decay: 0.1, sustain: 0, release: 0.05 }
99
+ });
100
+ const snareFilter = new Tone.Filter(2500, 'bandpass');
101
+ snare.connect(snareFilter);
102
+
103
+ // Room reverb — short, warm
104
+ const reverb = new Tone.Reverb({ decay: 1.8, wet: 0.3 }).toDestination();
105
+ ```
106
+
107
+ ## Composition Structure
108
+
109
+ 1. **Head in (0–4s):** Piano states the changes, bass walks, ride establishes swing
110
+ 2. **Melody chorus (4–8s):** Bebop-style single-note melody over the changes (arpeggiated +chromatic passing tones)
111
+ 3. **Reharmonization (8–12s):** Tritone sub on the V chord (Db7 instead of G7), piano plays richer voicings
112
+ 4. **Tag / turnaround:** Quick ii-V back to the top, ritardando on the final bar
113
+
114
+ ## Example Variations
115
+
116
+ ### 1 — Tritone substitution on V (G7 → Db7)
117
+ ```js
118
+ // Replace G7 shell with Db7 shell (tritone sub)
119
+ const changes = [
120
+ ['D3','F3','C4'], // Dm7
121
+ ['Db3','F3','B3'], // Db7 (tritone sub for G7)
122
+ ['C3','E3','B3'], // Cmaj7
123
+ ];
124
+ ```
125
+
126
+ ### 2 — Ballad tempo (70 BPM) with rubato melody
127
+ ```js
128
+ Tone.Transport.bpm.value = 70;
129
+ // Hold each chord for 2 bars, melody uses longer note values ('2n', '1n')
130
+ ```
131
+
132
+ ### 3 — Vibraphone color (add shimmer on the I chord)
133
+ ```js
134
+ const vibe = new Tone.PolySynth(Tone.Synth, {
135
+ oscillator: { type: 'sine' },
136
+ envelope: { attack: 0.005, decay: 1.5, sustain: 0.2, release: 2.0 }
137
+ });
138
+ const vibeVib = new Tone.Vibrato({ frequency: 5, depth: 0.05 }).connect(reverb);
139
+ vibe.connect(vibeVib);
140
+ vibe.triggerAttackRelease(['C4','E4','G4','B4'], '2n', barStart);
141
+ ```
@@ -0,0 +1,146 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>TuneFrames — Jazz</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":132,"duration":"12s"}</div>
9
+ <script>
10
+ async function main() {
11
+ await Tone.start();
12
+ Tone.Transport.bpm.value = 132;
13
+
14
+ // ── Effects ─────────────────────────────────────────────────────────
15
+ const reverb = new Tone.Reverb({ decay: 1.6, preDelay: 0.01, wet: 0.28 }).toDestination();
16
+ await reverb.ready;
17
+
18
+ // ── Piano — shell voicings (3rd + 7th on top) ───────────────────────
19
+ const piano = new Tone.PolySynth(Tone.Synth, {
20
+ oscillator: { type: 'triangle' },
21
+ envelope: { attack: 0.008, decay: 0.35, sustain: 0.5, release: 1.4 }
22
+ }).connect(reverb);
23
+ piano.volume.value = -10;
24
+
25
+ const bar = Tone.Time('1n').toSeconds();
26
+ const q = Tone.Time('4n').toSeconds();
27
+
28
+ // ii–V–I–vi turnaround in C major (2 full passes = 8 bars)
29
+ // Bar layout: Dm7 | G7 | Cmaj7 | Am7 | repeat
30
+ const changes = [
31
+ { chord: ['D3','F3','C4'], dur: '1n' }, // Dm7
32
+ { chord: ['G2','B2','F3'], dur: '1n' }, // G7
33
+ { chord: ['C3','E3','B3'], dur: '1n' }, // Cmaj7
34
+ { chord: ['A2','C3','G3','E4'], dur: '1n' }, // Am7
35
+ { chord: ['D3','F3','C4'], dur: '1n' }, // Dm7 (repeat)
36
+ { chord: ['Db3','F3','B3'], dur: '1n' }, // Db7 (tritone sub)
37
+ { chord: ['C3','E3','B3','G4'], dur: '2n' }, // Cmaj7
38
+ { chord: ['G2','B2','F3'], dur: '2n' }, // G7 (turnaround)
39
+ ];
40
+ let t = 0;
41
+ changes.forEach(({ chord, dur }) => {
42
+ piano.triggerAttackRelease(chord, dur, t);
43
+ t += Tone.Time(dur).toSeconds();
44
+ });
45
+
46
+ // ── Walking bass ────────────────────────────────────────────────────
47
+ const bass = new Tone.Synth({
48
+ oscillator: { type: 'sine' },
49
+ envelope: { attack: 0.008, decay: 0.09, sustain: 0.55, release: 0.15 }
50
+ }).connect(reverb);
51
+ bass.volume.value = -6;
52
+
53
+ // 8-bar walking line (one note per beat = 32 quarter notes)
54
+ const walkLine = [
55
+ // Bar 1 Dm7
56
+ 'D2','F2','A2','C3',
57
+ // Bar 2 G7
58
+ 'G2','B2','D3','F3',
59
+ // Bar 3 Cmaj7
60
+ 'C3','E2','G2','B2',
61
+ // Bar 4 Am7
62
+ 'A2','C3','E3','G2',
63
+ // Bar 5 Dm7 (chromatic approach)
64
+ 'D2','Eb2','E2','F2',
65
+ // Bar 6 Db7 (tritone)
66
+ 'Db3','Ab2','F2','Eb2',
67
+ // Bar 7 Cmaj7
68
+ 'C2','G2','E2','B2',
69
+ // Bar 8 G7
70
+ 'G2','Ab2','A2','B2',
71
+ ];
72
+ walkLine.forEach((n, i) => bass.triggerAttackRelease(n, '4n', i * q));
73
+
74
+ // ── Ride cymbal (jazz swing pattern) ────────────────────────────────
75
+ const ride = new Tone.MetalSynth({
76
+ frequency: 240, harmonicity: 5.1, modulationIndex: 16,
77
+ resonance: 4000, octaves: 1.5,
78
+ envelope: { attack: 0.001, decay: 0.28, release: 0.08 }
79
+ }).connect(reverb);
80
+ ride.volume.value = -22;
81
+
82
+ // Swing triplet feel: beat 1, beat 2-and (triplet late), beat 3, beat 4-and
83
+ const swingLate = q * 0.67;
84
+ for (let b = 0; b < 8; b++) {
85
+ const base = b * bar;
86
+ ride.triggerAttackRelease('8n', base);
87
+ ride.triggerAttackRelease('8n', base + swingLate);
88
+ ride.triggerAttackRelease('8n', base + q * 2);
89
+ ride.triggerAttackRelease('8n', base + q * 2 + swingLate);
90
+ }
91
+
92
+ // ── Brush snare on 2 and 4 ──────────────────────────────────────────
93
+ const snareBp = new Tone.Filter(2800, 'bandpass').connect(reverb);
94
+ const snare = new Tone.NoiseSynth({
95
+ noise: { type: 'white' },
96
+ envelope: { attack: 0.004, decay: 0.09, sustain: 0, release: 0.04 }
97
+ }).connect(snareBp);
98
+ snare.volume.value = -26;
99
+
100
+ for (let b = 0; b < 8; b++) {
101
+ snare.triggerAttackRelease('8n', b * bar + q); // beat 2
102
+ snare.triggerAttackRelease('8n', b * bar + q * 3); // beat 4
103
+ }
104
+
105
+ // ── Bebop melody (enters bar 3) ─────────────────────────────────────
106
+ const melody = new Tone.Synth({
107
+ oscillator: { type: 'triangle' },
108
+ envelope: { attack: 0.005, decay: 0.15, sustain: 0.4, release: 0.5 }
109
+ }).connect(reverb);
110
+ melody.volume.value = -12;
111
+
112
+ // Classic bebop phrase: arpeggiated chords + chromatic passing tones
113
+ const phrase = [
114
+ // Over Cmaj7 (bar 3)
115
+ { note: 'G4', time: bar * 2, dur: '8n' },
116
+ { note: 'E4', time: bar * 2 + q * 0.5, dur: '8n' },
117
+ { note: 'C5', time: bar * 2 + q, dur: '8n' },
118
+ { note: 'B4', time: bar * 2 + q * 1.5, dur: '8n' },
119
+ { note: 'A4', time: bar * 2 + q * 2, dur: '8n' },
120
+ { note: 'G4', time: bar * 2 + q * 2.5, dur: '8n' },
121
+ { note: 'F4', time: bar * 2 + q * 3, dur: '4n' },
122
+ // Over Am7 (bar 4)
123
+ { note: 'E4', time: bar * 3 + q * 0.5, dur: '8n' },
124
+ { note: 'C5', time: bar * 3 + q, dur: '8n' },
125
+ { note: 'B4', time: bar * 3 + q * 1.5, dur: '8n' },
126
+ { note: 'A4', time: bar * 3 + q * 2, dur: '4n' },
127
+ { note: 'G4', time: bar * 3 + q * 3, dur: '4n' },
128
+ // Over Dm7 again (bar 5)
129
+ { note: 'D5', time: bar * 4, dur: '8n' },
130
+ { note: 'C5', time: bar * 4 + q * 0.5, dur: '8n' },
131
+ { note: 'A4', time: bar * 4 + q, dur: '8n' },
132
+ { note: 'F4', time: bar * 4 + q * 1.5, dur: '8n' },
133
+ { note: 'E4', time: bar * 4 + q * 2, dur: '4n' },
134
+ { note: 'Eb4', time: bar * 4 + q * 3, dur: '8n' }, // chromatic
135
+ { note: 'D4', time: bar * 4 + q * 3.5, dur: '8n' },
136
+ // Over Db7 (bar 6) — altered sound
137
+ { note: 'Ab4', time: bar * 5, dur: '8n' },
138
+ { note: 'F4', time: bar * 5 + q * 0.5, dur: '8n' },
139
+ { note: 'Db4', time: bar * 5 + q, dur: '4n' },
140
+ { note: 'C4', time: bar * 5 + q * 2, dur: '2n' }, // resolve to C
141
+ ];
142
+ phrase.forEach(({ note, time, dur }) => melody.triggerAttackRelease(note, dur, time));
143
+ }
144
+ </script>
145
+ </body>
146
+ </html>
@@ -0,0 +1,140 @@
1
+ ---
2
+ name: audio-lofi
3
+ description: Lo-fi Hip Hop — dusty jazzy chords, soft keys, muffled drums, chill study vibe
4
+ ---
5
+
6
+ # TuneFrames — Lo-fi Hip Hop
7
+
8
+ ## Genre Profile
9
+ - BPM range: 70–90
10
+ - Key characteristics: Jazz-adjacent chord progressions (Am–F–C–G or ii–V–I extensions), swung/lazy 8th-note feel, heavy reverb + slight LP filter on everything, vinyl warmth
11
+ - Typical instruments: Rhodes/electric piano (PolySynth triangle/sine), soft bass (Synth), MembraneSynth kick filtered low, closed hi-hat (MetalSynth), pad layer
12
+ - Mood: Nostalgic, cozy, introspective, study-focus
13
+
14
+ ## Core Pattern
15
+
16
+ ```js
17
+ // Lo-fi core: 80 BPM, Am–F–C–G, 4 chords × 2 beats each
18
+ async function main() {
19
+ await Tone.start();
20
+ Tone.Transport.bpm.value = 80;
21
+
22
+ // ── Effects chain (everything runs warm and muffled) ──────────────────
23
+ const reverb = new Tone.Reverb({ decay: 3.5, wet: 0.55 }).toDestination();
24
+ const lpFilter = new Tone.Filter(1800, 'lowpass').connect(reverb);
25
+ // Optional: slight tape distortion
26
+ const warm = new Tone.Distortion(0.04).connect(lpFilter);
27
+
28
+ // ── Rhodes-style chords ───────────────────────────────────────────────
29
+ const keys = new Tone.PolySynth(Tone.Synth, {
30
+ oscillator: { type: 'triangle' },
31
+ envelope: { attack: 0.04, decay: 0.3, sustain: 0.7, release: 1.8 }
32
+ }).connect(warm);
33
+ keys.volume.value = -10;
34
+
35
+ // ii–V–I–VI in A minor
36
+ const chords = [
37
+ ['A3','C4','E4'], // Am
38
+ ['F3','A3','C4'], // F maj
39
+ ['C3','E3','G3'], // C maj
40
+ ['G3','B3','D4'], // G maj
41
+ ];
42
+ const step = Tone.Time('2n').toSeconds(); // 2 beats each chord
43
+ chords.forEach((ch, i) => keys.triggerAttackRelease(ch, '2n', i * step));
44
+
45
+ // ── Walking bass ──────────────────────────────────────────────────────
46
+ const bass = new Tone.Synth({
47
+ oscillator: { type: 'sine' },
48
+ envelope: { attack: 0.02, decay: 0.1, sustain: 0.5, release: 0.4 }
49
+ }).connect(lpFilter);
50
+ bass.volume.value = -8;
51
+
52
+ const bassNotes = ['A2', 'F2', 'C2', 'G2'];
53
+ bassNotes.forEach((n, i) => bass.triggerAttackRelease(n, '4n', i * step));
54
+
55
+ // ── Muffled kick (MembraneSynth + deep LP) ────────────────────────────
56
+ const kickFilter = new Tone.Filter(200, 'lowpass').connect(reverb);
57
+ const kick = new Tone.MembraneSynth({
58
+ pitchDecay: 0.05, octaves: 5,
59
+ envelope: { attack: 0.001, decay: 0.4, sustain: 0, release: 0.4 }
60
+ }).connect(kickFilter);
61
+ kick.volume.value = -14;
62
+
63
+ // Kick on beats 1 and 3 (seconds at 80 BPM: beat = 0.75s)
64
+ const beat = 60 / 80;
65
+ [0, beat * 2, beat * 4, beat * 6].forEach(t => kick.triggerAttackRelease('C1', '8n', t));
66
+
67
+ // ── Soft hi-hat ───────────────────────────────────────────────────────
68
+ const hat = new Tone.MetalSynth({
69
+ frequency: 400, envelope: { attack: 0.001, decay: 0.08, release: 0.01 },
70
+ harmonicity: 5.1, modulationIndex: 32, resonance: 4000, octaves: 1.5
71
+ }).connect(reverb);
72
+ hat.volume.value = -24;
73
+
74
+ for (let i = 0; i < 8; i++) {
75
+ hat.triggerAttackRelease('8n', i * beat * 0.5);
76
+ }
77
+ }
78
+ ```
79
+
80
+ ## Instrument Configuration
81
+
82
+ ```js
83
+ // Rhodes feel — triangle oscillator, slow attack, long release
84
+ const keys = new Tone.PolySynth(Tone.Synth, {
85
+ oscillator: { type: 'triangle' },
86
+ envelope: { attack: 0.04, decay: 0.3, sustain: 0.7, release: 1.8 }
87
+ });
88
+
89
+ // Tape warmth — very light distortion before the LP filter
90
+ const tape = new Tone.Distortion(0.04);
91
+ const lp = new Tone.Filter(1800, 'lowpass'); // cuts harsh highs
92
+
93
+ // Lush room reverb
94
+ const reverb = new Tone.Reverb({ decay: 3.5, preDelay: 0.01, wet: 0.55 });
95
+
96
+ // Chain: keys → tape → lp → reverb → destination
97
+ keys.connect(tape); tape.connect(lp); lp.connect(reverb); reverb.toDestination();
98
+
99
+ // Muffled kick — very tight low-pass (200 Hz)
100
+ const kickLp = new Tone.Filter(200, 'lowpass').toDestination();
101
+ const kick = new Tone.MembraneSynth({ pitchDecay: 0.05, octaves: 5 }).connect(kickLp);
102
+
103
+ // Airy hat — kept very quiet, short decay
104
+ const hat = new Tone.MetalSynth({ frequency: 400, envelope: { decay: 0.08 } });
105
+ hat.volume.value = -24;
106
+ hat.connect(reverb);
107
+ ```
108
+
109
+ ## Composition Structure
110
+
111
+ 1. **Intro (0–2s):** Keys enter alone with chord 1, bass follows on beat 2
112
+ 2. **Main loop (2–8s):** Full Am–F–C–G cycle, kick on 1+3, hat 8ths, bass walks roots
113
+ 3. **Variation (8–10s):** Add a simple melody fragment (C5–E5–G5–A5) over the progression
114
+ 4. **Outro:** Let chords ring out with long release, hat fades (volume ramp)
115
+
116
+ ## Example Variations
117
+
118
+ ### 1 — Jazzier chord voicings (7ths)
119
+ ```js
120
+ const chords = [
121
+ ['A3','C4','E4','G4'], // Am7
122
+ ['F3','A3','C4','E4'], // Fmaj7
123
+ ['C3','E3','G3','B3'], // Cmaj7
124
+ ['G3','B3','D4','F4'], // G7
125
+ ];
126
+ ```
127
+
128
+ ### 2 — Slower, dreamier (70 BPM + delay)
129
+ ```js
130
+ Tone.Transport.bpm.value = 70;
131
+ const delay = new Tone.FeedbackDelay('8n.', 0.3).connect(reverb);
132
+ keys.connect(delay); // dotted-8th echo
133
+ ```
134
+
135
+ ### 3 — Add a simple vinyl-crackle effect (noise burst)
136
+ ```js
137
+ const noise = new Tone.Noise('pink').connect(new Tone.Filter(600, 'lowpass').toDestination());
138
+ noise.volume.value = -40;
139
+ noise.start(0).stop('+10');
140
+ ```
@@ -0,0 +1,135 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>TuneFrames — Lo-fi Hip Hop</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":"12s"}</div>
9
+ <script>
10
+ async function main() {
11
+ await Tone.start();
12
+ Tone.Transport.bpm.value = 80;
13
+
14
+ // ── Effects chain ───────────────────────────────────────────────────
15
+ const reverb = new Tone.Reverb({ decay: 3.5, preDelay: 0.01, wet: 0.55 }).toDestination();
16
+ await reverb.ready;
17
+ const lpFilter = new Tone.Filter(1800, 'lowpass').connect(reverb);
18
+ const tape = new Tone.Distortion(0.04).connect(lpFilter);
19
+
20
+ // ── Soft pink noise (vinyl warmth) ──────────────────────────────────
21
+ const noiseLp = new Tone.Filter(600, 'lowpass').connect(reverb);
22
+ const vinylNoise = new Tone.Noise('pink').connect(noiseLp);
23
+ vinylNoise.volume.value = -42;
24
+ vinylNoise.start(0).stop('+12');
25
+
26
+ // ── Rhodes-style keys ───────────────────────────────────────────────
27
+ const keys = new Tone.PolySynth(Tone.Synth, {
28
+ oscillator: { type: 'triangle' },
29
+ envelope: { attack: 0.05, decay: 0.3, sustain: 0.7, release: 2.0 }
30
+ }).connect(tape);
31
+ keys.volume.value = -10;
32
+
33
+ // Am7 – Fmaj7 – Cmaj7 – G7
34
+ const chords = [
35
+ ['A3','C4','E4','G4'],
36
+ ['F3','A3','C4','E4'],
37
+ ['C3','E3','G3','B3'],
38
+ ['G3','B3','D4','F4'],
39
+ ];
40
+
41
+ // 2 beats per chord at 80 BPM = 1.5s per chord
42
+ const step = Tone.Time('2n').toSeconds();
43
+ chords.forEach((ch, i) => keys.triggerAttackRelease(ch, '2n', i * step));
44
+ // Second pass — loop the chords again from 6s
45
+ chords.forEach((ch, i) => keys.triggerAttackRelease(ch, '2n', 6 + i * step));
46
+
47
+ // ── Walking bass ────────────────────────────────────────────────────
48
+ const bassFilter = new Tone.Filter(600, 'lowpass').connect(reverb);
49
+ const bass = new Tone.Synth({
50
+ oscillator: { type: 'sine' },
51
+ envelope: { attack: 0.02, decay: 0.12, sustain: 0.5, release: 0.5 }
52
+ }).connect(bassFilter);
53
+ bass.volume.value = -6;
54
+
55
+ // Root + passing tone on each chord (quarter notes)
56
+ const bassLines = [
57
+ ['A2','C3'],
58
+ ['F2','G2'],
59
+ ['C2','E2'],
60
+ ['G2','A2'],
61
+ ];
62
+ bassLines.forEach(([root, pass], i) => {
63
+ bass.triggerAttackRelease(root, '4n', i * step);
64
+ bass.triggerAttackRelease(pass, '4n', i * step + step * 0.5);
65
+ });
66
+ bassLines.forEach(([root, pass], i) => {
67
+ bass.triggerAttackRelease(root, '4n', 6 + i * step);
68
+ bass.triggerAttackRelease(pass, '4n', 6 + i * step + step * 0.5);
69
+ });
70
+
71
+ // ── Muffled kick ────────────────────────────────────────────────────
72
+ const kickLp = new Tone.Filter(200, 'lowpass').connect(reverb);
73
+ const kick = new Tone.MembraneSynth({
74
+ pitchDecay: 0.05, octaves: 5,
75
+ envelope: { attack: 0.001, decay: 0.4, sustain: 0, release: 0.4 }
76
+ }).connect(kickLp);
77
+ kick.volume.value = -14;
78
+
79
+ const beat = 60 / 80; // 0.75s
80
+ // Kick on beats 1 and 3 of each bar (2 bars = 12 beats total)
81
+ for (let bar = 0; bar < 4; bar++) {
82
+ kick.triggerAttackRelease('C1', '8n', bar * beat * 4);
83
+ kick.triggerAttackRelease('C1', '8n', bar * beat * 4 + beat * 2);
84
+ }
85
+
86
+ // ── Snare (soft) ────────────────────────────────────────────────────
87
+ const snare = new Tone.NoiseSynth({
88
+ noise: { type: 'white' },
89
+ envelope: { attack: 0.001, decay: 0.12, sustain: 0, release: 0.05 }
90
+ }).connect(new Tone.Filter(3000, 'bandpass').connect(reverb));
91
+ snare.volume.value = -22;
92
+
93
+ // Snare on beats 2 and 4
94
+ for (let bar = 0; bar < 4; bar++) {
95
+ snare.triggerAttackRelease('8n', bar * beat * 4 + beat);
96
+ snare.triggerAttackRelease('8n', bar * beat * 4 + beat * 3);
97
+ }
98
+
99
+ // ── Soft hi-hat (swung 8ths) ────────────────────────────────────────
100
+ const hat = new Tone.MetalSynth({
101
+ frequency: 400,
102
+ envelope: { attack: 0.001, decay: 0.06, release: 0.01 },
103
+ harmonicity: 5.1, modulationIndex: 32, resonance: 4000, octaves: 1.5
104
+ }).connect(reverb);
105
+ hat.volume.value = -28;
106
+
107
+ // Swung 8th notes: on beat, then slightly late (lazy swing)
108
+ for (let i = 0; i < 16; i++) {
109
+ const swing = (i % 2 === 1) ? 0.05 : 0; // slight swing delay on offbeats
110
+ hat.triggerAttackRelease('16n', i * beat * 0.5 + swing);
111
+ }
112
+
113
+ // ── Melody fragment (enters bar 3, ~4.5s) ───────────────────────────
114
+ const melodyDelay = new Tone.FeedbackDelay({ delayTime: '8n', feedback: 0.25, wet: 0.3 }).connect(reverb);
115
+ const melody = new Tone.Synth({
116
+ oscillator: { type: 'sine' },
117
+ envelope: { attack: 0.02, decay: 0.2, sustain: 0.3, release: 0.8 }
118
+ }).connect(melodyDelay);
119
+ melody.volume.value = -16;
120
+
121
+ // Simple pentatonic phrase over bars 3–4 (starting at 4.5s)
122
+ const phrase = [
123
+ { note: 'E5', time: 4.5, dur: '8n' },
124
+ { note: 'C5', time: 5.0, dur: '8n' },
125
+ { note: 'A4', time: 5.5, dur: '4n' },
126
+ { note: 'G4', time: 6.5, dur: '8n' },
127
+ { note: 'A4', time: 7.0, dur: '4n' },
128
+ { note: 'E5', time: 7.75, dur: '8n' },
129
+ { note: 'C5', time: 8.25, dur: '2n' },
130
+ ];
131
+ phrase.forEach(({ note, time, dur }) => melody.triggerAttackRelease(note, dur, time));
132
+ }
133
+ </script>
134
+ </body>
135
+ </html>