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.
- package/LICENSE +167 -199
- package/README.md +150 -97
- package/examples/example-ai-dj-chill.html +167 -0
- package/examples/example-ai-dj-dark.html +167 -0
- package/examples/example-ai-dj-energetic.html +167 -0
- package/examples/example-ai-dj-happy.html +167 -0
- package/examples/example-ai-dj.html +167 -0
- package/examples/example-ambient.html +63 -63
- package/examples/example-ambient.mp3 +0 -0
- package/examples/example-bass.html +48 -0
- package/examples/example-lofi.html +45 -45
- package/examples/example-lofi.mp3 +0 -0
- package/examples/example-minimal.html +21 -19
- package/examples/example-minimal.mp3 +0 -0
- package/examples/example-orchestral.html +67 -67
- package/examples/example-orchestral.mp3 +0 -0
- package/examples/example-piano.html +53 -0
- package/examples/example-piano.mp3 +0 -0
- package/examples/example-techno.html +69 -69
- package/examples/example-techno.mp3 +0 -0
- package/package.json +42 -24
- package/registry/presets/bass-electric.html +67 -0
- package/registry/presets/bass-saw.html +22 -0
- package/registry/presets/chord-progression.html +22 -0
- package/registry/presets/drums-808.html +85 -0
- package/registry/presets/drums-lofi.html +35 -0
- package/registry/presets/lead-piano.html +26 -0
- package/registry/presets/piano-salamander.html +69 -0
- package/registry/presets/reverb-warm.html +11 -0
- package/registry/samples.json +226 -0
- package/skills/audio-ambient/SKILL.md +141 -0
- package/skills/audio-ambient/example.html +113 -0
- package/skills/audio-boss-battle/SKILL.md +157 -0
- package/skills/audio-boss-battle/example.html +185 -0
- package/skills/audio-boss-battle/example.mp3 +0 -0
- package/skills/audio-chillwave/SKILL.md +142 -0
- package/skills/audio-chillwave/example.html +144 -0
- package/skills/audio-cinematic/SKILL.md +147 -0
- package/skills/audio-cinematic/example.html +123 -0
- package/skills/audio-classical/SKILL.md +138 -0
- package/skills/audio-classical/example.html +145 -0
- package/skills/audio-dnb/SKILL.md +142 -0
- package/skills/audio-dnb/example.html +124 -0
- package/skills/audio-downtempo/SKILL.md +152 -0
- package/skills/audio-downtempo/example.html +164 -0
- package/skills/audio-folk/SKILL.md +139 -0
- package/skills/audio-folk/example.html +132 -0
- package/skills/audio-funk/SKILL.md +149 -0
- package/skills/audio-funk/example.html +144 -0
- package/skills/audio-future-bass/SKILL.md +163 -0
- package/skills/audio-future-bass/example.html +164 -0
- package/skills/audio-hip-hop/SKILL.md +133 -0
- package/skills/audio-hip-hop/example.html +129 -0
- package/skills/audio-house/SKILL.md +147 -0
- package/skills/audio-house/example.html +128 -0
- package/skills/audio-indie-pop/SKILL.md +150 -0
- package/skills/audio-indie-pop/example.html +121 -0
- package/skills/audio-jazz/SKILL.md +141 -0
- package/skills/audio-jazz/example.html +146 -0
- package/skills/audio-lofi/SKILL.md +140 -0
- package/skills/audio-lofi/example.html +135 -0
- package/skills/audio-minimal/SKILL.md +155 -0
- package/skills/audio-minimal/example.html +118 -0
- package/skills/audio-orchestral/SKILL.md +156 -0
- package/skills/audio-orchestral/example.html +140 -0
- package/skills/audio-r-and-b/SKILL.md +134 -0
- package/skills/audio-r-and-b/example.html +154 -0
- package/skills/audio-r-and-b/example.mp3 +0 -0
- package/skills/audio-techno/SKILL.md +140 -0
- package/skills/audio-techno/example.html +125 -0
- package/skills/audio-trap/SKILL.md +123 -0
- package/skills/audio-trap/example.html +119 -0
- package/skills/render-retest/example.html +115 -0
- package/skills/tuneframes/SKILL.md +221 -0
- package/skills/tuneframes-cli/SKILL.md +46 -0
- package/skills/verify/example.html +104 -0
- package/src/cli.js +261 -89
- 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>
|