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,113 @@
|
|
|
1
|
+
<div id="tuneframes" style="display:none">{"bpm":60,"duration":"14s"}</div>
|
|
2
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
|
|
3
|
+
<script>
|
|
4
|
+
// TuneFrames — Ambient (Brian Eno style)
|
|
5
|
+
// D minor drone pads, massive reverb, slow evolving pitch movement
|
|
6
|
+
// No percussion. Everything breathes.
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
await Tone.start();
|
|
10
|
+
|
|
11
|
+
Tone.Transport.bpm.value = 60;
|
|
12
|
+
|
|
13
|
+
// ── Effects chain ──────────────────────────────────────────────────────────
|
|
14
|
+
const reverb = new Tone.Reverb({ decay: 10, preDelay: 0.05, wet: 0.88 });
|
|
15
|
+
await reverb.generate();
|
|
16
|
+
reverb.toDestination();
|
|
17
|
+
|
|
18
|
+
const delay = new Tone.FeedbackDelay({ delayTime: 0.5, feedback: 0.42, wet: 0.32 });
|
|
19
|
+
delay.connect(reverb);
|
|
20
|
+
|
|
21
|
+
// ── Primary drone — sine wave, very slow attack ────────────────────────────
|
|
22
|
+
const drone = new Tone.PolySynth(Tone.Synth, {
|
|
23
|
+
oscillator: { type: 'sine' },
|
|
24
|
+
envelope: { attack: 4.5, decay: 2.0, sustain: 0.9, release: 7.0 },
|
|
25
|
+
volume: -8,
|
|
26
|
+
});
|
|
27
|
+
drone.connect(delay);
|
|
28
|
+
|
|
29
|
+
// ── Shimmer layer — triangle, even slower, upper register ─────────────────
|
|
30
|
+
const shimmer = new Tone.PolySynth(Tone.Synth, {
|
|
31
|
+
oscillator: { type: 'triangle' },
|
|
32
|
+
envelope: { attack: 5.0, decay: 1.5, sustain: 0.75, release: 6.0 },
|
|
33
|
+
volume: -17,
|
|
34
|
+
});
|
|
35
|
+
shimmer.connect(reverb);
|
|
36
|
+
|
|
37
|
+
// ── Sub drone — anchors the bass register ─────────────────────────────────
|
|
38
|
+
const sub = new Tone.Synth({
|
|
39
|
+
oscillator: { type: 'sine' },
|
|
40
|
+
envelope: { attack: 6.0, decay: 3.0, sustain: 0.9, release: 8.0 },
|
|
41
|
+
volume: -13,
|
|
42
|
+
});
|
|
43
|
+
sub.connect(reverb);
|
|
44
|
+
|
|
45
|
+
// ── Schedule drone tones — D minor: D F A C ───────────────────────────────
|
|
46
|
+
// Staggered so they don't all attack at once — the overlapping sustains
|
|
47
|
+
// create the "chord cloud" that defines ambient music.
|
|
48
|
+
// Note durations use seconds (6, 5, etc.) so the 4.5–6s attacks can fully
|
|
49
|
+
// develop before release. '8n' (0.5s at 60 BPM) is far too short.
|
|
50
|
+
|
|
51
|
+
// Root drone: enters immediately, fades in over 4.5 seconds
|
|
52
|
+
Tone.Transport.scheduleOnce((t) => {
|
|
53
|
+
drone.triggerAttackRelease(['D3', 'A3'], 6, t);
|
|
54
|
+
}, 0);
|
|
55
|
+
|
|
56
|
+
// Fifth enters at 1.5s — builds on the drone
|
|
57
|
+
Tone.Transport.scheduleOnce((t) => {
|
|
58
|
+
drone.triggerAttackRelease(['F3', 'C4'], 6, t);
|
|
59
|
+
}, 1.5);
|
|
60
|
+
|
|
61
|
+
// Color tone — minor 7th adds bittersweet quality
|
|
62
|
+
Tone.Transport.scheduleOnce((t) => {
|
|
63
|
+
drone.triggerAttackRelease(['E3', 'G3'], 5, t);
|
|
64
|
+
}, 3.5);
|
|
65
|
+
|
|
66
|
+
// Second wave — new chord cloud begins overlapping the first
|
|
67
|
+
Tone.Transport.scheduleOnce((t) => {
|
|
68
|
+
drone.triggerAttackRelease(['D3', 'F3'], 5, t);
|
|
69
|
+
}, 6.0);
|
|
70
|
+
|
|
71
|
+
Tone.Transport.scheduleOnce((t) => {
|
|
72
|
+
drone.triggerAttackRelease(['A3', 'C4'], 4, t);
|
|
73
|
+
}, 8.0);
|
|
74
|
+
|
|
75
|
+
// ── Shimmer tones — upper harmonics ───────────────────────────────────────
|
|
76
|
+
Tone.Transport.scheduleOnce((t) => {
|
|
77
|
+
shimmer.triggerAttackRelease(['F5', 'A5'], 5, t);
|
|
78
|
+
}, 2.0);
|
|
79
|
+
|
|
80
|
+
Tone.Transport.scheduleOnce((t) => {
|
|
81
|
+
shimmer.triggerAttackRelease(['D5', 'C5'], 5, t);
|
|
82
|
+
}, 5.5);
|
|
83
|
+
|
|
84
|
+
Tone.Transport.scheduleOnce((t) => {
|
|
85
|
+
shimmer.triggerAttackRelease(['E5', 'G5'], 4, t);
|
|
86
|
+
}, 9.0);
|
|
87
|
+
|
|
88
|
+
// ── Sub bass — single low D, fades in and holds ───────────────────────────
|
|
89
|
+
Tone.Transport.scheduleOnce((t) => {
|
|
90
|
+
sub.triggerAttackRelease('D2', 7, t);
|
|
91
|
+
}, 0.5);
|
|
92
|
+
|
|
93
|
+
// NOTE: do NOT call Tone.Transport.start() here — Tone.Offline manages
|
|
94
|
+
// transport internally. Calling it manually breaks offline rendering.
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
window.renderComposition = async function(wavPath) {
|
|
98
|
+
let attempts = 0;
|
|
99
|
+
while (typeof Tone === 'undefined' && attempts < 50) {
|
|
100
|
+
await new Promise(r => setTimeout(r, 100));
|
|
101
|
+
attempts++;
|
|
102
|
+
}
|
|
103
|
+
if (typeof Tone === 'undefined') throw new Error('Tone not loaded');
|
|
104
|
+
|
|
105
|
+
const meta = JSON.parse(document.getElementById('tuneframes').textContent);
|
|
106
|
+
const durationSec = parseFloat(meta.duration) + 1.0;
|
|
107
|
+
|
|
108
|
+
const buffer = await Tone.Offline(async () => { await main(); }, durationSec, 1, 44100);
|
|
109
|
+
const wav = audioBufferToWav(buffer);
|
|
110
|
+
window.writeFile(wavPath, Array.from(new Uint8Array(wav)));
|
|
111
|
+
return wav.byteLength;
|
|
112
|
+
};
|
|
113
|
+
</script>
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: audio-boss-battle
|
|
3
|
+
description: Video Game Boss Battle — driving 16th-note bass ostinato, power chord stabs on offbeats, heroic AMSynth brass melody, tuned timpani hits, fast counter-arpeggio. Final Fantasy / Nobuo Uematsu epic orchestral-hybrid energy.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TuneFrames — Video Game Boss Battle
|
|
7
|
+
|
|
8
|
+
## Genre Profile
|
|
9
|
+
- BPM range: 150–185 (intense, relentless forward drive)
|
|
10
|
+
- Key characteristics: Bass ostinato hammering the root in 16th notes (MonoSynth sawtooth), power chord stabs on offbeats (PolySynth square/sawtooth, short decay), heroic lead melody (AMSynth with sawtooth + sine modulation for brass-like tone), tuned timpani accents (MembraneSynth pitched to D1/A1), fast ascending counter-arpeggio for tension, driving kick pattern
|
|
11
|
+
- Typical instruments: MonoSynth (bass ostinato), PolySynth (power chords), AMSynth (brass melody), MembraneSynth × 2 (kick + timpani), PolySynth (counter arpeggio)
|
|
12
|
+
- Mood: Intense, epic, driving, heroic, desperate, high-stakes
|
|
13
|
+
|
|
14
|
+
## Core Pattern
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
// Boss Battle — 165 BPM, D minor
|
|
18
|
+
// Bars 1–2: bass ostinato alone (ominous)
|
|
19
|
+
// Bars 3–4: power chord stabs on offbeats
|
|
20
|
+
// Bars 5–8: full: melody + counter arpeggio + drums
|
|
21
|
+
|
|
22
|
+
Tone.Transport.bpm.value = 165;
|
|
23
|
+
|
|
24
|
+
// Bass ostinato — driving 16th note figure around D root
|
|
25
|
+
const bass = new Tone.MonoSynth({
|
|
26
|
+
oscillator: { type: "sawtooth" },
|
|
27
|
+
filter: { Q: 3, type: "lowpass", rolloff: -24 },
|
|
28
|
+
filterEnvelope: { attack: 0.001, decay: 0.1, sustain: 0.5, release: 0.2,
|
|
29
|
+
baseFrequency: 80, octaves: 2 },
|
|
30
|
+
envelope: { attack: 0.001, decay: 0.1, sustain: 0.7, release: 0.1 },
|
|
31
|
+
volume: -4
|
|
32
|
+
}).toDestination();
|
|
33
|
+
|
|
34
|
+
// 16th-note ostinato: D root hammered with neighbor-note movement
|
|
35
|
+
const bassLine = [
|
|
36
|
+
"D2","D2","F2","D2", "G2","D2","F2","E2",
|
|
37
|
+
"D2","D2","F2","D2", "G2","F2","Eb2","D2"
|
|
38
|
+
];
|
|
39
|
+
let bassStep = 0;
|
|
40
|
+
new Tone.Sequence((time) => {
|
|
41
|
+
bass.triggerAttackRelease(bassLine[bassStep++ % bassLine.length], "16n", time);
|
|
42
|
+
}, new Array(16).fill(1), "16n").start(0);
|
|
43
|
+
|
|
44
|
+
// Power chord stabs — PolySynth on the "and" of beats 1 and 3
|
|
45
|
+
const stabChords = [["D3","A3"],["F3","C4"],["G3","D4"],["Bb3","F4"]];
|
|
46
|
+
let stabStep = 0;
|
|
47
|
+
new Tone.Sequence((time, val) => {
|
|
48
|
+
if (val) stabs.triggerAttackRelease(stabChords[stabStep++ % stabChords.length], "16n", time);
|
|
49
|
+
}, [null,null,1,null,null,null,null,null,null,null,1,null,null,null,null,null], "16n").start("2m");
|
|
50
|
+
|
|
51
|
+
// Heroic AMSynth melody — D minor ascending/descending
|
|
52
|
+
const melody = [
|
|
53
|
+
{ time: "4m", note: "D4", dur: "4n" }, { time: "4m:1", note: "F4", dur: "4n" },
|
|
54
|
+
{ time: "4m:2", note: "A4", dur: "4n" }, { time: "4m:3", note: "C5", dur: "4n" },
|
|
55
|
+
{ time: "5m", note: "Bb4", dur: "4n" }, { time: "5m:1", note: "A4", dur: "4n" },
|
|
56
|
+
{ time: "5m:2", note: "G4", dur: "2n" },
|
|
57
|
+
];
|
|
58
|
+
melody.forEach(({ time, note, dur }) => brass.triggerAttackRelease(note, dur, time));
|
|
59
|
+
|
|
60
|
+
Tone.Transport.start();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Instrument Configuration
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
// Bass ostinato — sawtooth through lowpass filter for growl
|
|
67
|
+
const bassVerb = new Tone.Reverb({ decay: 0.4, wet: 0.1 }).toDestination();
|
|
68
|
+
const bass = new Tone.MonoSynth({
|
|
69
|
+
oscillator: { type: "sawtooth" },
|
|
70
|
+
filter: { Q: 3, type: "lowpass", rolloff: -24 },
|
|
71
|
+
filterEnvelope: { attack: 0.001, decay: 0.1, sustain: 0.5, release: 0.2,
|
|
72
|
+
baseFrequency: 80, octaves: 2 },
|
|
73
|
+
envelope: { attack: 0.001, decay: 0.1, sustain: 0.7, release: 0.1 },
|
|
74
|
+
volume: -4
|
|
75
|
+
}).connect(bassVerb);
|
|
76
|
+
|
|
77
|
+
// Power chord stabs — square wave, very short decay, punchy
|
|
78
|
+
const stabReverb = new Tone.Reverb({ decay: 0.6, wet: 0.2 }).toDestination();
|
|
79
|
+
const stabs = new Tone.PolySynth(Tone.Synth, {
|
|
80
|
+
oscillator: { type: "square" },
|
|
81
|
+
envelope: { attack: 0.001, decay: 0.12, sustain: 0.0, release: 0.08 },
|
|
82
|
+
volume: -8
|
|
83
|
+
}).connect(stabReverb);
|
|
84
|
+
|
|
85
|
+
// AMSynth brass melody — sawtooth carrier + sine AM creates "blat" of brass
|
|
86
|
+
const brassVerb = new Tone.Reverb({ decay: 1.5, wet: 0.3 }).toDestination();
|
|
87
|
+
const brass = new Tone.AMSynth({
|
|
88
|
+
oscillator: { type: "sawtooth" },
|
|
89
|
+
envelope: { attack: 0.04, decay: 0.1, sustain: 0.85, release: 0.4 },
|
|
90
|
+
modulation: { type: "sine" },
|
|
91
|
+
modulationEnvelope: { attack: 0.5, decay: 0, sustain: 1, release: 0.5 },
|
|
92
|
+
harmonicity: 1,
|
|
93
|
+
volume: -8
|
|
94
|
+
}).connect(brassVerb);
|
|
95
|
+
|
|
96
|
+
// Timpani — MembraneSynth pitched low, longer decay than kick
|
|
97
|
+
const timpsVerb = new Tone.Reverb({ decay: 1.2, wet: 0.35 }).toDestination();
|
|
98
|
+
const timps = new Tone.MembraneSynth({
|
|
99
|
+
pitchDecay: 0.2,
|
|
100
|
+
octaves: 4,
|
|
101
|
+
envelope: { attack: 0.001, decay: 0.6, sustain: 0, release: 0.3 },
|
|
102
|
+
volume: -4
|
|
103
|
+
}).connect(timpsVerb);
|
|
104
|
+
|
|
105
|
+
// Kick — driving, 8th or 16th note pattern
|
|
106
|
+
const kick = new Tone.MembraneSynth({
|
|
107
|
+
pitchDecay: 0.06, octaves: 8,
|
|
108
|
+
envelope: { attack: 0.001, decay: 0.28, sustain: 0, release: 0.08 },
|
|
109
|
+
volume: 0
|
|
110
|
+
}).toDestination();
|
|
111
|
+
|
|
112
|
+
// Counter arpeggio — fast 16th note ascent, adds tension
|
|
113
|
+
const arpVerb = new Tone.Reverb({ decay: 0.8, wet: 0.25 }).toDestination();
|
|
114
|
+
const arpSynth = new Tone.PolySynth(Tone.Synth, {
|
|
115
|
+
oscillator: { type: "sawtooth4" },
|
|
116
|
+
envelope: { attack: 0.001, decay: 0.08, sustain: 0, release: 0.1 },
|
|
117
|
+
volume: -14
|
|
118
|
+
}).connect(arpVerb);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Composition Structure
|
|
122
|
+
|
|
123
|
+
- **Bars 1–2:** Bass ostinato only — ominous, low, relentless (the enemy approaches)
|
|
124
|
+
- **Bars 3–4:** Timpani accents on bar downbeats, power chord stabs on offbeats (tension escalates)
|
|
125
|
+
- **Bars 5–6:** Kick + snare enter, driving rhythm locked in, counter arpeggio begins
|
|
126
|
+
- **Bars 7–10:** Full arrangement — AMSynth brass melody soars over the groove
|
|
127
|
+
- **Bar 11–12:** Climax: all elements together, melody at highest pitch, bass most active
|
|
128
|
+
- **Buildup trick:** Use `Tone.Transport.bpm.linearRampTo(180, "+4m")` to gradually accelerate into the final bars
|
|
129
|
+
|
|
130
|
+
Harmonic vocabulary: D natural minor (D E F G A Bb C). Tritone sub on beat 3 (Ab) adds danger. Perfect 5th power chords [root, 5th] — no thirds — are the "8-bit" signature.
|
|
131
|
+
|
|
132
|
+
## Example Variations
|
|
133
|
+
|
|
134
|
+
### 1 — Faster bass (triplet feel, more chaotic)
|
|
135
|
+
```js
|
|
136
|
+
// 8th note triplets instead of 16ths — more chaotic, less locked-in
|
|
137
|
+
const tripletBass = ["D2","F2","A2","G2","F2","D2"];
|
|
138
|
+
let step = 0;
|
|
139
|
+
new Tone.Sequence((time) => {
|
|
140
|
+
bass.triggerAttackRelease(tripletBass[step++ % tripletBass.length], "8t", time);
|
|
141
|
+
}, new Array(6).fill(1), "8t").start(0);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 2 — Tempo acceleration (building to a climax)
|
|
145
|
+
```js
|
|
146
|
+
// Slowly push the BPM from 160 to 185 over 8 bars
|
|
147
|
+
const startTime = Tone.Time("4m").toSeconds();
|
|
148
|
+
Tone.Transport.bpm.setValueAtTime(160, startTime);
|
|
149
|
+
Tone.Transport.bpm.linearRampToValueAtTime(185, startTime + Tone.Time("4m").toSeconds());
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 3 — Minor key shift mid-battle (F minor for boss phase 2)
|
|
153
|
+
```js
|
|
154
|
+
// After 8 bars, shift to F minor: F G Ab Bb C Db Eb
|
|
155
|
+
// Retrigger stab chords and melody with new root
|
|
156
|
+
const phase2Chords = [["F3","C4"],["Ab3","Eb4"],["Bb3","F4"],["Db4","Ab4"]];
|
|
157
|
+
```
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>TuneFrames — Boss Battle</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":165,"duration":"12s","genre":"boss-battle","key":"D minor"}</div>
|
|
11
|
+
|
|
12
|
+
<script>
|
|
13
|
+
async function main() {
|
|
14
|
+
await Tone.start();
|
|
15
|
+
Tone.Transport.bpm.value = 165;
|
|
16
|
+
|
|
17
|
+
// --- MASTER BUS ---
|
|
18
|
+
const masterLimiter = new Tone.Limiter(-1).toDestination();
|
|
19
|
+
const masterGain = new Tone.Gain(0.8).connect(masterLimiter);
|
|
20
|
+
|
|
21
|
+
// --- BASS OSTINATO — driving 16th notes, sawtooth growl ---
|
|
22
|
+
const bassVerb = new Tone.Reverb({ decay: 0.4, wet: 0.1 }).connect(masterGain);
|
|
23
|
+
const bassGain = new Tone.Gain(0.75).connect(bassVerb);
|
|
24
|
+
const bass = new Tone.MonoSynth({
|
|
25
|
+
oscillator: { type: "sawtooth" },
|
|
26
|
+
filter: { Q: 3, type: "lowpass", rolloff: -24 },
|
|
27
|
+
filterEnvelope: {
|
|
28
|
+
attack: 0.001, decay: 0.1, sustain: 0.5, release: 0.2,
|
|
29
|
+
baseFrequency: 80, octaves: 2
|
|
30
|
+
},
|
|
31
|
+
envelope: { attack: 0.001, decay: 0.1, sustain: 0.7, release: 0.1 },
|
|
32
|
+
volume: -2
|
|
33
|
+
}).connect(bassGain);
|
|
34
|
+
|
|
35
|
+
// --- POWER CHORD STABS — square wave, very punchy ---
|
|
36
|
+
const stabReverb = new Tone.Reverb({ decay: 0.6, wet: 0.22 }).connect(masterGain);
|
|
37
|
+
const stabGain = new Tone.Gain(0.5).connect(stabReverb);
|
|
38
|
+
const stabs = new Tone.PolySynth(Tone.Synth, {
|
|
39
|
+
oscillator: { type: "square" },
|
|
40
|
+
envelope: { attack: 0.001, decay: 0.12, sustain: 0.0, release: 0.08 },
|
|
41
|
+
volume: 0
|
|
42
|
+
}).connect(stabGain);
|
|
43
|
+
|
|
44
|
+
// --- BRASS MELODY — AMSynth: sawtooth + sine AM = heroic blat ---
|
|
45
|
+
const brassVerb = new Tone.Reverb({ decay: 1.5, wet: 0.3 }).connect(masterGain);
|
|
46
|
+
const brassGain = new Tone.Gain(0.55).connect(brassVerb);
|
|
47
|
+
const brass = new Tone.AMSynth({
|
|
48
|
+
oscillator: { type: "sawtooth" },
|
|
49
|
+
envelope: { attack: 0.04, decay: 0.1, sustain: 0.85, release: 0.4 },
|
|
50
|
+
modulation: { type: "sine" },
|
|
51
|
+
modulationEnvelope: { attack: 0.5, decay: 0, sustain: 1, release: 0.5 },
|
|
52
|
+
harmonicity: 1,
|
|
53
|
+
volume: 0
|
|
54
|
+
}).connect(brassGain);
|
|
55
|
+
|
|
56
|
+
// --- TIMPANI — low-tuned MembraneSynth, long decay ---
|
|
57
|
+
const timpsVerb = new Tone.Reverb({ decay: 1.2, wet: 0.35 }).connect(masterGain);
|
|
58
|
+
const timpsGain = new Tone.Gain(0.65).connect(timpsVerb);
|
|
59
|
+
const timps = new Tone.MembraneSynth({
|
|
60
|
+
pitchDecay: 0.2,
|
|
61
|
+
octaves: 4,
|
|
62
|
+
envelope: { attack: 0.001, decay: 0.6, sustain: 0, release: 0.3 }
|
|
63
|
+
}).connect(timpsGain);
|
|
64
|
+
|
|
65
|
+
// --- KICK — hard, driving ---
|
|
66
|
+
const kickGain = new Tone.Gain(0.9).connect(masterGain);
|
|
67
|
+
const kick = new Tone.MembraneSynth({
|
|
68
|
+
pitchDecay: 0.06,
|
|
69
|
+
octaves: 8,
|
|
70
|
+
envelope: { attack: 0.001, decay: 0.28, sustain: 0, release: 0.08 }
|
|
71
|
+
}).connect(kickGain);
|
|
72
|
+
|
|
73
|
+
// --- SNARE — sharp crack ---
|
|
74
|
+
const snareVerb = new Tone.Reverb({ decay: 0.5, wet: 0.3 }).connect(masterGain);
|
|
75
|
+
const snareGain = new Tone.Gain(0.48).connect(snareVerb);
|
|
76
|
+
const snare = new Tone.NoiseSynth({
|
|
77
|
+
noise: { type: "white" },
|
|
78
|
+
envelope: { attack: 0.001, decay: 0.1, sustain: 0, release: 0.04 }
|
|
79
|
+
}).connect(snareGain);
|
|
80
|
+
|
|
81
|
+
// --- COUNTER ARPEGGIO — fast ascending D minor runs ---
|
|
82
|
+
const arpVerb = new Tone.Reverb({ decay: 0.8, wet: 0.25 }).connect(masterGain);
|
|
83
|
+
const arpGain = new Tone.Gain(0.3).connect(arpVerb);
|
|
84
|
+
const arpSynth = new Tone.PolySynth(Tone.Synth, {
|
|
85
|
+
oscillator: { type: "sawtooth4" },
|
|
86
|
+
envelope: { attack: 0.001, decay: 0.08, sustain: 0, release: 0.1 },
|
|
87
|
+
volume: 0
|
|
88
|
+
}).connect(arpGain);
|
|
89
|
+
|
|
90
|
+
// --- PATTERNS ---
|
|
91
|
+
|
|
92
|
+
// Bass ostinato: 16th-note D minor figure with neighbor movement (from bar 1)
|
|
93
|
+
const bassLine = [
|
|
94
|
+
"D2","D2","F2","D2", "G2","D2","F2","E2",
|
|
95
|
+
"D2","D2","F2","D2", "G2","F2","Eb2","D2"
|
|
96
|
+
];
|
|
97
|
+
let bassStep = 0;
|
|
98
|
+
const bassSeq = new Tone.Sequence((time) => {
|
|
99
|
+
bass.triggerAttackRelease(bassLine[bassStep % bassLine.length], "16n", time);
|
|
100
|
+
bassStep++;
|
|
101
|
+
}, new Array(16).fill(1), "16n");
|
|
102
|
+
bassSeq.start(0);
|
|
103
|
+
|
|
104
|
+
// Timpani: D1 on bar 1, A1 on beat 3, D1 on bar 3 — enters bar 2
|
|
105
|
+
// Use bars:beats:sixteenths (0-indexed) to avoid mixed-notation parsing errors.
|
|
106
|
+
const timpsHits = [
|
|
107
|
+
{ time: "1:0:0", note: "D1" },
|
|
108
|
+
{ time: "1:2:0", note: "A1" },
|
|
109
|
+
{ time: "2:0:0", note: "D1" },
|
|
110
|
+
{ time: "2:2:0", note: "A1" },
|
|
111
|
+
{ time: "3:0:0", note: "D1" },
|
|
112
|
+
{ time: "3:2:0", note: "A1" },
|
|
113
|
+
{ time: "4:0:0", note: "D1" },
|
|
114
|
+
{ time: "5:0:0", note: "D1" },
|
|
115
|
+
{ time: "6:0:0", note: "D1" },
|
|
116
|
+
{ time: "7:0:0", note: "D1" },
|
|
117
|
+
];
|
|
118
|
+
timpsHits.forEach(({ time, note }) => {
|
|
119
|
+
timps.triggerAttackRelease(note, "4n", time);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Power chord stabs: cycling Dm–F–G–Bb on offbeats, enters bar 3
|
|
123
|
+
const stabChords = [["D3","A3"], ["F3","C4"], ["G3","D4"], ["Bb3","F4"]];
|
|
124
|
+
let stabStep = 0;
|
|
125
|
+
const stabSeq = new Tone.Sequence((time, val) => {
|
|
126
|
+
if (val) {
|
|
127
|
+
stabs.triggerAttackRelease(stabChords[stabStep % stabChords.length], "16n", time);
|
|
128
|
+
stabStep++;
|
|
129
|
+
}
|
|
130
|
+
}, [null,null,1,null,null,null,null,null,null,null,1,null,null,null,null,null], "16n");
|
|
131
|
+
stabSeq.start("2m");
|
|
132
|
+
|
|
133
|
+
// Kick: driving 8th notes, enters bar 3
|
|
134
|
+
const kickSeq = new Tone.Sequence((time, val) => {
|
|
135
|
+
if (val) kick.triggerAttackRelease("C1", "8n", time);
|
|
136
|
+
}, ["C1",null,null,null,"C1",null,null,null,"C1",null,null,null,"C1",null,null,null], "16n");
|
|
137
|
+
kickSeq.start("2m");
|
|
138
|
+
|
|
139
|
+
// Snare: beats 2 and 4, enters bar 3
|
|
140
|
+
const snareSeq = new Tone.Sequence((time, val) => {
|
|
141
|
+
if (val) snare.triggerAttackRelease("8n", time);
|
|
142
|
+
}, [null,null,null,null,1,null,null,null,null,null,null,null,1,null,null,null], "16n");
|
|
143
|
+
snareSeq.start("2m");
|
|
144
|
+
|
|
145
|
+
// Counter arpeggio: D minor ascending 16th runs (D F A C, repeat), enters bar 5
|
|
146
|
+
const arpNotes = ["D4","F4","A4","C5","A4","F4","D4","F4",
|
|
147
|
+
"D4","F4","A4","C5","D5","C5","A4","F4"];
|
|
148
|
+
let arpStep = 0;
|
|
149
|
+
const arpSeq = new Tone.Sequence((time) => {
|
|
150
|
+
arpSynth.triggerAttackRelease(arpNotes[arpStep % arpNotes.length], "16n", time);
|
|
151
|
+
arpStep++;
|
|
152
|
+
}, new Array(16).fill(1), "16n");
|
|
153
|
+
arpSeq.start("4m");
|
|
154
|
+
|
|
155
|
+
// Heroic brass melody: D minor scale, ascending then descending, enters bar 5
|
|
156
|
+
const melodyNotes = [
|
|
157
|
+
// Bar 4: D ascending triad + 7th
|
|
158
|
+
{ time: "4:0:0", note: "D4", dur: "4n" },
|
|
159
|
+
{ time: "4:1:0", note: "F4", dur: "4n" },
|
|
160
|
+
{ time: "4:2:0", note: "A4", dur: "4n" },
|
|
161
|
+
{ time: "4:3:0", note: "C5", dur: "4n" },
|
|
162
|
+
// Bar 5: descend
|
|
163
|
+
{ time: "5:0:0", note: "Bb4", dur: "4n" },
|
|
164
|
+
{ time: "5:1:0", note: "A4", dur: "4n" },
|
|
165
|
+
{ time: "5:2:0", note: "G4", dur: "2n" },
|
|
166
|
+
// Bar 6: climb again, higher
|
|
167
|
+
{ time: "6:0:0", note: "A4", dur: "4n" },
|
|
168
|
+
{ time: "6:1:0", note: "C5", dur: "4n" },
|
|
169
|
+
{ time: "6:2:0", note: "D5", dur: "4n" },
|
|
170
|
+
{ time: "6:3:0", note: "F5", dur: "4n" },
|
|
171
|
+
// Bar 7: final resolve
|
|
172
|
+
{ time: "7:0:0", note: "Eb5", dur: "4n" },
|
|
173
|
+
{ time: "7:1:0", note: "D5", dur: "4n" },
|
|
174
|
+
{ time: "7:2:0", note: "A4", dur: "2n" },
|
|
175
|
+
];
|
|
176
|
+
melodyNotes.forEach(({ time, note, dur }) => {
|
|
177
|
+
brass.triggerAttackRelease(note, dur, time);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
main();
|
|
183
|
+
</script>
|
|
184
|
+
</body>
|
|
185
|
+
</html>
|
|
Binary file
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: audio-chillwave
|
|
3
|
+
description: Chillwave / Retrowave — hazy 80s nostalgia, dreamy arpeggio over gated drum machine, warm chorus + delay, minor chords
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TuneFrames — Chillwave / Retrowave
|
|
7
|
+
|
|
8
|
+
## Genre Profile
|
|
9
|
+
- BPM range: 90-110 (laid back, never rushed)
|
|
10
|
+
- Key characteristics: minor key arpeggios, 80s drum machine pattern (four-on-the-floor kick, clap on 2/4, 16th hi-hats), chorus on synths, slight ping-pong delay, tape-saturated warmth
|
|
11
|
+
- Typical instruments: PolySynth (sawtooth with chorus), NoiseSynth hi-hats, MembraneSynth kick, FMSynth for clap texture
|
|
12
|
+
- Mood: nostalgic, washed-out, sun-bleached, driving at 2am
|
|
13
|
+
|
|
14
|
+
## Core Pattern
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
// Chillwave core: minor arpeggio over 80s drum machine
|
|
18
|
+
// Key = A minor, BPM = 98
|
|
19
|
+
Tone.Transport.bpm.value = 98;
|
|
20
|
+
|
|
21
|
+
// Warm chorus on all melodic synths
|
|
22
|
+
const chorus = new Tone.Chorus({ frequency: 1.5, delayTime: 3.5, depth: 0.7, wet: 0.6 });
|
|
23
|
+
chorus.start(); // Chorus must be started manually
|
|
24
|
+
const reverb = new Tone.Reverb({ decay: 3.5, wet: 0.55 });
|
|
25
|
+
const delay = new Tone.FeedbackDelay({ delayTime: '8n.', feedback: 0.35, wet: 0.4 });
|
|
26
|
+
reverb.toDestination();
|
|
27
|
+
delay.connect(reverb);
|
|
28
|
+
chorus.connect(delay);
|
|
29
|
+
|
|
30
|
+
// Main arpeggio synth — the neon heartbeat
|
|
31
|
+
const arp = new Tone.Synth({
|
|
32
|
+
oscillator: { type: 'sawtooth' },
|
|
33
|
+
envelope: { attack: 0.02, decay: 0.15, sustain: 0.3, release: 0.5 },
|
|
34
|
+
volume: -8,
|
|
35
|
+
});
|
|
36
|
+
arp.connect(chorus);
|
|
37
|
+
|
|
38
|
+
// A minor arpeggio pattern: Am - F - C - G
|
|
39
|
+
const arpNotes = ['A4','C5','E5','A5', 'F4','A4','C5','F5', 'C4','E4','G4','C5', 'G4','B4','D5','G5'];
|
|
40
|
+
let arpIdx = 0;
|
|
41
|
+
new Tone.Sequence((time) => {
|
|
42
|
+
arp.triggerAttackRelease(arpNotes[arpIdx % arpNotes.length], '16n', time);
|
|
43
|
+
arpIdx++;
|
|
44
|
+
}, [null], '16n').start(0);
|
|
45
|
+
// ^ Simpler: use scheduleRepeat or explicit schedule below
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Instrument Configuration
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
// Chorus — the defining warmth of chillwave
|
|
52
|
+
const chorus = new Tone.Chorus({ frequency: 1.5, delayTime: 3.5, depth: 0.7, wet: 0.6 }).start();
|
|
53
|
+
|
|
54
|
+
// Tape-style reverb
|
|
55
|
+
const reverb = new Tone.Reverb({ decay: 3.0, preDelay: 0.02, wet: 0.5 });
|
|
56
|
+
await reverb.generate();
|
|
57
|
+
|
|
58
|
+
// Slapback-style delay (dotted 8th = that 80s bounce)
|
|
59
|
+
const delay = new Tone.FeedbackDelay({ delayTime: '8n.', feedback: 0.3, wet: 0.38 });
|
|
60
|
+
|
|
61
|
+
// Signal chain: arp → chorus → delay → reverb → destination
|
|
62
|
+
reverb.toDestination();
|
|
63
|
+
delay.connect(reverb);
|
|
64
|
+
chorus.connect(delay);
|
|
65
|
+
|
|
66
|
+
// Arpeggio synth — sawtooth for that retrowave shimmer
|
|
67
|
+
const arp = new Tone.Synth({
|
|
68
|
+
oscillator: { type: 'sawtooth' },
|
|
69
|
+
envelope: { attack: 0.01, decay: 0.1, sustain: 0.25, release: 0.4 },
|
|
70
|
+
volume: -6,
|
|
71
|
+
});
|
|
72
|
+
arp.connect(chorus);
|
|
73
|
+
|
|
74
|
+
// Pad behind the arp — PolySynth with longer attack
|
|
75
|
+
const pad = new Tone.PolySynth(Tone.Synth, {
|
|
76
|
+
oscillator: { type: 'triangle' },
|
|
77
|
+
envelope: { attack: 0.6, decay: 0.5, sustain: 0.7, release: 2.0 },
|
|
78
|
+
volume: -16,
|
|
79
|
+
});
|
|
80
|
+
pad.connect(reverb);
|
|
81
|
+
|
|
82
|
+
// Kick — punchy 4-on-the-floor
|
|
83
|
+
const kick = new Tone.MembraneSynth({
|
|
84
|
+
pitchDecay: 0.06, octaves: 8,
|
|
85
|
+
envelope: { attack: 0.001, decay: 0.35, sustain: 0, release: 0.1 },
|
|
86
|
+
volume: -4,
|
|
87
|
+
}).toDestination();
|
|
88
|
+
|
|
89
|
+
// Hi-hats — 16th-note machine pattern, slightly gated
|
|
90
|
+
const hihat = new Tone.NoiseSynth({
|
|
91
|
+
noise: { type: 'white' },
|
|
92
|
+
envelope: { attack: 0.001, decay: 0.04, sustain: 0, release: 0.01 },
|
|
93
|
+
volume: -16,
|
|
94
|
+
}).toDestination();
|
|
95
|
+
|
|
96
|
+
// Clap — pink noise, sits on 2 and 4
|
|
97
|
+
const clap = new Tone.NoiseSynth({
|
|
98
|
+
noise: { type: 'pink' },
|
|
99
|
+
envelope: { attack: 0.001, decay: 0.12, sustain: 0, release: 0.08 },
|
|
100
|
+
volume: -10,
|
|
101
|
+
}).toDestination();
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Composition Structure
|
|
105
|
+
|
|
106
|
+
1. **Intro (0-2s):** Arp enters alone — no drums yet. Establishes the hazy tone.
|
|
107
|
+
2. **Drop-in (2-4s):** Kick and hi-hat join. Clap enters on 2 and 4.
|
|
108
|
+
3. **Full loop (4-12s):** All elements running. Pad adds chord warmth behind the arp.
|
|
109
|
+
4. **Variation (optional):** Drop the kick for 2 bars, let the arp breathe.
|
|
110
|
+
|
|
111
|
+
**Chord cycle (4 chords, each 1 bar):**
|
|
112
|
+
- Am → F → C → G (classic 80s nostalgia loop)
|
|
113
|
+
- Em → C → G → D (brighter alternative)
|
|
114
|
+
- Dm → Bb → F → C (darker, more melancholic)
|
|
115
|
+
|
|
116
|
+
## Example Variations
|
|
117
|
+
|
|
118
|
+
### Variation 1: Slower / dreamier
|
|
119
|
+
```js
|
|
120
|
+
Tone.Transport.bpm.value = 88;
|
|
121
|
+
// Longer attack on arp: 0.04
|
|
122
|
+
// Chorus depth: 0.85, wet: 0.7
|
|
123
|
+
// Reverb decay: 5.0 — more smear
|
|
124
|
+
// Drop hi-hats to 8th notes instead of 16th
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Variation 2: More synthwave, less chill
|
|
128
|
+
```js
|
|
129
|
+
Tone.Transport.bpm.value = 108;
|
|
130
|
+
// Square oscillator instead of sawtooth
|
|
131
|
+
// Add a bass line: MonoSynth root notes, sawtooth, lowpass filter at 400hz
|
|
132
|
+
// Kick volume up: -2
|
|
133
|
+
// Less reverb: 0.3 wet
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Variation 3: Pure pads (no drums)
|
|
137
|
+
```js
|
|
138
|
+
// Just the chord pad + arp, massive chorus + reverb
|
|
139
|
+
// Pad attack: 2.0s, release: 4.0s
|
|
140
|
+
// This is the most "chill" direction — remove percussion entirely
|
|
141
|
+
// Use a second delayed arp an octave higher for shimmer
|
|
142
|
+
```
|