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,135 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>TuneFrames — Lo-fi Hip Hop</title>
|
|
5
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<div id="tuneframes" style="display:none">{"bpm":80,"duration":"12s"}</div>
|
|
9
|
+
<script>
|
|
10
|
+
async function main() {
|
|
11
|
+
await Tone.start();
|
|
12
|
+
Tone.Transport.bpm.value = 80;
|
|
13
|
+
|
|
14
|
+
// ── Effects chain ───────────────────────────────────────────────────
|
|
15
|
+
const reverb = new Tone.Reverb({ decay: 3.5, preDelay: 0.01, wet: 0.55 }).toDestination();
|
|
16
|
+
await reverb.ready;
|
|
17
|
+
const lpFilter = new Tone.Filter(1800, 'lowpass').connect(reverb);
|
|
18
|
+
const tape = new Tone.Distortion(0.04).connect(lpFilter);
|
|
19
|
+
|
|
20
|
+
// ── Soft pink noise (vinyl warmth) ──────────────────────────────────
|
|
21
|
+
const noiseLp = new Tone.Filter(600, 'lowpass').connect(reverb);
|
|
22
|
+
const vinylNoise = new Tone.Noise('pink').connect(noiseLp);
|
|
23
|
+
vinylNoise.volume.value = -42;
|
|
24
|
+
vinylNoise.start(0).stop('+12');
|
|
25
|
+
|
|
26
|
+
// ── Rhodes-style keys ───────────────────────────────────────────────
|
|
27
|
+
const keys = new Tone.PolySynth(Tone.Synth, {
|
|
28
|
+
oscillator: { type: 'triangle' },
|
|
29
|
+
envelope: { attack: 0.05, decay: 0.3, sustain: 0.7, release: 2.0 }
|
|
30
|
+
}).connect(tape);
|
|
31
|
+
keys.volume.value = -10;
|
|
32
|
+
|
|
33
|
+
// Am7 – Fmaj7 – Cmaj7 – G7
|
|
34
|
+
const chords = [
|
|
35
|
+
['A3','C4','E4','G4'],
|
|
36
|
+
['F3','A3','C4','E4'],
|
|
37
|
+
['C3','E3','G3','B3'],
|
|
38
|
+
['G3','B3','D4','F4'],
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
// 2 beats per chord at 80 BPM = 1.5s per chord
|
|
42
|
+
const step = Tone.Time('2n').toSeconds();
|
|
43
|
+
chords.forEach((ch, i) => keys.triggerAttackRelease(ch, '2n', i * step));
|
|
44
|
+
// Second pass — loop the chords again from 6s
|
|
45
|
+
chords.forEach((ch, i) => keys.triggerAttackRelease(ch, '2n', 6 + i * step));
|
|
46
|
+
|
|
47
|
+
// ── Walking bass ────────────────────────────────────────────────────
|
|
48
|
+
const bassFilter = new Tone.Filter(600, 'lowpass').connect(reverb);
|
|
49
|
+
const bass = new Tone.Synth({
|
|
50
|
+
oscillator: { type: 'sine' },
|
|
51
|
+
envelope: { attack: 0.02, decay: 0.12, sustain: 0.5, release: 0.5 }
|
|
52
|
+
}).connect(bassFilter);
|
|
53
|
+
bass.volume.value = -6;
|
|
54
|
+
|
|
55
|
+
// Root + passing tone on each chord (quarter notes)
|
|
56
|
+
const bassLines = [
|
|
57
|
+
['A2','C3'],
|
|
58
|
+
['F2','G2'],
|
|
59
|
+
['C2','E2'],
|
|
60
|
+
['G2','A2'],
|
|
61
|
+
];
|
|
62
|
+
bassLines.forEach(([root, pass], i) => {
|
|
63
|
+
bass.triggerAttackRelease(root, '4n', i * step);
|
|
64
|
+
bass.triggerAttackRelease(pass, '4n', i * step + step * 0.5);
|
|
65
|
+
});
|
|
66
|
+
bassLines.forEach(([root, pass], i) => {
|
|
67
|
+
bass.triggerAttackRelease(root, '4n', 6 + i * step);
|
|
68
|
+
bass.triggerAttackRelease(pass, '4n', 6 + i * step + step * 0.5);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// ── Muffled kick ────────────────────────────────────────────────────
|
|
72
|
+
const kickLp = new Tone.Filter(200, 'lowpass').connect(reverb);
|
|
73
|
+
const kick = new Tone.MembraneSynth({
|
|
74
|
+
pitchDecay: 0.05, octaves: 5,
|
|
75
|
+
envelope: { attack: 0.001, decay: 0.4, sustain: 0, release: 0.4 }
|
|
76
|
+
}).connect(kickLp);
|
|
77
|
+
kick.volume.value = -14;
|
|
78
|
+
|
|
79
|
+
const beat = 60 / 80; // 0.75s
|
|
80
|
+
// Kick on beats 1 and 3 of each bar (2 bars = 12 beats total)
|
|
81
|
+
for (let bar = 0; bar < 4; bar++) {
|
|
82
|
+
kick.triggerAttackRelease('C1', '8n', bar * beat * 4);
|
|
83
|
+
kick.triggerAttackRelease('C1', '8n', bar * beat * 4 + beat * 2);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Snare (soft) ────────────────────────────────────────────────────
|
|
87
|
+
const snare = new Tone.NoiseSynth({
|
|
88
|
+
noise: { type: 'white' },
|
|
89
|
+
envelope: { attack: 0.001, decay: 0.12, sustain: 0, release: 0.05 }
|
|
90
|
+
}).connect(new Tone.Filter(3000, 'bandpass').connect(reverb));
|
|
91
|
+
snare.volume.value = -22;
|
|
92
|
+
|
|
93
|
+
// Snare on beats 2 and 4
|
|
94
|
+
for (let bar = 0; bar < 4; bar++) {
|
|
95
|
+
snare.triggerAttackRelease('8n', bar * beat * 4 + beat);
|
|
96
|
+
snare.triggerAttackRelease('8n', bar * beat * 4 + beat * 3);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Soft hi-hat (swung 8ths) ────────────────────────────────────────
|
|
100
|
+
const hat = new Tone.MetalSynth({
|
|
101
|
+
frequency: 400,
|
|
102
|
+
envelope: { attack: 0.001, decay: 0.06, release: 0.01 },
|
|
103
|
+
harmonicity: 5.1, modulationIndex: 32, resonance: 4000, octaves: 1.5
|
|
104
|
+
}).connect(reverb);
|
|
105
|
+
hat.volume.value = -28;
|
|
106
|
+
|
|
107
|
+
// Swung 8th notes: on beat, then slightly late (lazy swing)
|
|
108
|
+
for (let i = 0; i < 16; i++) {
|
|
109
|
+
const swing = (i % 2 === 1) ? 0.05 : 0; // slight swing delay on offbeats
|
|
110
|
+
hat.triggerAttackRelease('16n', i * beat * 0.5 + swing);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Melody fragment (enters bar 3, ~4.5s) ───────────────────────────
|
|
114
|
+
const melodyDelay = new Tone.FeedbackDelay({ delayTime: '8n', feedback: 0.25, wet: 0.3 }).connect(reverb);
|
|
115
|
+
const melody = new Tone.Synth({
|
|
116
|
+
oscillator: { type: 'sine' },
|
|
117
|
+
envelope: { attack: 0.02, decay: 0.2, sustain: 0.3, release: 0.8 }
|
|
118
|
+
}).connect(melodyDelay);
|
|
119
|
+
melody.volume.value = -16;
|
|
120
|
+
|
|
121
|
+
// Simple pentatonic phrase over bars 3–4 (starting at 4.5s)
|
|
122
|
+
const phrase = [
|
|
123
|
+
{ note: 'E5', time: 4.5, dur: '8n' },
|
|
124
|
+
{ note: 'C5', time: 5.0, dur: '8n' },
|
|
125
|
+
{ note: 'A4', time: 5.5, dur: '4n' },
|
|
126
|
+
{ note: 'G4', time: 6.5, dur: '8n' },
|
|
127
|
+
{ note: 'A4', time: 7.0, dur: '4n' },
|
|
128
|
+
{ note: 'E5', time: 7.75, dur: '8n' },
|
|
129
|
+
{ note: 'C5', time: 8.25, dur: '2n' },
|
|
130
|
+
];
|
|
131
|
+
phrase.forEach(({ note, time, dur }) => melody.triggerAttackRelease(note, dur, time));
|
|
132
|
+
}
|
|
133
|
+
</script>
|
|
134
|
+
</body>
|
|
135
|
+
</html>
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: audio-minimal
|
|
3
|
+
description: Minimal techno — Robert Hood / Villalobos style. A click, a sub, a single hi-hat, one repeating note. Every sound has massive space. Groove lives in subtle velocity variation and silence.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TuneFrames — Minimal Techno
|
|
7
|
+
|
|
8
|
+
## Genre Profile
|
|
9
|
+
- BPM range: 128–135
|
|
10
|
+
- Key characteristics: Extreme sparsity — 3 to 5 sounds maximum, massive amounts of silence, subtle velocity and timing micro-variations create the groove, no melodic content or filtered sweeps, hypnotic through repetition and restraint
|
|
11
|
+
- Typical instruments: MembraneSynth (kick/click), one MetalSynth or NoiseSynth (hi-hat), one MonoSynth (single repeating note or sub), maybe one sparse PolySynth texture
|
|
12
|
+
- Mood: Hypnotic, austere, cerebral, meditative, relentless
|
|
13
|
+
|
|
14
|
+
## Core Pattern
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
// Minimal Techno — 132 BPM
|
|
18
|
+
// Rule: if you're adding a sound, ask whether removing it makes it better.
|
|
19
|
+
// The answer is usually yes.
|
|
20
|
+
|
|
21
|
+
// Click kick — not a deep sub, more of a transient "click"
|
|
22
|
+
const kick = new Tone.MembraneSynth({
|
|
23
|
+
pitchDecay: 0.02, // very short pitch decay = more click, less boom
|
|
24
|
+
octaves: 4,
|
|
25
|
+
envelope: { attack: 0.001, decay: 0.18, sustain: 0, release: 0.05 }
|
|
26
|
+
}).toDestination();
|
|
27
|
+
|
|
28
|
+
// Sub — single note, all session, barely moving
|
|
29
|
+
const sub = new Tone.MonoSynth({
|
|
30
|
+
oscillator: { type: "sine" },
|
|
31
|
+
envelope: { attack: 0.01, decay: 0.3, sustain: 0.6, release: 0.2 },
|
|
32
|
+
filter: { type: "lowpass", frequency: 120 },
|
|
33
|
+
volume: -6
|
|
34
|
+
}).toDestination();
|
|
35
|
+
|
|
36
|
+
// Hi-hat — one hit, lots of space, slight velocity drift
|
|
37
|
+
const hat = new Tone.MetalSynth({
|
|
38
|
+
frequency: 400,
|
|
39
|
+
envelope: { attack: 0.001, decay: 0.06, release: 0.015 },
|
|
40
|
+
harmonicity: 3.1, modulationIndex: 16, resonance: 3000, octaves: 1.2,
|
|
41
|
+
volume: -12
|
|
42
|
+
}).toDestination();
|
|
43
|
+
|
|
44
|
+
// The groove is here: velocity micro-variation on the kick
|
|
45
|
+
let step = 0;
|
|
46
|
+
const vels = [0.9, 0.75, 0.85, 0.7, 0.9, 0.8, 0.78, 0.85]; // never quite the same
|
|
47
|
+
new Tone.Sequence((time) => {
|
|
48
|
+
kick.triggerAttackRelease("C1", "8n", time, vels[step % vels.length]);
|
|
49
|
+
step++;
|
|
50
|
+
}, [1, null, null, null, 1, null, null, null,
|
|
51
|
+
1, null, null, null, 1, null, null, null], "16n").start(0);
|
|
52
|
+
|
|
53
|
+
// Hat: only on the "and" of beat 4 — one hit per bar
|
|
54
|
+
new Tone.Sequence((time, val) => {
|
|
55
|
+
if (val) hat.triggerAttackRelease("16n", time, 0.55 + Math.random() * 0.12);
|
|
56
|
+
}, [null, null, null, null, null, null, null, null,
|
|
57
|
+
null, null, null, null, null, null, 1, null], "16n").start(0);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Instrument Configuration
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
// Kick — transient-heavy click, minimal sub content
|
|
64
|
+
const kick = new Tone.MembraneSynth({
|
|
65
|
+
pitchDecay: 0.018, // ultra-short = it's mostly a click/transient
|
|
66
|
+
octaves: 4,
|
|
67
|
+
volume: -2,
|
|
68
|
+
envelope: { attack: 0.001, decay: 0.16, sustain: 0, release: 0.04 }
|
|
69
|
+
}).toDestination();
|
|
70
|
+
|
|
71
|
+
// Sub tone — sine wave, barely audible, felt not heard
|
|
72
|
+
// This is the floor the kick sits on
|
|
73
|
+
const sub = new Tone.MonoSynth({
|
|
74
|
+
oscillator: { type: "sine" },
|
|
75
|
+
filter: { type: "lowpass", frequency: 100 },
|
|
76
|
+
envelope: { attack: 0.008, decay: 0.25, sustain: 0.7, release: 0.3 },
|
|
77
|
+
volume: -10
|
|
78
|
+
}).toDestination();
|
|
79
|
+
|
|
80
|
+
// Hi-hat — single sparse hit, slightly random velocity = humanization
|
|
81
|
+
const hat = new Tone.MetalSynth({
|
|
82
|
+
frequency: 380, harmonicity: 3.1, modulationIndex: 16,
|
|
83
|
+
resonance: 3200, octaves: 1.2,
|
|
84
|
+
envelope: { attack: 0.001, decay: 0.065, release: 0.018 },
|
|
85
|
+
volume: -14
|
|
86
|
+
}).toDestination();
|
|
87
|
+
|
|
88
|
+
// Optional: one sparse texture note
|
|
89
|
+
// A single note (Tone.Synth) with long release and deep reverb
|
|
90
|
+
// Triggered once every 4-8 bars — the only "melodic" element
|
|
91
|
+
const textureVerb = new Tone.Reverb({ decay: 8.0, wet: 0.85 }).toDestination();
|
|
92
|
+
const texture = new Tone.Synth({
|
|
93
|
+
oscillator: { type: "sine" },
|
|
94
|
+
envelope: { attack: 0.2, decay: 2.0, sustain: 0, release: 3.0 },
|
|
95
|
+
volume: -18
|
|
96
|
+
}).connect(textureVerb);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Composition Structure
|
|
100
|
+
|
|
101
|
+
Minimal techno is NOT about adding — it's about what you don't play.
|
|
102
|
+
|
|
103
|
+
- **Bar 1-4:** Kick only. Establish the pulse. Let silence speak.
|
|
104
|
+
- **Bar 5-8:** Add sub note on beat 1 only. Four bars of kick + one sub note per bar.
|
|
105
|
+
- **Bar 9-16:** Add hat — ONE hit per bar, on beat 4.5 (last 16th). That's it.
|
|
106
|
+
- **Bar 17-32:** No changes. Let the loop breathe. Micro-velocity variation IS the movement.
|
|
107
|
+
- **Bar 33:** Remove the hat. Just kick + sub. Feel the absence.
|
|
108
|
+
- **Bar 37:** Bring hat back at different position in the bar.
|
|
109
|
+
- **Bar 41:** Single texture note — sine wave through 8s reverb. Play it once. Don't repeat it for 8 bars.
|
|
110
|
+
- **Outro:** Remove sub. Just kick. Then silence mid-beat.
|
|
111
|
+
|
|
112
|
+
The key insight: in minimal, a sound you've heard 32 times and then REMOVED is louder in its absence than it ever was playing. Use this. Remove things. Let the memory of the sound carry the groove.
|
|
113
|
+
|
|
114
|
+
Micro-variation toolkit:
|
|
115
|
+
- Velocity: never the same value twice in a row — vary ±10% randomly
|
|
116
|
+
- Timing: slight humanization (`time + (Math.random() - 0.5) * 0.005`) on the hat only
|
|
117
|
+
- Filter: one slow, almost-imperceptible filter movement over 32 bars on the sub
|
|
118
|
+
- Volume: a 2dB swell over 8 bars, back down — the listener should feel it not notice it
|
|
119
|
+
|
|
120
|
+
## Example Variations
|
|
121
|
+
|
|
122
|
+
### 1 — Hat timing humanization (the Villalobos move)
|
|
123
|
+
```js
|
|
124
|
+
// Subtle timing displacement on hat = the groove isn't in the kick, it's here
|
|
125
|
+
hat.triggerAttackRelease(
|
|
126
|
+
"16n",
|
|
127
|
+
time + (Math.random() - 0.5) * 0.006, // ±6ms drift
|
|
128
|
+
0.5 + Math.random() * 0.15
|
|
129
|
+
);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 2 — Sub filter drift (imperceptible movement)
|
|
133
|
+
```js
|
|
134
|
+
// Extremely slow filter sweep — listener feels unease without knowing why
|
|
135
|
+
const now = Tone.now();
|
|
136
|
+
subFilter.frequency.setValueAtTime(90, now);
|
|
137
|
+
subFilter.frequency.linearRampToValueAtTime(140, now + 30); // over 30 seconds
|
|
138
|
+
subFilter.frequency.linearRampToValueAtTime(90, now + 60);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 3 — Silence as event (drop the kick for 1 bar)
|
|
142
|
+
```js
|
|
143
|
+
// Most powerful move in minimal: remove kick for exactly one bar
|
|
144
|
+
// Use a Tone.Part to exclude one specific bar from the sequence
|
|
145
|
+
// The groove snaps back harder when the kick returns
|
|
146
|
+
let kickEnabled = true;
|
|
147
|
+
const kickSeq = new Tone.Sequence((time) => {
|
|
148
|
+
if (kickEnabled) kick.triggerAttackRelease("C1", "8n", time);
|
|
149
|
+
}, [1, null, null, null, 1, null, null, null,
|
|
150
|
+
1, null, null, null, 1, null, null, null], "16n");
|
|
151
|
+
|
|
152
|
+
// Disable for bar 9 only
|
|
153
|
+
Tone.Transport.scheduleOnce(() => { kickEnabled = false; }, "8m");
|
|
154
|
+
Tone.Transport.scheduleOnce(() => { kickEnabled = true; }, "9m");
|
|
155
|
+
```
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>TuneFrames — Minimal Techno</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"
|
|
11
|
+
data-bpm="132"
|
|
12
|
+
data-duration="12s"
|
|
13
|
+
data-genre="minimal"
|
|
14
|
+
data-key="none"
|
|
15
|
+
style="display:none">
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
async function main() {
|
|
20
|
+
await Tone.start();
|
|
21
|
+
Tone.Transport.bpm.value = 132;
|
|
22
|
+
|
|
23
|
+
// --- MASTER BUS ---
|
|
24
|
+
const masterLimiter = new Tone.Limiter(-2).toDestination();
|
|
25
|
+
const masterGain = new Tone.Gain(0.9).connect(masterLimiter);
|
|
26
|
+
|
|
27
|
+
// --- KICK CLICK — transient-forward, minimal sub ---
|
|
28
|
+
const kickGain = new Tone.Gain(0.8).connect(masterGain);
|
|
29
|
+
const kick = new Tone.MembraneSynth({
|
|
30
|
+
pitchDecay: 0.019,
|
|
31
|
+
octaves: 4,
|
|
32
|
+
envelope: { attack: 0.001, decay: 0.16, sustain: 0, release: 0.04 }
|
|
33
|
+
}).connect(kickGain);
|
|
34
|
+
|
|
35
|
+
// --- SUB TONE — sine, low-passed, felt not heard ---
|
|
36
|
+
const subGain = new Tone.Gain(0.55).connect(masterGain);
|
|
37
|
+
const subFilter = new Tone.Filter({ frequency: 110, type: "lowpass", Q: 1 }).connect(subGain);
|
|
38
|
+
const sub = new Tone.MonoSynth({
|
|
39
|
+
oscillator: { type: "sine" },
|
|
40
|
+
filter: { type: "lowpass", frequency: 200 },
|
|
41
|
+
filterEnvelope: { attack: 0.01, decay: 0.3, sustain: 0.6, release: 0.3,
|
|
42
|
+
baseFrequency: 80, octaves: 1 },
|
|
43
|
+
envelope: { attack: 0.008, decay: 0.22, sustain: 0.65, release: 0.25 },
|
|
44
|
+
volume: -4
|
|
45
|
+
}).connect(subFilter);
|
|
46
|
+
|
|
47
|
+
// --- HI-HAT — sparse, single hit per bar, humanized ---
|
|
48
|
+
const hatGain = new Tone.Gain(0.35).connect(masterGain);
|
|
49
|
+
const hat = new Tone.MetalSynth({
|
|
50
|
+
frequency: 385,
|
|
51
|
+
envelope: { attack: 0.001, decay: 0.062, release: 0.018 },
|
|
52
|
+
harmonicity: 3.1,
|
|
53
|
+
modulationIndex: 16,
|
|
54
|
+
resonance: 3100,
|
|
55
|
+
octaves: 1.2
|
|
56
|
+
}).connect(hatGain);
|
|
57
|
+
|
|
58
|
+
// --- TEXTURE — single sine note, massive reverb, triggered once ---
|
|
59
|
+
const textureVerb = new Tone.Reverb({ decay: 7.5, wet: 0.88 }).connect(masterGain);
|
|
60
|
+
const textureGain = new Tone.Gain(0.3).connect(textureVerb);
|
|
61
|
+
const texture = new Tone.Synth({
|
|
62
|
+
oscillator: { type: "sine" },
|
|
63
|
+
envelope: { attack: 0.25, decay: 2.5, sustain: 0, release: 4.0 },
|
|
64
|
+
volume: -2
|
|
65
|
+
}).connect(textureGain);
|
|
66
|
+
|
|
67
|
+
// --- KICK SEQUENCE — 4-on-the-floor with subtle velocity drift ---
|
|
68
|
+
// The groove IS the velocity variation — never fully uniform
|
|
69
|
+
const kickVels = [0.88, 0.74, 0.84, 0.71, 0.90, 0.76, 0.82, 0.70,
|
|
70
|
+
0.91, 0.73, 0.85, 0.72, 0.88, 0.75, 0.83, 0.74];
|
|
71
|
+
let kickStep = 0;
|
|
72
|
+
const kickSeq = new Tone.Sequence((time) => {
|
|
73
|
+
kick.triggerAttackRelease("C1", "8n", time, kickVels[kickStep % kickVels.length]);
|
|
74
|
+
kickStep++;
|
|
75
|
+
}, [1, null, null, null, 1, null, null, null,
|
|
76
|
+
1, null, null, null, 1, null, null, null], "16n");
|
|
77
|
+
kickSeq.start(0);
|
|
78
|
+
|
|
79
|
+
// --- SUB SEQUENCE — one hit per bar, beat 1 only ---
|
|
80
|
+
// Enters at bar 3 — two bars of kick-only silence first
|
|
81
|
+
const subSeq = new Tone.Sequence((time) => {
|
|
82
|
+
sub.triggerAttackRelease("C1", "4n", time);
|
|
83
|
+
}, [1, null, null, null, null, null, null, null,
|
|
84
|
+
null, null, null, null, null, null, null, null], "16n");
|
|
85
|
+
subSeq.start("2m");
|
|
86
|
+
|
|
87
|
+
// Very slow filter drift on sub — imperceptible but felt
|
|
88
|
+
const subDriftStart = Tone.Time("2m").toSeconds();
|
|
89
|
+
subFilter.frequency.setValueAtTime(100, subDriftStart);
|
|
90
|
+
subFilter.frequency.linearRampToValueAtTime(145, subDriftStart + 8);
|
|
91
|
+
subFilter.frequency.linearRampToValueAtTime(95, subDriftStart + 12);
|
|
92
|
+
|
|
93
|
+
// --- HI-HAT SEQUENCE — ONE hit per bar, on last 16th (step 15) ---
|
|
94
|
+
// Slight timing humanization = the Villalobos groove
|
|
95
|
+
// Enters at bar 5
|
|
96
|
+
const hatSeq = new Tone.Sequence((time) => {
|
|
97
|
+
const jitter = (Math.random() - 0.5) * 0.005; // ±5ms humanization
|
|
98
|
+
const vel = 0.48 + Math.random() * 0.16; // velocity drift
|
|
99
|
+
hat.triggerAttackRelease("16n", time + jitter, vel);
|
|
100
|
+
}, [null, null, null, null, null, null, null, null,
|
|
101
|
+
null, null, null, null, null, null, 1, null], "16n");
|
|
102
|
+
hatSeq.start("4m");
|
|
103
|
+
|
|
104
|
+
// --- TEXTURE NOTE — single event, bar 7, D above middle ---
|
|
105
|
+
// Plays once. Never repeats. The reverb tail carries it.
|
|
106
|
+
texture.triggerAttackRelease("D4", "8n", "6m");
|
|
107
|
+
|
|
108
|
+
// --- STRUCTURAL EVENT: remove hat for exactly one bar (bar 9), then return ---
|
|
109
|
+
// Absence as rhythm — the most minimal move there is
|
|
110
|
+
Tone.Transport.scheduleOnce(() => { hatSeq.mute = true; }, "8m");
|
|
111
|
+
Tone.Transport.scheduleOnce(() => { hatSeq.mute = false; }, "9m");
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
main();
|
|
116
|
+
</script>
|
|
117
|
+
</body>
|
|
118
|
+
</html>
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: audio-orchestral
|
|
3
|
+
description: Full orchestral writing — strings carry melody, brass punctuate, timpani on downbeats, woodwind color
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TuneFrames — Orchestral
|
|
7
|
+
|
|
8
|
+
## Genre Profile
|
|
9
|
+
- BPM range: 60-120 (varies by mood — slow for drama, faster for action)
|
|
10
|
+
- Key characteristics: proper voice leading between parts, strings as primary melodic voice, brass on structural downbeats, timpani emphasizes phrase endings, woodwinds add color between string phrases
|
|
11
|
+
- Typical instruments: PolySynth triangle (strings), PolySynth sawtooth (brass), MembraneSynth (timpani), AMSynth (woodwinds)
|
|
12
|
+
- Mood: cinematic, epic, emotional, noble
|
|
13
|
+
|
|
14
|
+
## Core Pattern
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
// Orchestral texture: strings carry the tune, brass confirm the harmony
|
|
18
|
+
// Key = D minor, BPM = 80
|
|
19
|
+
Tone.Transport.bpm.value = 80;
|
|
20
|
+
|
|
21
|
+
// Hall reverb — essential for orchestral realism
|
|
22
|
+
const reverb = new Tone.Reverb({ decay: 3.5, preDelay: 0.04, wet: 0.7 });
|
|
23
|
+
await reverb.generate();
|
|
24
|
+
reverb.toDestination();
|
|
25
|
+
|
|
26
|
+
// STRINGS — PolySynth, triangle wave, slow attack (bowing)
|
|
27
|
+
const strings = new Tone.PolySynth(Tone.Synth, {
|
|
28
|
+
oscillator: { type: 'triangle' },
|
|
29
|
+
envelope: { attack: 0.8, decay: 0.3, sustain: 0.9, release: 1.5 },
|
|
30
|
+
volume: -6,
|
|
31
|
+
});
|
|
32
|
+
strings.connect(reverb);
|
|
33
|
+
|
|
34
|
+
// BRASS — PolySynth, sawtooth, medium attack (breath to tone)
|
|
35
|
+
const brass = new Tone.PolySynth(Tone.Synth, {
|
|
36
|
+
oscillator: { type: 'sawtooth' },
|
|
37
|
+
envelope: { attack: 0.3, decay: 0.4, sustain: 0.7, release: 0.8 },
|
|
38
|
+
volume: -10,
|
|
39
|
+
});
|
|
40
|
+
brass.connect(reverb);
|
|
41
|
+
|
|
42
|
+
// TIMPANI — MembraneSynth with long resonance
|
|
43
|
+
const timpani = new Tone.MembraneSynth({
|
|
44
|
+
pitchDecay: 0.4, octaves: 4,
|
|
45
|
+
envelope: { attack: 0.001, decay: 1.2, sustain: 0, release: 0.8 },
|
|
46
|
+
volume: -8,
|
|
47
|
+
}).connect(reverb);
|
|
48
|
+
|
|
49
|
+
// Strings: melody phrase (quarter notes + half notes)
|
|
50
|
+
// D minor: D E F G A Bb A G
|
|
51
|
+
const stringMelody = ['D4','E4','F4','G4','A4','Bb4','A4','G4'];
|
|
52
|
+
const stringTimes = [0, 0.75, 1.5, 2.25, 3, 3.75, 4.5, 5.25];
|
|
53
|
+
stringMelody.forEach((note, i) => {
|
|
54
|
+
Tone.Transport.scheduleOnce(time => {
|
|
55
|
+
strings.triggerAttackRelease(note, '4n', time);
|
|
56
|
+
}, stringTimes[i]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Brass: hold root + fifth on downbeats, confirm the harmony
|
|
60
|
+
Tone.Transport.scheduleOnce(t => brass.triggerAttackRelease(['D3','A3'], '2n', t), 0);
|
|
61
|
+
Tone.Transport.scheduleOnce(t => brass.triggerAttackRelease(['F3','C4'], '2n', t), 3);
|
|
62
|
+
Tone.Transport.scheduleOnce(t => brass.triggerAttackRelease(['G3','D4'], '2n', t), 6);
|
|
63
|
+
|
|
64
|
+
// Timpani: roll on downbeats — D2 as the root
|
|
65
|
+
[0, 3, 6].forEach(t => {
|
|
66
|
+
Tone.Transport.scheduleOnce(time => timpani.triggerAttackRelease('D2', '8n', time), t);
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Instrument Configuration
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
// Hall reverb — orchestras play in halls
|
|
74
|
+
const reverb = new Tone.Reverb({ decay: 3.5, preDelay: 0.05, wet: 0.7 });
|
|
75
|
+
await reverb.generate();
|
|
76
|
+
reverb.toDestination();
|
|
77
|
+
|
|
78
|
+
// STRINGS (Violin section)
|
|
79
|
+
// Triangle wave approximates the bowed string harmonic spectrum (rough, not sine)
|
|
80
|
+
// Slow attack simulates bow catching the string
|
|
81
|
+
const strings = new Tone.PolySynth(Tone.Synth, {
|
|
82
|
+
oscillator: { type: 'triangle' },
|
|
83
|
+
envelope: { attack: 0.8, decay: 0.2, sustain: 0.95, release: 1.5 },
|
|
84
|
+
volume: -5,
|
|
85
|
+
}).connect(reverb);
|
|
86
|
+
|
|
87
|
+
// BRASS (French horns / Trumpets)
|
|
88
|
+
// Sawtooth gives the harmonic richness of a brass instrument
|
|
89
|
+
// Medium attack = breath control into tone
|
|
90
|
+
const brass = new Tone.PolySynth(Tone.Synth, {
|
|
91
|
+
oscillator: { type: 'sawtooth' },
|
|
92
|
+
envelope: { attack: 0.35, decay: 0.3, sustain: 0.75, release: 1.0 },
|
|
93
|
+
volume: -9,
|
|
94
|
+
}).connect(reverb);
|
|
95
|
+
|
|
96
|
+
// WOODWINDS (Oboe / Flute color)
|
|
97
|
+
// AMSynth provides the reedy, breathy quality
|
|
98
|
+
const woodwinds = new Tone.AMSynth({
|
|
99
|
+
harmonicity: 1,
|
|
100
|
+
oscillator: { type: 'triangle' },
|
|
101
|
+
envelope: { attack: 0.15, decay: 0.1, sustain: 0.85, release: 0.6 },
|
|
102
|
+
modulation: { type: 'square' },
|
|
103
|
+
modulationEnvelope: { attack: 0.5, decay: 0.0, sustain: 0.3, release: 0.5 },
|
|
104
|
+
volume: -16,
|
|
105
|
+
}).connect(reverb);
|
|
106
|
+
|
|
107
|
+
// TIMPANI
|
|
108
|
+
// MembraneSynth: pitchDecay simulates the drum head ringing
|
|
109
|
+
const timpani = new Tone.MembraneSynth({
|
|
110
|
+
pitchDecay: 0.35, octaves: 5,
|
|
111
|
+
envelope: { attack: 0.001, decay: 1.5, sustain: 0, release: 1.0 },
|
|
112
|
+
volume: -7,
|
|
113
|
+
}).connect(reverb);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Composition Structure
|
|
117
|
+
|
|
118
|
+
**Phrase-based (not loop-based):**
|
|
119
|
+
|
|
120
|
+
1. **Statement (0-3s):** Strings introduce the melody. Brass hold the tonic chord quietly. Timpani on beat 1.
|
|
121
|
+
2. **Response (3-6s):** Strings move to a contrasting phrase. Brass shift to IV or V chord. Woodwinds enter between string notes.
|
|
122
|
+
3. **Development (6-9s):** Melody moves to upper strings (higher octave). Brass build in volume. Timpani on phrase downbeats.
|
|
123
|
+
4. **Cadence (9-12s):** All voices converge on a half-cadence (ends on V) or full cadence (ends on I). Timpani rolls.
|
|
124
|
+
|
|
125
|
+
**Voice leading rules:**
|
|
126
|
+
- Strings: move by step or small intervals. Avoid leaps larger than a 5th in the melody.
|
|
127
|
+
- Brass: hold common tones between chords when possible. Move the voice that needs to move.
|
|
128
|
+
- Woodwinds: fill in the 3rd or 7th of the chord — the color tones strings and brass don't cover.
|
|
129
|
+
|
|
130
|
+
## Example Variations
|
|
131
|
+
|
|
132
|
+
### Variation 1: Heroic major (C major, faster)
|
|
133
|
+
```js
|
|
134
|
+
Tone.Transport.bpm.value = 108;
|
|
135
|
+
// Key: C major — C D E F G A B C
|
|
136
|
+
// Brass on I, IV, V, I (C, F, G, C)
|
|
137
|
+
// Timpani on C2, G2 alternating
|
|
138
|
+
// Strings lead in 8th notes — more energetic
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Variation 2: Tragic minor (A minor, slow)
|
|
142
|
+
```js
|
|
143
|
+
Tone.Transport.bpm.value = 66;
|
|
144
|
+
// Key: A minor — A B C D E F G A
|
|
145
|
+
// Very slow strings: half notes and whole notes only
|
|
146
|
+
// Brass only enter at climax (bar 3), very quiet before that
|
|
147
|
+
// Timpani: soft rolls (8 rapid 32nd notes) instead of single hits
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Variation 3: Mysterious woodwind feature
|
|
151
|
+
```js
|
|
152
|
+
// Woodwinds take the melody for 4 bars — strings accompany
|
|
153
|
+
// Strings: slow tremolo (fast repeating 32nd notes on a held chord)
|
|
154
|
+
// Woodwinds: stepwise descending line D4→C4→B3→A3→G3
|
|
155
|
+
// No brass, no timpani — intimate chamber feel
|
|
156
|
+
```
|