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,221 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tuneframes
|
|
3
|
+
description: Write music compositions in HTML using Tone.js. Use when asked to create music, audio, sound effects, beats, or any audio generation task.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TuneFrames
|
|
7
|
+
|
|
8
|
+
Open-source music generation: write HTML with Tone.js, render to MP3 with one CLI command. Built for AI agents — no native audio code, no per-render fees, fully deterministic.
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
- User asks for music, a beat, soundtrack, sound effects, or audio
|
|
13
|
+
- User describes a musical mood (lofi, ambient, techno, orchestral) and wants output
|
|
14
|
+
- User wants audio for a video, game, or application
|
|
15
|
+
- User wants to generate music programmatically in an automated pipeline
|
|
16
|
+
|
|
17
|
+
## Core Authoring Pattern
|
|
18
|
+
|
|
19
|
+
Every TuneFrames composition is a single HTML file:
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<!DOCTYPE html>
|
|
23
|
+
<html>
|
|
24
|
+
<head>
|
|
25
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
|
|
26
|
+
</head>
|
|
27
|
+
<body>
|
|
28
|
+
<div id="tuneframes" style="display:none">{"bpm":120,"duration":"4s"}</div>
|
|
29
|
+
<script>
|
|
30
|
+
async function main() {
|
|
31
|
+
await Tone.start();
|
|
32
|
+
const synth = new Tone.Synth().toDestination();
|
|
33
|
+
synth.triggerAttackRelease('C4', '4n', 0);
|
|
34
|
+
}
|
|
35
|
+
</script>
|
|
36
|
+
</body>
|
|
37
|
+
</html>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Metadata Block
|
|
41
|
+
|
|
42
|
+
```html
|
|
43
|
+
<div id="tuneframes" style="display:none">{"bpm":120,"duration":"4s"}</div>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- **bpm**: beats per minute (default: 120)
|
|
47
|
+
- **duration**: render length in **seconds** — use `"4s"`, `"10s"`, etc. Do NOT use Tone.js note values like `"4n"` for duration (see warning below)
|
|
48
|
+
|
|
49
|
+
## The `main()` Function Rules
|
|
50
|
+
|
|
51
|
+
1. Must be `async function main()`
|
|
52
|
+
2. Must start with `await Tone.start()`
|
|
53
|
+
3. All notes are scheduled relative to transport time 0
|
|
54
|
+
4. Tone.Offline renders deterministically — same HTML = identical MP3 every time
|
|
55
|
+
|
|
56
|
+
## Instruments
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
// Basic tone generation
|
|
60
|
+
const synth = new Tone.Synth().toDestination();
|
|
61
|
+
const mono = new Tone.MonoSynth().toDestination();
|
|
62
|
+
const poly = new Tone.PolySynth(Tone.Synth).toDestination();
|
|
63
|
+
|
|
64
|
+
// Drums
|
|
65
|
+
const kick = new Tone.MembraneSynth().toDestination(); // Kick drum
|
|
66
|
+
const noise = new Tone.NoiseSynth().toDestination(); // Hi-hats, snares
|
|
67
|
+
|
|
68
|
+
// Effects
|
|
69
|
+
const reverb = new Tone.Reverb({ decay: 2.5, wet: 0.3 }).toDestination();
|
|
70
|
+
const delay = new Tone.FeedbackDelay('8n', 0.4).toDestination();
|
|
71
|
+
const comp = new Tone.Compressor(-12, 2).toDestination();
|
|
72
|
+
const chorus = new Tone.Chorus(4, 2.5, 0.5).toDestination();
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Common Patterns
|
|
76
|
+
|
|
77
|
+
**Note timing:**
|
|
78
|
+
```js
|
|
79
|
+
const beat = Tone.Time('4n').toSeconds();
|
|
80
|
+
synth.triggerAttackRelease('C4', '4n', 0); // Beat 1
|
|
81
|
+
synth.triggerAttackRelease('E4', '4n', beat); // Beat 2
|
|
82
|
+
synth.triggerAttackRelease('G4', '4n', beat * 2); // Beat 3
|
|
83
|
+
synth.triggerAttackRelease('C5', '4n', beat * 3); // Beat 4
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Chord progression:**
|
|
87
|
+
```js
|
|
88
|
+
const pad = new Tone.PolySynth(Tone.Synth).toDestination();
|
|
89
|
+
const chords = [
|
|
90
|
+
['D3','F3','A3'], // Dm
|
|
91
|
+
['C3','E3','G3'], // C
|
|
92
|
+
['Bb2','D3','F3'], // Bb
|
|
93
|
+
['A2','C3','E3'], // Am
|
|
94
|
+
];
|
|
95
|
+
const measure = Tone.Time('1n').toSeconds();
|
|
96
|
+
chords.forEach((chord, i) => {
|
|
97
|
+
pad.triggerAttackRelease(chord, '1n', i * measure);
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Drum pattern:**
|
|
102
|
+
```js
|
|
103
|
+
const kick = new Tone.MembraneSynth().toDestination();
|
|
104
|
+
const snare = new Tone.NoiseSynth().toDestination();
|
|
105
|
+
const beat = Tone.Time('4n').toSeconds();
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < 4; i++) {
|
|
108
|
+
kick.triggerAttackRelease('C1', '4n', i * beat); // Kick on 1-4
|
|
109
|
+
snare.triggerAttackRelease('C3', '4n', (i + 0.5) * beat); // Snare on 2,4
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Lofi beat (full example):**
|
|
114
|
+
```js
|
|
115
|
+
const kick = new Tone.MembraneSynth().toDestination();
|
|
116
|
+
const snare = new Tone.NoiseSynth().toDestination();
|
|
117
|
+
const hihat = new Tone.NoiseSynth().toDestination();
|
|
118
|
+
const piano = new Tone.PolySynth(Tone.Synth).toDestination();
|
|
119
|
+
const reverb = new Tone.Reverb({ decay: 1.5, wet: 0.4 }).toDestination();
|
|
120
|
+
|
|
121
|
+
const beat = Tone.Time('4n').toSeconds();
|
|
122
|
+
const bar = beat * 4;
|
|
123
|
+
|
|
124
|
+
// Kick: four on the floor
|
|
125
|
+
for (let i = 0; i < 4; i++) kick.triggerAttackRelease('C1', '4n', i * beat);
|
|
126
|
+
|
|
127
|
+
// Snare: 2 and 4
|
|
128
|
+
[1, 3].forEach(i => snare.triggerAttackRelease('C3', '4n', i * beat));
|
|
129
|
+
|
|
130
|
+
// Hi-hat: eighth notes, quiet
|
|
131
|
+
hihat.volume.value = -12;
|
|
132
|
+
for (let i = 0; i < 8; i++) hihat.triggerAttackRelease('16n', i * beat * 0.5);
|
|
133
|
+
|
|
134
|
+
// Piano: chord progression
|
|
135
|
+
const progression = [['D3','F3','A3'],['C3','E3','G3']];
|
|
136
|
+
progression.forEach((chord, i) => {
|
|
137
|
+
piano.connect(reverb);
|
|
138
|
+
piano.triggerAttackRelease(chord, '2n', i * bar);
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## ⚠️ Duration Warning
|
|
143
|
+
|
|
144
|
+
**Use literal seconds (`"4s"`, `"10s"`) for the metadata duration.**
|
|
145
|
+
|
|
146
|
+
Tone.js note notation (`4n`, `2n`, `1n`) is a fraction of a whole note at the given BPM:
|
|
147
|
+
- `1n` = 1 whole note = 4 beats → at 120 BPM, 1n = 2.0s
|
|
148
|
+
- `2n` = 1/2 whole note = 2 beats → at 120 BPM, 2n = 1.0s
|
|
149
|
+
- `4n` = 1/4 whole note = 1 beat → at 120 BPM, 4n = 0.5s
|
|
150
|
+
- `8n` = 1/8 whole note = 1/2 beat → at 120 BPM, 8n = 0.25s
|
|
151
|
+
|
|
152
|
+
This is a common source of clipped renders. Always use seconds in the metadata block.
|
|
153
|
+
|
|
154
|
+
## CLI Reference
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
tuneframes render <file.html> [--output <out.mp3>] # Render to MP3
|
|
158
|
+
tuneframes render <file.html> --format wav [--output <out.wav>]
|
|
159
|
+
tuneframes preview <file.html> # Live preview in browser
|
|
160
|
+
tuneframes init <name> # Scaffold new project
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
# TuneFrames — Sample Instruments
|
|
166
|
+
|
|
167
|
+
Extends the core TuneFrames skill with real instrument samples via CDN.
|
|
168
|
+
|
|
169
|
+
## Sample Pattern (Tone.Sampler + gleitz CDN)
|
|
170
|
+
|
|
171
|
+
Every sample composition must:
|
|
172
|
+
1. Create Tone.Sampler instances with CDN baseUrl
|
|
173
|
+
2. Call `await Tone.loaded()` before scheduling any notes
|
|
174
|
+
3. Then schedule all notes using the loaded sampler
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
async function main() {
|
|
178
|
+
await Tone.start();
|
|
179
|
+
|
|
180
|
+
// Load piano from CDN
|
|
181
|
+
const piano = new Tone.Sampler({
|
|
182
|
+
urls: { A4: 'A4.mp3', C4: 'C4.mp3', 'F#4': 'Fs4.mp3', A5: 'A5.mp3' },
|
|
183
|
+
baseUrl: 'https://gleitz.github.io/midi-js-soundfonts/FluidR3_GM/acoustic_grand_piano-mp3/'
|
|
184
|
+
}).toDestination();
|
|
185
|
+
|
|
186
|
+
// CRITICAL: wait for samples to load before scheduling
|
|
187
|
+
await Tone.loaded();
|
|
188
|
+
|
|
189
|
+
// Now schedule notes
|
|
190
|
+
piano.triggerAttackRelease('C4', '4n', 0);
|
|
191
|
+
piano.triggerAttackRelease('E4', '4n', '4n');
|
|
192
|
+
piano.triggerAttackRelease('G4', '2n', '2n');
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Available Instruments (gleitz FluidR3_GM)
|
|
197
|
+
|
|
198
|
+
Full registry: `registry/samples.json` — includes all URL mappings and per-instrument notes.
|
|
199
|
+
|
|
200
|
+
| Key | CDN path suffix | Category | Best range |
|
|
201
|
+
|-----|----------------|----------|------------|
|
|
202
|
+
| `acoustic_grand_piano` | `acoustic_grand_piano-mp3` | Piano | A0–C8 |
|
|
203
|
+
| `acoustic_bass` | `acoustic_bass-mp3` | Bass | C1–C4 |
|
|
204
|
+
| `string_ensemble_1` | `string_ensemble_1-mp3` | Strings | C2–C7 |
|
|
205
|
+
| `brass_section` | `brass_section-mp3` | Brass | C2–C6 |
|
|
206
|
+
| `acoustic_guitar_nylon` | `acoustic_guitar_nylon-mp3` | Guitar | Fs2–C6 |
|
|
207
|
+
| `vibraphone` | `vibraphone-mp3` | Mallet | C3–C7 |
|
|
208
|
+
| `flute` | `flute-mp3` | Woodwind | C4–A7 |
|
|
209
|
+
| `choir_aahs` | `choir_aahs-mp3` | Choir | C3–G5 |
|
|
210
|
+
|
|
211
|
+
CDN base: `https://gleitz.github.io/midi-js-soundfonts/FluidR3_GM/{instrument}-mp3/`
|
|
212
|
+
|
|
213
|
+
Sharp notes use `s` suffix in filenames: F#4 → `Fs4.mp3`, D#3 → `Ds3.mp3`.
|
|
214
|
+
|
|
215
|
+
## Presets
|
|
216
|
+
|
|
217
|
+
Ready-to-render compositions in `registry/presets/`:
|
|
218
|
+
|
|
219
|
+
- `piano-salamander.html` — Am–F–C–G chord progression, acoustic grand piano, 8s
|
|
220
|
+
- `drums-808.html` — Trap 808 pattern, MembraneSynth + MetalSynth (no CDN), 7s
|
|
221
|
+
- `bass-electric.html` — Walking bass line in C minor, acoustic bass, 6s
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tuneframes-cli
|
|
3
|
+
description: CLI commands for TuneFrames — render, preview, init, validate. Load this skill when working with the tuneframes CLI.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TuneFrames CLI
|
|
7
|
+
|
|
8
|
+
## Commands
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# Render composition to MP3
|
|
12
|
+
tuneframes render <file.html> [--output <out.mp3>]
|
|
13
|
+
|
|
14
|
+
# Render to WAV (no re-encoding)
|
|
15
|
+
tuneframes render <file.html> --format wav [--output <out.wav>]
|
|
16
|
+
|
|
17
|
+
# Preview in browser (live reload)
|
|
18
|
+
tuneframes preview <file.html>
|
|
19
|
+
|
|
20
|
+
# Scaffold a new project
|
|
21
|
+
tuneframes init my-track
|
|
22
|
+
|
|
23
|
+
# Validate a composition (headless render test)
|
|
24
|
+
tuneframes validate <file.html>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Rendering Pipeline
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
HTML + Tone.js → Chromium (headless, Tone.Offline)
|
|
31
|
+
→ WAV (PCM 44.1kHz mono)
|
|
32
|
+
→ FFmpeg
|
|
33
|
+
→ MP3 192kbps
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Tone.Offline renders without audio hardware. Same input HTML = identical output MP3. This is what makes TuneFrames safe for automated pipelines.
|
|
37
|
+
|
|
38
|
+
## Requirements
|
|
39
|
+
|
|
40
|
+
- Node.js >= 18
|
|
41
|
+
- FFmpeg (install via: `apt install ffmpeg` or `brew install ffmpeg`)
|
|
42
|
+
|
|
43
|
+
## Output Locations
|
|
44
|
+
|
|
45
|
+
Default: input file's directory with same basename, `.mp3` extension.
|
|
46
|
+
`--output` flag overrides: `tuneframes render in.html --output /tmp/out.mp3`
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>TuneFrames — Verify</title>
|
|
6
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="tuneframes" style="display:none">{"bpm":120,"duration":"10s"}</div>
|
|
10
|
+
<script>
|
|
11
|
+
// Verify — upbeat electronic, C major, I-vi-IV-V-I arc. "System checked out: PASS."
|
|
12
|
+
//
|
|
13
|
+
// Instrument choices follow confirmed-working pattern (techno/lofi examples):
|
|
14
|
+
// - Tone.Reverb without await — generate() resolves before startRendering() inside Offline
|
|
15
|
+
// - Tone.MembraneSynth kick — confirmed working in example-techno.html
|
|
16
|
+
// - Tone.NoiseSynth hi-hat — confirmed working; MetalSynth fails inside Tone.Offline
|
|
17
|
+
// (MetalSynth's FM oscillators use sustain:0 early-stop scheduling which creates
|
|
18
|
+
// a state-timeline ordering conflict on re-trigger in the Offline context)
|
|
19
|
+
// - main() NOT called from HTML — render.js calls it inside Tone.Offline()
|
|
20
|
+
|
|
21
|
+
async function main() {
|
|
22
|
+
await Tone.start();
|
|
23
|
+
|
|
24
|
+
const beat = Tone.Time('4n').toSeconds(); // 0.5s at 120 BPM
|
|
25
|
+
const bar = beat * 4; // 2.0s
|
|
26
|
+
|
|
27
|
+
// Effects
|
|
28
|
+
const reverb = new Tone.Reverb({ decay: 2.2, wet: 0.28 }).toDestination();
|
|
29
|
+
const delay = new Tone.FeedbackDelay({ delayTime: beat, feedback: 0.28, wet: 0.22 }).toDestination();
|
|
30
|
+
|
|
31
|
+
// Chord pad — PolySynth(Synth) → reverb
|
|
32
|
+
const pad = new Tone.PolySynth(Tone.Synth, {
|
|
33
|
+
oscillator: { type: 'triangle' },
|
|
34
|
+
envelope: { attack: 0.08, decay: 0.40, sustain: 0.60, release: 1.20 }
|
|
35
|
+
}).connect(reverb);
|
|
36
|
+
pad.volume.value = -10;
|
|
37
|
+
|
|
38
|
+
// Melody — Synth → delay
|
|
39
|
+
const mel = new Tone.Synth({
|
|
40
|
+
oscillator: { type: 'sawtooth' },
|
|
41
|
+
envelope: { attack: 0.01, decay: 0.15, sustain: 0.40, release: 0.30 }
|
|
42
|
+
}).connect(delay);
|
|
43
|
+
mel.volume.value = -9;
|
|
44
|
+
|
|
45
|
+
// Kick — MembraneSynth
|
|
46
|
+
const kick = new Tone.MembraneSynth({
|
|
47
|
+
pitchDecay: 0.05,
|
|
48
|
+
octaves: 6,
|
|
49
|
+
oscillator: { type: 'sine' },
|
|
50
|
+
envelope: { attack: 0.001, decay: 0.28, sustain: 0, release: 0.08 }
|
|
51
|
+
}).toDestination();
|
|
52
|
+
kick.volume.value = -5;
|
|
53
|
+
|
|
54
|
+
// Hi-hat — NoiseSynth (white noise burst, similar timbre to metallic hat)
|
|
55
|
+
// NoiseSynth works inside Tone.Offline; MetalSynth does not in v14.8.49
|
|
56
|
+
const hihat = new Tone.NoiseSynth({
|
|
57
|
+
noise: { type: 'white' },
|
|
58
|
+
envelope: { attack: 0.001, decay: 0.05, sustain: 0, release: 0.01 }
|
|
59
|
+
}).toDestination();
|
|
60
|
+
hihat.volume.value = -16;
|
|
61
|
+
|
|
62
|
+
// Kick: 4-on-the-floor, 20 hits across 10s
|
|
63
|
+
for (var i = 0; i < 20; i++) {
|
|
64
|
+
kick.triggerAttackRelease('C1', '8n', i * beat);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Hi-hat: off-beat 8th notes, 19 hits
|
|
68
|
+
for (var j = 0; j < 19; j++) {
|
|
69
|
+
var vel = j % 2 === 0 ? 0.60 : 0.32;
|
|
70
|
+
hihat.triggerAttackRelease('16n', beat * 0.5 + j * beat, vel);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Chords: I-vi-IV-V-I (C-Am-F-G-C), one per bar
|
|
74
|
+
[
|
|
75
|
+
{ notes: ['C4', 'E4', 'G4'], t: bar * 0 },
|
|
76
|
+
{ notes: ['A3', 'C4', 'E4'], t: bar * 1 },
|
|
77
|
+
{ notes: ['F3', 'A3', 'C4'], t: bar * 2 },
|
|
78
|
+
{ notes: ['G3', 'B3', 'D4'], t: bar * 3 },
|
|
79
|
+
{ notes: ['C4', 'E4', 'G4'], t: bar * 4 },
|
|
80
|
+
].forEach(function(c) {
|
|
81
|
+
pad.triggerAttackRelease(c.notes, '1n', c.t);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Melody: question phrase (bars 2-3), answer resolves to tonic (bars 4-5)
|
|
85
|
+
var e = beat * 0.5; // eighth-note duration
|
|
86
|
+
[
|
|
87
|
+
[bar + beat * 0, 'E5', e],
|
|
88
|
+
[bar + beat * 1, 'G5', e],
|
|
89
|
+
[bar + beat * 2, 'A5', beat],
|
|
90
|
+
[bar * 2 + beat * 0, 'G5', e],
|
|
91
|
+
[bar * 2 + beat * 1, 'F5', e],
|
|
92
|
+
[bar * 2 + beat * 2, 'E5', beat],
|
|
93
|
+
[bar * 3 + beat * 0, 'D5', e],
|
|
94
|
+
[bar * 3 + beat * 1, 'E5', e],
|
|
95
|
+
[bar * 3 + beat * 2, 'F5', e],
|
|
96
|
+
[bar * 3 + beat * 3, 'G5', e],
|
|
97
|
+
[bar * 4, 'C5', beat * 3],
|
|
98
|
+
].forEach(function(n) {
|
|
99
|
+
mel.triggerAttackRelease(n[1], n[2], n[0]);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
</script>
|
|
103
|
+
</body>
|
|
104
|
+
</html>
|