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.
Files changed (78) hide show
  1. package/LICENSE +167 -199
  2. package/README.md +150 -97
  3. package/examples/example-ai-dj-chill.html +167 -0
  4. package/examples/example-ai-dj-dark.html +167 -0
  5. package/examples/example-ai-dj-energetic.html +167 -0
  6. package/examples/example-ai-dj-happy.html +167 -0
  7. package/examples/example-ai-dj.html +167 -0
  8. package/examples/example-ambient.html +63 -63
  9. package/examples/example-ambient.mp3 +0 -0
  10. package/examples/example-bass.html +48 -0
  11. package/examples/example-lofi.html +45 -45
  12. package/examples/example-lofi.mp3 +0 -0
  13. package/examples/example-minimal.html +21 -19
  14. package/examples/example-minimal.mp3 +0 -0
  15. package/examples/example-orchestral.html +67 -67
  16. package/examples/example-orchestral.mp3 +0 -0
  17. package/examples/example-piano.html +53 -0
  18. package/examples/example-piano.mp3 +0 -0
  19. package/examples/example-techno.html +69 -69
  20. package/examples/example-techno.mp3 +0 -0
  21. package/package.json +42 -24
  22. package/registry/presets/bass-electric.html +67 -0
  23. package/registry/presets/bass-saw.html +22 -0
  24. package/registry/presets/chord-progression.html +22 -0
  25. package/registry/presets/drums-808.html +85 -0
  26. package/registry/presets/drums-lofi.html +35 -0
  27. package/registry/presets/lead-piano.html +26 -0
  28. package/registry/presets/piano-salamander.html +69 -0
  29. package/registry/presets/reverb-warm.html +11 -0
  30. package/registry/samples.json +226 -0
  31. package/skills/audio-ambient/SKILL.md +141 -0
  32. package/skills/audio-ambient/example.html +113 -0
  33. package/skills/audio-boss-battle/SKILL.md +157 -0
  34. package/skills/audio-boss-battle/example.html +185 -0
  35. package/skills/audio-boss-battle/example.mp3 +0 -0
  36. package/skills/audio-chillwave/SKILL.md +142 -0
  37. package/skills/audio-chillwave/example.html +144 -0
  38. package/skills/audio-cinematic/SKILL.md +147 -0
  39. package/skills/audio-cinematic/example.html +123 -0
  40. package/skills/audio-classical/SKILL.md +138 -0
  41. package/skills/audio-classical/example.html +145 -0
  42. package/skills/audio-dnb/SKILL.md +142 -0
  43. package/skills/audio-dnb/example.html +124 -0
  44. package/skills/audio-downtempo/SKILL.md +152 -0
  45. package/skills/audio-downtempo/example.html +164 -0
  46. package/skills/audio-folk/SKILL.md +139 -0
  47. package/skills/audio-folk/example.html +132 -0
  48. package/skills/audio-funk/SKILL.md +149 -0
  49. package/skills/audio-funk/example.html +144 -0
  50. package/skills/audio-future-bass/SKILL.md +163 -0
  51. package/skills/audio-future-bass/example.html +164 -0
  52. package/skills/audio-hip-hop/SKILL.md +133 -0
  53. package/skills/audio-hip-hop/example.html +129 -0
  54. package/skills/audio-house/SKILL.md +147 -0
  55. package/skills/audio-house/example.html +128 -0
  56. package/skills/audio-indie-pop/SKILL.md +150 -0
  57. package/skills/audio-indie-pop/example.html +121 -0
  58. package/skills/audio-jazz/SKILL.md +141 -0
  59. package/skills/audio-jazz/example.html +146 -0
  60. package/skills/audio-lofi/SKILL.md +140 -0
  61. package/skills/audio-lofi/example.html +135 -0
  62. package/skills/audio-minimal/SKILL.md +155 -0
  63. package/skills/audio-minimal/example.html +118 -0
  64. package/skills/audio-orchestral/SKILL.md +156 -0
  65. package/skills/audio-orchestral/example.html +140 -0
  66. package/skills/audio-r-and-b/SKILL.md +134 -0
  67. package/skills/audio-r-and-b/example.html +154 -0
  68. package/skills/audio-r-and-b/example.mp3 +0 -0
  69. package/skills/audio-techno/SKILL.md +140 -0
  70. package/skills/audio-techno/example.html +125 -0
  71. package/skills/audio-trap/SKILL.md +123 -0
  72. package/skills/audio-trap/example.html +119 -0
  73. package/skills/render-retest/example.html +115 -0
  74. package/skills/tuneframes/SKILL.md +221 -0
  75. package/skills/tuneframes-cli/SKILL.md +46 -0
  76. package/skills/verify/example.html +104 -0
  77. package/src/cli.js +261 -89
  78. package/src/render.js +134 -7
