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,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>
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: audio-funk
|
|
3
|
+
description: Funk — slap bass, brass stabs, 16th-note hi-hats, syncopated groove that snaps
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TuneFrames — Funk
|
|
7
|
+
|
|
8
|
+
## Genre Profile
|
|
9
|
+
- BPM range: 95–115 (the pocket lives around 100–108)
|
|
10
|
+
- Key characteristics: Syncopated 16th-note grid, everything lands on or anticipates the beat, brass stabs on upbeats, rhythm guitar "chank" on the "and" of 2 and 4, bass owns the low end
|
|
11
|
+
- Typical instruments: Slap bass (Synth with fast attack + filter), brass stabs (PolySynth sawtooth, sharp envelope), rhythm guitar (PolySynth pluck), kick (MembraneSynth), tight snare (NoiseSynth), 16th hi-hats (MetalSynth)
|
|
12
|
+
- Mood: Infectious, locked-in, body-moving, celebratory — James Brown, Parliament, Daft Punk, Vulfpeck
|
|
13
|
+
|
|
14
|
+
## Core Pattern
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
// Funk core: 104 BPM, E minor groove
|
|
18
|
+
async function main() {
|
|
19
|
+
await Tone.start();
|
|
20
|
+
Tone.Transport.bpm.value = 104;
|
|
21
|
+
|
|
22
|
+
const s16 = Tone.Time('16n').toSeconds(); // one 16th note
|
|
23
|
+
const q = Tone.Time('4n').toSeconds(); // one beat
|
|
24
|
+
|
|
25
|
+
const dest = new Tone.Gain(1).toDestination();
|
|
26
|
+
const comp = new Tone.Compressor(-18, 4).connect(dest);
|
|
27
|
+
|
|
28
|
+
// ── Slap bass ────────────────────────────────────────────────────────
|
|
29
|
+
const bass = new Tone.Synth({
|
|
30
|
+
oscillator: { type: 'square' },
|
|
31
|
+
envelope: { attack: 0.001, decay: 0.08, sustain: 0.0, release: 0.05 }
|
|
32
|
+
}).connect(comp);
|
|
33
|
+
bass.volume.value = -6;
|
|
34
|
+
|
|
35
|
+
// Classic one-bar funk bass: E on 1, ghost note 16th early, pop on e2-and
|
|
36
|
+
const bassLine = [
|
|
37
|
+
{ note: 'E2', time: 0 },
|
|
38
|
+
{ note: 'E2', time: s16 * 2 },
|
|
39
|
+
{ note: 'G2', time: s16 * 3 },
|
|
40
|
+
{ note: 'E2', time: s16 * 4 },
|
|
41
|
+
{ note: 'A2', time: s16 * 6 },
|
|
42
|
+
{ note: 'E2', time: s16 * 8 },
|
|
43
|
+
{ note: 'D2', time: s16 * 10 },
|
|
44
|
+
{ note: 'E2', time: s16 * 12 },
|
|
45
|
+
{ note: 'G2', time: s16 * 14 },
|
|
46
|
+
];
|
|
47
|
+
bassLine.forEach(({ note, time }) => bass.triggerAttackRelease(note, '16n', time));
|
|
48
|
+
|
|
49
|
+
// ── Brass stabs (upbeat hits — "and" of 2 and 4) ──────────────────────
|
|
50
|
+
const brass = new Tone.PolySynth(Tone.Synth, {
|
|
51
|
+
oscillator: { type: 'sawtooth' },
|
|
52
|
+
envelope: { attack: 0.005, decay: 0.12, sustain: 0.0, release: 0.08 }
|
|
53
|
+
}).connect(comp);
|
|
54
|
+
brass.volume.value = -12;
|
|
55
|
+
|
|
56
|
+
// Stab on the "and" of beat 2 (s16*9) and "and" of beat 4 (s16*13)
|
|
57
|
+
brass.triggerAttackRelease(['E4','G4','B4'], '16n', s16 * 9);
|
|
58
|
+
brass.triggerAttackRelease(['E4','G4','B4'], '16n', s16 * 13);
|
|
59
|
+
|
|
60
|
+
// ── Tight kick ────────────────────────────────────────────────────────
|
|
61
|
+
const kick = new Tone.MembraneSynth({
|
|
62
|
+
pitchDecay: 0.04, octaves: 6,
|
|
63
|
+
envelope: { attack: 0.001, decay: 0.25, sustain: 0, release: 0.1 }
|
|
64
|
+
}).connect(dest);
|
|
65
|
+
kick.volume.value = -10;
|
|
66
|
+
kick.triggerAttackRelease('C1', '8n', 0); // beat 1
|
|
67
|
+
kick.triggerAttackRelease('C1', '8n', s16 * 10); // beat 2-and-a (syncopated)
|
|
68
|
+
|
|
69
|
+
// ── Snare on 3 ────────────────────────────────────────────────────────
|
|
70
|
+
const snare = new Tone.NoiseSynth({
|
|
71
|
+
noise: { type: 'white' },
|
|
72
|
+
envelope: { attack: 0.001, decay: 0.1, sustain: 0, release: 0.05 }
|
|
73
|
+
}).connect(new Tone.Filter(3500, 'bandpass').connect(dest));
|
|
74
|
+
snare.volume.value = -14;
|
|
75
|
+
snare.triggerAttackRelease('8n', s16 * 8); // beat 3
|
|
76
|
+
|
|
77
|
+
// ── 16th hi-hats ─────────────────────────────────────────────────────
|
|
78
|
+
const hat = new Tone.MetalSynth({
|
|
79
|
+
frequency: 600, harmonicity: 5.1, modulationIndex: 32,
|
|
80
|
+
resonance: 4000, octaves: 1.5,
|
|
81
|
+
envelope: { attack: 0.001, decay: 0.03, release: 0.01 }
|
|
82
|
+
}).connect(dest);
|
|
83
|
+
hat.volume.value = -20;
|
|
84
|
+
for (let i = 0; i < 16; i++) hat.triggerAttackRelease('32n', i * s16);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Instrument Configuration
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
// Slap bass — square wave, nearly no sustain (percussive pluck feel)
|
|
92
|
+
const bass = new Tone.Synth({
|
|
93
|
+
oscillator: { type: 'square' },
|
|
94
|
+
envelope: { attack: 0.001, decay: 0.08, sustain: 0.0, release: 0.05 }
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Rhythm guitar "chank" — bright pluck, bandpass filtered, chord stab
|
|
98
|
+
const guitar = new Tone.PolySynth(Tone.Synth, {
|
|
99
|
+
oscillator: { type: 'triangle' },
|
|
100
|
+
envelope: { attack: 0.003, decay: 0.15, sustain: 0.0, release: 0.08 }
|
|
101
|
+
});
|
|
102
|
+
const guitarFilter = new Tone.Filter(2500, 'bandpass');
|
|
103
|
+
guitar.connect(guitarFilter);
|
|
104
|
+
|
|
105
|
+
// Brass stabs — sawtooth, instant attack, very short decay (punchy, no tail)
|
|
106
|
+
const brass = new Tone.PolySynth(Tone.Synth, {
|
|
107
|
+
oscillator: { type: 'sawtooth' },
|
|
108
|
+
envelope: { attack: 0.005, decay: 0.12, sustain: 0.0, release: 0.08 }
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Bus compressor glues everything together (the "pumping" funk feel)
|
|
112
|
+
const comp = new Tone.Compressor({ threshold: -18, ratio: 4, attack: 0.003, release: 0.1 });
|
|
113
|
+
comp.toDestination();
|
|
114
|
+
// Route everything through comp
|
|
115
|
+
[bass, brass, guitar].forEach(s => s.connect(comp));
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Composition Structure
|
|
119
|
+
|
|
120
|
+
1. **Intro (0–2s):** Bass alone on the groove, just root and fifth — establish the pocket
|
|
121
|
+
2. **Drums in (2–4s):** Kick + snare + 16th hats lock with the bass
|
|
122
|
+
3. **Full band (4–8s):** Brass stabs enter on upbeats, guitar chank fills the midrange
|
|
123
|
+
4. **Breakdown (8–10s):** Strip to bass + kick only — tension before the re-hit
|
|
124
|
+
5. **Re-hit (10–12s):** Everything back, maybe add a filter sweep on the bass up
|
|
125
|
+
|
|
126
|
+
## Example Variations
|
|
127
|
+
|
|
128
|
+
### 1 — Open hi-hat accent on beat 4
|
|
129
|
+
```js
|
|
130
|
+
const openHat = new Tone.MetalSynth({ envelope: { decay: 0.25 } }).connect(dest);
|
|
131
|
+
openHat.volume.value = -18;
|
|
132
|
+
openHat.triggerAttackRelease('8n', q * 3); // beat 4 open
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 2 — Wah filter on brass (auto-wah effect)
|
|
136
|
+
```js
|
|
137
|
+
const autoWah = new Tone.AutoFilter({ frequency: 4, baseFrequency: 200, octaves: 4, wet: 0.8 });
|
|
138
|
+
autoWah.connect(dest);
|
|
139
|
+
autoWah.start(0);
|
|
140
|
+
brass.connect(autoWah);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 3 — Daft Punk-style filtered house funk
|
|
144
|
+
```js
|
|
145
|
+
// Add LP filter sweep on the bass from 300 Hz to 2000 Hz over 4 bars
|
|
146
|
+
const bassFilter = new Tone.Filter(300, 'lowpass').connect(comp);
|
|
147
|
+
bass.connect(bassFilter);
|
|
148
|
+
bassFilter.frequency.rampTo(2000, bar * 4);
|
|
149
|
+
```
|