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.
- package/LICENSE +166 -166
- package/README.md +158 -55
- 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 +47 -47
- package/examples/example-lofi.html +45 -45
- package/examples/example-lofi.mp3 +0 -0
- package/examples/example-minimal.html +20 -20
- package/examples/example-orchestral.html +67 -67
- package/examples/example-orchestral.mp3 +0 -0
- package/examples/example-piano.html +52 -52
- 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 -37
- 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 +260 -151
- package/src/render.js +134 -7
- package/examples/example-bass.mp3 +0 -0
- package/examples/example-demo-beat.wav +0 -0
- 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>
|