@@ -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
+ ```
@@ -0,0 +1,140 @@
1
+ <div id="tuneframes" style="display:none">{"bpm":80,"duration":"14s"}</div>
2
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
3
+ <script>
4
+ // TuneFrames — Orchestral
5
+ // D minor: strings carry melody, brass confirm harmony,
6
+ // woodwinds add color, timpani on downbeats
7
+ // Proper voice leading — each section has a role
8
+
9
+ async function main() {
10
+ await Tone.start();
11
+
12
+ const beat = 60 / 80; // 0.75s per quarter note
13
+ const half = beat * 2; // 1.5s per half note
14
+
15
+ // ── Hall reverb — all orchestral sound happens in a hall ──────────────────
16
+ const reverb = new Tone.Reverb({ decay: 3.5, preDelay: 0.05, wet: 0.7 });
17
+ await reverb.generate();
18
+ reverb.toDestination();
19
+
20
+ // ── STRINGS — PolySynth triangle, slow bow-attack ─────────────────────────
21
+ const strings = new Tone.PolySynth(Tone.Synth, {
22
+ oscillator: { type: 'triangle' },
23
+ envelope: { attack: 0.75, decay: 0.2, sustain: 0.95, release: 1.5 },
24
+ volume: -5,
25
+ });
26
+ strings.connect(reverb);
27
+
28
+ // ── BRASS — PolySynth sawtooth, medium breath-attack ─────────────────────
29
+ const brass = new Tone.PolySynth(Tone.Synth, {
30
+ oscillator: { type: 'sawtooth' },
31
+ envelope: { attack: 0.32, decay: 0.35, sustain: 0.72, release: 1.0 },
32
+ volume: -10,
33
+ });
34
+ brass.connect(reverb);
35
+
36
+ // ── WOODWINDS — AMSynth for reedy, breathy character ─────────────────────
37
+ const woodwinds = new Tone.AMSynth({
38
+ harmonicity: 1.0,
39
+ oscillator: { type: 'triangle' },
40
+ envelope: { attack: 0.14, decay: 0.1, sustain: 0.88, release: 0.6 },
41
+ modulation: { type: 'square' },
42
+ modulationEnvelope: { attack: 0.5, decay: 0.0, sustain: 0.3, release: 0.5 },
43
+ volume: -16,
44
+ });
45
+ woodwinds.connect(reverb);
46
+
47
+ // ── TIMPANI — MembraneSynth, long resonating pitch ───────────────────────
48
+ const timpani = new Tone.MembraneSynth({
49
+ pitchDecay: 0.38, octaves: 5,
50
+ envelope: { attack: 0.001, decay: 1.6, sustain: 0, release: 1.0 },
51
+ volume: -7,
52
+ });
53
+ timpani.connect(reverb);
54
+
55
+ // ─────────────────────────────────────────────────────────────────────────
56
+ // PHRASE 1 (0-6s): Statement — strings introduce D minor melody
57
+ // D minor scale ascending: D E F G A Bb A G
58
+ // ─────────────────────────────────────────────────────────────────────────
59
+
60
+ const phrase1 = [
61
+ { note: 'D4', t: 0 },
62
+ { note: 'E4', t: beat * 0.5 },
63
+ { note: 'F4', t: beat * 1 },
64
+ { note: 'G4', t: beat * 1.5 },
65
+ { note: 'A4', t: beat * 2 },
66
+ { note: 'Bb4', t: beat * 2.5 },
67
+ { note: 'A4', t: beat * 3 },
68
+ { note: 'G4', t: beat * 3.5 },
69
+ ];
70
+ phrase1.forEach(({ note, t }) => strings.triggerAttackRelease(note, '4n', t));
71
+
72
+ // Brass: tonic Dm for 3 beats, then IV (Gm)
73
+ brass.triggerAttackRelease(['D3', 'F3', 'A3'], half, 0);
74
+ brass.triggerAttackRelease(['G2', 'Bb2', 'D3'], half, beat * 3);
75
+
76
+ // Timpani: downbeat on D2
77
+ timpani.triggerAttackRelease('D2', '8n', 0);
78
+
79
+ // ─────────────────────────────────────────────────────────────────────────
80
+ // PHRASE 2 (6-10s): Response — melody rises, woodwinds fill
81
+ // Half-cadence: ends on A major (V chord)
82
+ // ─────────────────────────────────────────────────────────────────────────
83
+
84
+ const p2start = beat * 8; // 6s
85
+
86
+ const phrase2 = [
87
+ { note: 'F4', t: p2start },
88
+ { note: 'A4', t: p2start + beat * 0.5 },
89
+ { note: 'C5', t: p2start + beat * 1 },
90
+ { note: 'D5', t: p2start + beat * 1.5 },
91
+ { note: 'E5', t: p2start + beat * 2 },
92
+ { note: 'F5', t: p2start + beat * 2.5 },
93
+ ];
94
+ phrase2.forEach(({ note, t }) => strings.triggerAttackRelease(note, '4n', t));
95
+
96
+ // Woodwinds: fill inner voices between string notes
97
+ const woodwindNotes = [
98
+ { note: 'A4', t: p2start + beat * 0.25 },
99
+ { note: 'C5', t: p2start + beat * 1.25 },
100
+ { note: 'F4', t: p2start + beat * 2.25 },
101
+ ];
102
+ woodwindNotes.forEach(({ note, t }) => woodwinds.triggerAttackRelease(note, '4n', t));
103
+
104
+ // Brass: IV (Bb) → V (A major) — half cadence
105
+ brass.triggerAttackRelease(['Bb2', 'D3', 'F3'], half, p2start);
106
+ brass.triggerAttackRelease(['A2', 'C#3', 'E3'], half, p2start + beat * 3);
107
+
108
+ timpani.triggerAttackRelease('G2', '8n', p2start);
109
+ timpani.triggerAttackRelease('A2', '8n', p2start + beat * 3);
110
+
111
+ // ─────────────────────────────────────────────────────────────────────────
112
+ // PHRASE 3 (10-14s): Resolution — full cadence I (Dm), all voices converge
113
+ // ─────────────────────────────────────────────────────────────────────────
114
+
115
+ const p3start = p2start + beat * 6; // ~10.5s
116
+
117
+ const phrase3 = [
118
+ { note: 'F5', t: p3start },
119
+ { note: 'E5', t: p3start + beat * 0.5 },
120
+ { note: 'D5', t: p3start + beat * 1 },
121
+ { note: 'C5', t: p3start + beat * 1.5 },
122
+ { note: 'A4', t: p3start + beat * 2 },
123
+ { note: 'F4', t: p3start + beat * 2.5 },
124
+ { note: 'D4', t: p3start + beat * 3 },
125
+ ];
126
+ phrase3.forEach(({ note, t }) => {
127
+ if (t < 14) strings.triggerAttackRelease(note, '4n', t);
128
+ });
129
+
130
+ // Brass: full Dm resolution chord
131
+ brass.triggerAttackRelease(['D3', 'F3', 'A3', 'D4'], beat * 4, p3start);
132
+
133
+ // Woodwinds: hold the 5th above brass
134
+ woodwinds.triggerAttackRelease('F5', beat * 3, p3start);
135
+
136
+ // Timpani: final downbeats on D2
137
+ timpani.triggerAttackRelease('D2', '4n', p3start);
138
+ timpani.triggerAttackRelease('D2', '4n', p3start + beat * 3);
139
+ }
140
+ </script>