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,167 @@
|
|
|
1
|
+
<script src="https://unpkg.com/tone@14.7.77/build/Tone.js"></script>
|
|
2
|
+
<script>
|
|
3
|
+
// ─── AI DJ — mood-parameterized Tone.js composition ────────────────────────
|
|
4
|
+
// Renders different music based on ?mood= query param or window.MOOD
|
|
5
|
+
// Supported moods: chill | energetic | dark | happy
|
|
6
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
const MOOD = (() => {
|
|
9
|
+
const p = 'energetic'; // forced
|
|
10
|
+
return ['chill', 'energetic', 'dark', 'happy'].includes(p) ? p : 'chill';
|
|
11
|
+
})();
|
|
12
|
+
|
|
13
|
+
const CONFIG = {
|
|
14
|
+
chill: {
|
|
15
|
+
bpm: 72, duration: '12s',
|
|
16
|
+
scale: ['D3', 'F3', 'A3', 'C4'],
|
|
17
|
+
chords: [['D3','F3','A3'],['C3','E3','G3'],['D3','F3','A3'],['Bb2','D3','F3']],
|
|
18
|
+
leadNotes: ['D4','F4','A4','C5','D4','Bb3'],
|
|
19
|
+
leadRhythm: ['4n','4n','4n','4n','4n','4n'],
|
|
20
|
+
filterFreq: 800, reverbWet: 0.85, delayWet: 0.4, delayTime: '8n.',
|
|
21
|
+
kickTimes: [0, 2, 4, 6, 8, 10],
|
|
22
|
+
hatTimes: [1, 3, 5, 7, 9, 11],
|
|
23
|
+
snareTimes: [2, 6, 10],
|
|
24
|
+
},
|
|
25
|
+
energetic: {
|
|
26
|
+
bpm: 140, duration: '8s',
|
|
27
|
+
scale: ['C4', 'E4', 'G4', 'A4'],
|
|
28
|
+
chords: [['C4','E4','G4'],['A3','C4','E4'],['F3','A3','C4'],['G3','B3','D4']],
|
|
29
|
+
leadNotes: ['C5','E5','G5','C5','D5','E5'],
|
|
30
|
+
leadRhythm: ['8n','8n','8n','8n','8n','8n'],
|
|
31
|
+
filterFreq: 4000, reverbWet: 0.2, delayWet: 0.1, delayTime: '16n',
|
|
32
|
+
kickTimes: [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5],
|
|
33
|
+
hatTimes: [0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3, 3.25, 3.5, 3.75, 4, 4.25, 4.5, 4.75, 5, 5.25, 5.5, 5.75, 6, 6.25, 6.5, 6.75, 7, 7.25, 7.5, 7.75],
|
|
34
|
+
snareTimes: [1, 3, 5, 7],
|
|
35
|
+
},
|
|
36
|
+
dark: {
|
|
37
|
+
bpm: 80, duration: '10s',
|
|
38
|
+
scale: ['A2', 'C3', 'E3', 'G3'],
|
|
39
|
+
chords: [['A2','C3','E3'],['E2','G2','B2'],['A2','C3','E3'],['D2','F2','A2']],
|
|
40
|
+
leadNotes: ['A3','G3','E3','C3','A3','E3'],
|
|
41
|
+
leadRhythm: ['2n','2n','2n','2n','2n','2n'],
|
|
42
|
+
filterFreq: 600, reverbWet: 0.9, delayWet: 0.5, delayTime: '4n',
|
|
43
|
+
kickTimes: [0, 3, 6, 9],
|
|
44
|
+
hatTimes: [1.5, 4.5, 7.5],
|
|
45
|
+
snareTimes: [1.5, 5.5, 9.5],
|
|
46
|
+
},
|
|
47
|
+
happy: {
|
|
48
|
+
bpm: 120, duration: '8s',
|
|
49
|
+
scale: ['C4', 'E4', 'G4', 'A4'],
|
|
50
|
+
chords: [['C4','E4','G4'],['F3','A3','C4'],['G3','B3','D4'],['C4','E4','G4']],
|
|
51
|
+
leadNotes: ['C5','E5','G5','E5','D5','C5'],
|
|
52
|
+
leadRhythm: ['8n.','8n.','8n.','8n.','8n.','8n.'],
|
|
53
|
+
filterFreq: 5000, reverbWet: 0.3, delayWet: 0.2, delayTime: '8n',
|
|
54
|
+
kickTimes: [0, 1, 2, 3, 4, 5, 6, 7],
|
|
55
|
+
hatTimes: [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5],
|
|
56
|
+
snareTimes: [0.5, 2.5, 4.5, 6.5],
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const cfg = CONFIG[MOOD];
|
|
61
|
+
|
|
62
|
+
async function main() {
|
|
63
|
+
await Tone.start();
|
|
64
|
+
|
|
65
|
+
const pad = new Tone.PolySynth(Tone.Synth, {
|
|
66
|
+
oscillator: { type: 'sine' },
|
|
67
|
+
envelope: { attack: 2.0, decay: 1, sustain: 0.8, release: 3 },
|
|
68
|
+
volume: -6,
|
|
69
|
+
}).toDestination();
|
|
70
|
+
|
|
71
|
+
const lead = new Tone.Synth({
|
|
72
|
+
oscillator: { type: MOOD === 'dark' ? 'sawtooth' : MOOD === 'energetic' ? 'square' : 'triangle' },
|
|
73
|
+
envelope: { attack: 0.02, decay: 0.1, sustain: 0.3, release: 0.5 },
|
|
74
|
+
volume: -4,
|
|
75
|
+
}).toDestination();
|
|
76
|
+
|
|
77
|
+
const bass = new Tone.MonoSynth({
|
|
78
|
+
oscillator: { type: 'sawtooth' },
|
|
79
|
+
filter: { Q: 2, type: 'lowpass', frequency: cfg.filterFreq },
|
|
80
|
+
envelope: { attack: 0.01, decay: 0.3, sustain: 0.4, release: 0.4 },
|
|
81
|
+
volume: -8,
|
|
82
|
+
}).toDestination();
|
|
83
|
+
|
|
84
|
+
const reverb = new Tone.Reverb({ decay: 4.5, wet: cfg.reverbWet }).toDestination();
|
|
85
|
+
const delay = new Tone.FeedbackDelay({ delayTime: cfg.delayTime, feedback: 0.3, wet: cfg.delayWet }).toDestination();
|
|
86
|
+
pad.connect(reverb);
|
|
87
|
+
lead.connect(delay);
|
|
88
|
+
delay.toDestination();
|
|
89
|
+
|
|
90
|
+
// Chord pads
|
|
91
|
+
new Tone.Sequence((time, chord) => {
|
|
92
|
+
pad.triggerAttackRelease(chord, '2n', time);
|
|
93
|
+
}, cfg.chords, 0).start(0);
|
|
94
|
+
|
|
95
|
+
// Lead melody
|
|
96
|
+
new Tone.Sequence((time, note) => {
|
|
97
|
+
lead.triggerAttackRelease(note, '8n.', time);
|
|
98
|
+
}, cfg.leadNotes, 0).start(0);
|
|
99
|
+
|
|
100
|
+
// Bass
|
|
101
|
+
new Tone.Sequence((time, note) => {
|
|
102
|
+
bass.triggerAttackRelease(note, '4n', time);
|
|
103
|
+
}, cfg.chords.map(c => c[0]), 0).start(0);
|
|
104
|
+
|
|
105
|
+
// Kick
|
|
106
|
+
const kick = new Tone.MembraneSynth({
|
|
107
|
+
pitchDecay: 0.05, octaves: 6,
|
|
108
|
+
envelope: { attack: 0.001, decay: 0.4, sustain: 0, release: 0.1 },
|
|
109
|
+
volume: MOOD === 'chill' ? -12 : MOOD === 'dark' ? -14 : -6,
|
|
110
|
+
}).toDestination();
|
|
111
|
+
cfg.kickTimes.forEach(t => {
|
|
112
|
+
Tone.Transport.scheduleOnce((time) => {
|
|
113
|
+
kick.triggerAttackRelease('C1', '8n', time);
|
|
114
|
+
}, t);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Hi-hat
|
|
118
|
+
const hihat = new Tone.NoiseSynth({
|
|
119
|
+
noise: { type: 'white' },
|
|
120
|
+
envelope: { attack: 0.001, decay: 0.05, sustain: 0, release: 0.01 },
|
|
121
|
+
volume: MOOD === 'chill' ? -18 : -10,
|
|
122
|
+
}).toDestination();
|
|
123
|
+
cfg.hatTimes.forEach(t => {
|
|
124
|
+
Tone.Transport.scheduleOnce((time) => {
|
|
125
|
+
hihat.triggerAttackRelease('16n', time);
|
|
126
|
+
}, t);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Snare
|
|
130
|
+
const snare = new Tone.NoiseSynth({
|
|
131
|
+
noise: { type: 'pink' },
|
|
132
|
+
envelope: { attack: 0.001, decay: 0.15, sustain: 0, release: 0.1 },
|
|
133
|
+
volume: MOOD === 'chill' ? -16 : MOOD === 'dark' ? -14 : -8,
|
|
134
|
+
}).toDestination();
|
|
135
|
+
cfg.snareTimes.forEach(t => {
|
|
136
|
+
Tone.Transport.scheduleOnce((time) => {
|
|
137
|
+
snare.triggerAttackRelease('8n', time);
|
|
138
|
+
}, t);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
Tone.Transport.bpm.value = cfg.bpm;
|
|
142
|
+
Tone.Transport.start();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
window.renderComposition = async function(wavPath) {
|
|
146
|
+
let attempts = 0;
|
|
147
|
+
while (typeof Tone === 'undefined' && attempts < 50) {
|
|
148
|
+
await new Promise(r => setTimeout(r, 100));
|
|
149
|
+
attempts++;
|
|
150
|
+
}
|
|
151
|
+
if (typeof Tone === 'undefined') throw new Error('Tone not loaded');
|
|
152
|
+
|
|
153
|
+
const durationSec = Math.max(Tone.Time(cfg.duration).toSeconds() + 0.5, 2);
|
|
154
|
+
|
|
155
|
+
const buffer = await Tone.Offline(async () => { await main(); }, durationSec, 1, 44100, cfg.bpm);
|
|
156
|
+
const wav = audioBufferToWav(buffer);
|
|
157
|
+
window.writeFile(wavPath, Array.from(new Uint8Array(wav)));
|
|
158
|
+
return wav.byteLength;
|
|
159
|
+
};
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
<!-- Metadata driven by JS so each mood gets correct BPM + duration -->
|
|
163
|
+
<script>
|
|
164
|
+
document.open();
|
|
165
|
+
document.write(`<div id="tuneframes" style="display:none">{"bpm":${cfg.bpm},"duration":"${cfg.duration}"}</div>`);
|
|
166
|
+
document.close();
|
|
167
|
+
</script>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<script src="https://unpkg.com/tone@14.7.77/build/Tone.js"></script>
|
|
2
|
+
<script>
|
|
3
|
+
// ─── AI DJ — mood-parameterized Tone.js composition ────────────────────────
|
|
4
|
+
// Renders different music based on ?mood= query param or window.MOOD
|
|
5
|
+
// Supported moods: chill | energetic | dark | happy
|
|
6
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
const MOOD = (() => {
|
|
9
|
+
const p = 'happy'; // forced
|
|
10
|
+
return ['chill', 'energetic', 'dark', 'happy'].includes(p) ? p : 'chill';
|
|
11
|
+
})();
|
|
12
|
+
|
|
13
|
+
const CONFIG = {
|
|
14
|
+
chill: {
|
|
15
|
+
bpm: 72, duration: '12s',
|
|
16
|
+
scale: ['D3', 'F3', 'A3', 'C4'],
|
|
17
|
+
chords: [['D3','F3','A3'],['C3','E3','G3'],['D3','F3','A3'],['Bb2','D3','F3']],
|
|
18
|
+
leadNotes: ['D4','F4','A4','C5','D4','Bb3'],
|
|
19
|
+
leadRhythm: ['4n','4n','4n','4n','4n','4n'],
|
|
20
|
+
filterFreq: 800, reverbWet: 0.85, delayWet: 0.4, delayTime: '8n.',
|
|
21
|
+
kickTimes: [0, 2, 4, 6, 8, 10],
|
|
22
|
+
hatTimes: [1, 3, 5, 7, 9, 11],
|
|
23
|
+
snareTimes: [2, 6, 10],
|
|
24
|
+
},
|
|
25
|
+
energetic: {
|
|
26
|
+
bpm: 140, duration: '8s',
|
|
27
|
+
scale: ['C4', 'E4', 'G4', 'A4'],
|
|
28
|
+
chords: [['C4','E4','G4'],['A3','C4','E4'],['F3','A3','C4'],['G3','B3','D4']],
|
|
29
|
+
leadNotes: ['C5','E5','G5','C5','D5','E5'],
|
|
30
|
+
leadRhythm: ['8n','8n','8n','8n','8n','8n'],
|
|
31
|
+
filterFreq: 4000, reverbWet: 0.2, delayWet: 0.1, delayTime: '16n',
|
|
32
|
+
kickTimes: [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5],
|
|
33
|
+
hatTimes: [0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3, 3.25, 3.5, 3.75, 4, 4.25, 4.5, 4.75, 5, 5.25, 5.5, 5.75, 6, 6.25, 6.5, 6.75, 7, 7.25, 7.5, 7.75],
|
|
34
|
+
snareTimes: [1, 3, 5, 7],
|
|
35
|
+
},
|
|
36
|
+
dark: {
|
|
37
|
+
bpm: 80, duration: '10s',
|
|
38
|
+
scale: ['A2', 'C3', 'E3', 'G3'],
|
|
39
|
+
chords: [['A2','C3','E3'],['E2','G2','B2'],['A2','C3','E3'],['D2','F2','A2']],
|
|
40
|
+
leadNotes: ['A3','G3','E3','C3','A3','E3'],
|
|
41
|
+
leadRhythm: ['2n','2n','2n','2n','2n','2n'],
|
|
42
|
+
filterFreq: 600, reverbWet: 0.9, delayWet: 0.5, delayTime: '4n',
|
|
43
|
+
kickTimes: [0, 3, 6, 9],
|
|
44
|
+
hatTimes: [1.5, 4.5, 7.5],
|
|
45
|
+
snareTimes: [1.5, 5.5, 9.5],
|
|
46
|
+
},
|
|
47
|
+
happy: {
|
|
48
|
+
bpm: 120, duration: '8s',
|
|
49
|
+
scale: ['C4', 'E4', 'G4', 'A4'],
|
|
50
|
+
chords: [['C4','E4','G4'],['F3','A3','C4'],['G3','B3','D4'],['C4','E4','G4']],
|
|
51
|
+
leadNotes: ['C5','E5','G5','E5','D5','C5'],
|
|
52
|
+
leadRhythm: ['8n.','8n.','8n.','8n.','8n.','8n.'],
|
|
53
|
+
filterFreq: 5000, reverbWet: 0.3, delayWet: 0.2, delayTime: '8n',
|
|
54
|
+
kickTimes: [0, 1, 2, 3, 4, 5, 6, 7],
|
|
55
|
+
hatTimes: [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5],
|
|
56
|
+
snareTimes: [0.5, 2.5, 4.5, 6.5],
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const cfg = CONFIG[MOOD];
|
|
61
|
+
|
|
62
|
+
async function main() {
|
|
63
|
+
await Tone.start();
|
|
64
|
+
|
|
65
|
+
const pad = new Tone.PolySynth(Tone.Synth, {
|
|
66
|
+
oscillator: { type: 'sine' },
|
|
67
|
+
envelope: { attack: 2.0, decay: 1, sustain: 0.8, release: 3 },
|
|
68
|
+
volume: -6,
|
|
69
|
+
}).toDestination();
|
|
70
|
+
|
|
71
|
+
const lead = new Tone.Synth({
|
|
72
|
+
oscillator: { type: MOOD === 'dark' ? 'sawtooth' : MOOD === 'energetic' ? 'square' : 'triangle' },
|
|
73
|
+
envelope: { attack: 0.02, decay: 0.1, sustain: 0.3, release: 0.5 },
|
|
74
|
+
volume: -4,
|
|
75
|
+
}).toDestination();
|
|
76
|
+
|
|
77
|
+
const bass = new Tone.MonoSynth({
|
|
78
|
+
oscillator: { type: 'sawtooth' },
|
|
79
|
+
filter: { Q: 2, type: 'lowpass', frequency: cfg.filterFreq },
|
|
80
|
+
envelope: { attack: 0.01, decay: 0.3, sustain: 0.4, release: 0.4 },
|
|
81
|
+
volume: -8,
|
|
82
|
+
}).toDestination();
|
|
83
|
+
|
|
84
|
+
const reverb = new Tone.Reverb({ decay: 4.5, wet: cfg.reverbWet }).toDestination();
|
|
85
|
+
const delay = new Tone.FeedbackDelay({ delayTime: cfg.delayTime, feedback: 0.3, wet: cfg.delayWet }).toDestination();
|
|
86
|
+
pad.connect(reverb);
|
|
87
|
+
lead.connect(delay);
|
|
88
|
+
delay.toDestination();
|
|
89
|
+
|
|
90
|
+
// Chord pads
|
|
91
|
+
new Tone.Sequence((time, chord) => {
|
|
92
|
+
pad.triggerAttackRelease(chord, '2n', time);
|
|
93
|
+
}, cfg.chords, 0).start(0);
|
|
94
|
+
|
|
95
|
+
// Lead melody
|
|
96
|
+
new Tone.Sequence((time, note) => {
|
|
97
|
+
lead.triggerAttackRelease(note, '8n.', time);
|
|
98
|
+
}, cfg.leadNotes, 0).start(0);
|
|
99
|
+
|
|
100
|
+
// Bass
|
|
101
|
+
new Tone.Sequence((time, note) => {
|
|
102
|
+
bass.triggerAttackRelease(note, '4n', time);
|
|
103
|
+
}, cfg.chords.map(c => c[0]), 0).start(0);
|
|
104
|
+
|
|
105
|
+
// Kick
|
|
106
|
+
const kick = new Tone.MembraneSynth({
|
|
107
|
+
pitchDecay: 0.05, octaves: 6,
|
|
108
|
+
envelope: { attack: 0.001, decay: 0.4, sustain: 0, release: 0.1 },
|
|
109
|
+
volume: MOOD === 'chill' ? -12 : MOOD === 'dark' ? -14 : -6,
|
|
110
|
+
}).toDestination();
|
|
111
|
+
cfg.kickTimes.forEach(t => {
|
|
112
|
+
Tone.Transport.scheduleOnce((time) => {
|
|
113
|
+
kick.triggerAttackRelease('C1', '8n', time);
|
|
114
|
+
}, t);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Hi-hat
|
|
118
|
+
const hihat = new Tone.NoiseSynth({
|
|
119
|
+
noise: { type: 'white' },
|
|
120
|
+
envelope: { attack: 0.001, decay: 0.05, sustain: 0, release: 0.01 },
|
|
121
|
+
volume: MOOD === 'chill' ? -18 : -10,
|
|
122
|
+
}).toDestination();
|
|
123
|
+
cfg.hatTimes.forEach(t => {
|
|
124
|
+
Tone.Transport.scheduleOnce((time) => {
|
|
125
|
+
hihat.triggerAttackRelease('16n', time);
|
|
126
|
+
}, t);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Snare
|
|
130
|
+
const snare = new Tone.NoiseSynth({
|
|
131
|
+
noise: { type: 'pink' },
|
|
132
|
+
envelope: { attack: 0.001, decay: 0.15, sustain: 0, release: 0.1 },
|
|
133
|
+
volume: MOOD === 'chill' ? -16 : MOOD === 'dark' ? -14 : -8,
|
|
134
|
+
}).toDestination();
|
|
135
|
+
cfg.snareTimes.forEach(t => {
|
|
136
|
+
Tone.Transport.scheduleOnce((time) => {
|
|
137
|
+
snare.triggerAttackRelease('8n', time);
|
|
138
|
+
}, t);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
Tone.Transport.bpm.value = cfg.bpm;
|
|
142
|
+
Tone.Transport.start();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
window.renderComposition = async function(wavPath) {
|
|
146
|
+
let attempts = 0;
|
|
147
|
+
while (typeof Tone === 'undefined' && attempts < 50) {
|
|
148
|
+
await new Promise(r => setTimeout(r, 100));
|
|
149
|
+
attempts++;
|
|
150
|
+
}
|
|
151
|
+
if (typeof Tone === 'undefined') throw new Error('Tone not loaded');
|
|
152
|
+
|
|
153
|
+
const durationSec = Math.max(Tone.Time(cfg.duration).toSeconds() + 0.5, 2);
|
|
154
|
+
|
|
155
|
+
const buffer = await Tone.Offline(async () => { await main(); }, durationSec, 1, 44100, cfg.bpm);
|
|
156
|
+
const wav = audioBufferToWav(buffer);
|
|
157
|
+
window.writeFile(wavPath, Array.from(new Uint8Array(wav)));
|
|
158
|
+
return wav.byteLength;
|
|
159
|
+
};
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
<!-- Metadata driven by JS so each mood gets correct BPM + duration -->
|
|
163
|
+
<script>
|
|
164
|
+
document.open();
|
|
165
|
+
document.write(`<div id="tuneframes" style="display:none">{"bpm":${cfg.bpm},"duration":"${cfg.duration}"}</div>`);
|
|
166
|
+
document.close();
|
|
167
|
+
</script>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<script src="https://unpkg.com/tone@14.7.77/build/Tone.js"></script>
|
|
2
|
+
<script>
|
|
3
|
+
// ─── AI DJ — mood-parameterized Tone.js composition ────────────────────────
|
|
4
|
+
// Renders different music based on ?mood= query param or window.MOOD
|
|
5
|
+
// Supported moods: chill | energetic | dark | happy
|
|
6
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
const MOOD = (() => {
|
|
9
|
+
const p = new URLSearchParams(window.location.search).get('mood');
|
|
10
|
+
return ['chill', 'energetic', 'dark', 'happy'].includes(p) ? p : 'chill';
|
|
11
|
+
})();
|
|
12
|
+
|
|
13
|
+
const CONFIG = {
|
|
14
|
+
chill: {
|
|
15
|
+
bpm: 72, duration: '12s',
|
|
16
|
+
scale: ['D3', 'F3', 'A3', 'C4'],
|
|
17
|
+
chords: [['D3','F3','A3'],['C3','E3','G3'],['D3','F3','A3'],['Bb2','D3','F3']],
|
|
18
|
+
leadNotes: ['D4','F4','A4','C5','D4','Bb3'],
|
|
19
|
+
leadRhythm: ['4n','4n','4n','4n','4n','4n'],
|
|
20
|
+
filterFreq: 800, reverbWet: 0.85, delayWet: 0.4, delayTime: '8n.',
|
|
21
|
+
kickTimes: [0, 2, 4, 6, 8, 10],
|
|
22
|
+
hatTimes: [1, 3, 5, 7, 9, 11],
|
|
23
|
+
snareTimes: [2, 6, 10],
|
|
24
|
+
},
|
|
25
|
+
energetic: {
|
|
26
|
+
bpm: 140, duration: '8s',
|
|
27
|
+
scale: ['C4', 'E4', 'G4', 'A4'],
|
|
28
|
+
chords: [['C4','E4','G4'],['A3','C4','E4'],['F3','A3','C4'],['G3','B3','D4']],
|
|
29
|
+
leadNotes: ['C5','E5','G5','C5','D5','E5'],
|
|
30
|
+
leadRhythm: ['8n','8n','8n','8n','8n','8n'],
|
|
31
|
+
filterFreq: 4000, reverbWet: 0.2, delayWet: 0.1, delayTime: '16n',
|
|
32
|
+
kickTimes: [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5],
|
|
33
|
+
hatTimes: [0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3, 3.25, 3.5, 3.75, 4, 4.25, 4.5, 4.75, 5, 5.25, 5.5, 5.75, 6, 6.25, 6.5, 6.75, 7, 7.25, 7.5, 7.75],
|
|
34
|
+
snareTimes: [1, 3, 5, 7],
|
|
35
|
+
},
|
|
36
|
+
dark: {
|
|
37
|
+
bpm: 80, duration: '10s',
|
|
38
|
+
scale: ['A2', 'C3', 'E3', 'G3'],
|
|
39
|
+
chords: [['A2','C3','E3'],['E2','G2','B2'],['A2','C3','E3'],['D2','F2','A2']],
|
|
40
|
+
leadNotes: ['A3','G3','E3','C3','A3','E3'],
|
|
41
|
+
leadRhythm: ['2n','2n','2n','2n','2n','2n'],
|
|
42
|
+
filterFreq: 600, reverbWet: 0.9, delayWet: 0.5, delayTime: '4n',
|
|
43
|
+
kickTimes: [0, 3, 6, 9],
|
|
44
|
+
hatTimes: [1.5, 4.5, 7.5],
|
|
45
|
+
snareTimes: [1.5, 5.5, 9.5],
|
|
46
|
+
},
|
|
47
|
+
happy: {
|
|
48
|
+
bpm: 120, duration: '8s',
|
|
49
|
+
scale: ['C4', 'E4', 'G4', 'A4'],
|
|
50
|
+
chords: [['C4','E4','G4'],['F3','A3','C4'],['G3','B3','D4'],['C4','E4','G4']],
|
|
51
|
+
leadNotes: ['C5','E5','G5','E5','D5','C5'],
|
|
52
|
+
leadRhythm: ['8n.','8n.','8n.','8n.','8n.','8n.'],
|
|
53
|
+
filterFreq: 5000, reverbWet: 0.3, delayWet: 0.2, delayTime: '8n',
|
|
54
|
+
kickTimes: [0, 1, 2, 3, 4, 5, 6, 7],
|
|
55
|
+
hatTimes: [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5],
|
|
56
|
+
snareTimes: [0.5, 2.5, 4.5, 6.5],
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const cfg = CONFIG[MOOD];
|
|
61
|
+
|
|
62
|
+
async function main() {
|
|
63
|
+
await Tone.start();
|
|
64
|
+
|
|
65
|
+
const pad = new Tone.PolySynth(Tone.Synth, {
|
|
66
|
+
oscillator: { type: 'sine' },
|
|
67
|
+
envelope: { attack: 2.0, decay: 1, sustain: 0.8, release: 3 },
|
|
68
|
+
volume: -6,
|
|
69
|
+
}).toDestination();
|
|
70
|
+
|
|
71
|
+
const lead = new Tone.Synth({
|
|
72
|
+
oscillator: { type: MOOD === 'dark' ? 'sawtooth' : MOOD === 'energetic' ? 'square' : 'triangle' },
|
|
73
|
+
envelope: { attack: 0.02, decay: 0.1, sustain: 0.3, release: 0.5 },
|
|
74
|
+
volume: -4,
|
|
75
|
+
}).toDestination();
|
|
76
|
+
|
|
77
|
+
const bass = new Tone.MonoSynth({
|
|
78
|
+
oscillator: { type: 'sawtooth' },
|
|
79
|
+
filter: { Q: 2, type: 'lowpass', frequency: cfg.filterFreq },
|
|
80
|
+
envelope: { attack: 0.01, decay: 0.3, sustain: 0.4, release: 0.4 },
|
|
81
|
+
volume: -8,
|
|
82
|
+
}).toDestination();
|
|
83
|
+
|
|
84
|
+
const reverb = new Tone.Reverb({ decay: 4.5, wet: cfg.reverbWet }).toDestination();
|
|
85
|
+
const delay = new Tone.FeedbackDelay({ delayTime: cfg.delayTime, feedback: 0.3, wet: cfg.delayWet }).toDestination();
|
|
86
|
+
pad.connect(reverb);
|
|
87
|
+
lead.connect(delay);
|
|
88
|
+
delay.toDestination();
|
|
89
|
+
|
|
90
|
+
// Chord pads
|
|
91
|
+
new Tone.Sequence((time, chord) => {
|
|
92
|
+
pad.triggerAttackRelease(chord, '2n', time);
|
|
93
|
+
}, cfg.chords, 0).start(0);
|
|
94
|
+
|
|
95
|
+
// Lead melody
|
|
96
|
+
new Tone.Sequence((time, note) => {
|
|
97
|
+
lead.triggerAttackRelease(note, '8n.', time);
|
|
98
|
+
}, cfg.leadNotes, 0).start(0);
|
|
99
|
+
|
|
100
|
+
// Bass
|
|
101
|
+
new Tone.Sequence((time, note) => {
|
|
102
|
+
bass.triggerAttackRelease(note, '4n', time);
|
|
103
|
+
}, cfg.chords.map(c => c[0]), 0).start(0);
|
|
104
|
+
|
|
105
|
+
// Kick
|
|
106
|
+
const kick = new Tone.MembraneSynth({
|
|
107
|
+
pitchDecay: 0.05, octaves: 6,
|
|
108
|
+
envelope: { attack: 0.001, decay: 0.4, sustain: 0, release: 0.1 },
|
|
109
|
+
volume: MOOD === 'chill' ? -12 : MOOD === 'dark' ? -14 : -6,
|
|
110
|
+
}).toDestination();
|
|
111
|
+
cfg.kickTimes.forEach(t => {
|
|
112
|
+
Tone.Transport.scheduleOnce((time) => {
|
|
113
|
+
kick.triggerAttackRelease('C1', '8n', time);
|
|
114
|
+
}, t);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Hi-hat
|
|
118
|
+
const hihat = new Tone.NoiseSynth({
|
|
119
|
+
noise: { type: 'white' },
|
|
120
|
+
envelope: { attack: 0.001, decay: 0.05, sustain: 0, release: 0.01 },
|
|
121
|
+
volume: MOOD === 'chill' ? -18 : -10,
|
|
122
|
+
}).toDestination();
|
|
123
|
+
cfg.hatTimes.forEach(t => {
|
|
124
|
+
Tone.Transport.scheduleOnce((time) => {
|
|
125
|
+
hihat.triggerAttackRelease('16n', time);
|
|
126
|
+
}, t);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Snare
|
|
130
|
+
const snare = new Tone.NoiseSynth({
|
|
131
|
+
noise: { type: 'pink' },
|
|
132
|
+
envelope: { attack: 0.001, decay: 0.15, sustain: 0, release: 0.1 },
|
|
133
|
+
volume: MOOD === 'chill' ? -16 : MOOD === 'dark' ? -14 : -8,
|
|
134
|
+
}).toDestination();
|
|
135
|
+
cfg.snareTimes.forEach(t => {
|
|
136
|
+
Tone.Transport.scheduleOnce((time) => {
|
|
137
|
+
snare.triggerAttackRelease('8n', time);
|
|
138
|
+
}, t);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
Tone.Transport.bpm.value = cfg.bpm;
|
|
142
|
+
Tone.Transport.start();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
window.renderComposition = async function(wavPath) {
|
|
146
|
+
let attempts = 0;
|
|
147
|
+
while (typeof Tone === 'undefined' && attempts < 50) {
|
|
148
|
+
await new Promise(r => setTimeout(r, 100));
|
|
149
|
+
attempts++;
|
|
150
|
+
}
|
|
151
|
+
if (typeof Tone === 'undefined') throw new Error('Tone not loaded');
|
|
152
|
+
|
|
153
|
+
const durationSec = Math.max(Tone.Time(cfg.duration).toSeconds() + 0.5, 2);
|
|
154
|
+
|
|
155
|
+
const buffer = await Tone.Offline(async () => { await main(); }, durationSec, 1, 44100, cfg.bpm);
|
|
156
|
+
const wav = audioBufferToWav(buffer);
|
|
157
|
+
window.writeFile(wavPath, Array.from(new Uint8Array(wav)));
|
|
158
|
+
return wav.byteLength;
|
|
159
|
+
};
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
<!-- Metadata driven by JS so each mood gets correct BPM + duration -->
|
|
163
|
+
<script>
|
|
164
|
+
document.open();
|
|
165
|
+
document.write(`<div id="tuneframes" style="display:none">{"bpm":${cfg.bpm},"duration":"${cfg.duration}"}</div>`);
|
|
166
|
+
document.close();
|
|
167
|
+
</script>
|
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>TuneFrames — Ambient</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":60,"duration":"
|
|
9
|
-
<script>
|
|
10
|
-
async function main() {
|
|
11
|
-
await Tone.start();
|
|
12
|
-
|
|
13
|
-
const verb = new Tone.Reverb({ decay: 8, wet: 0.8 }).toDestination();
|
|
14
|
-
const delay = new Tone.PingPongDelay('4n', 0.3).toDestination();
|
|
15
|
-
|
|
16
|
-
// Warm pad
|
|
17
|
-
const pad = new Tone.PolySynth(Tone.Synth, {
|
|
18
|
-
oscillator: { type: 'sine' },
|
|
19
|
-
envelope: { attack: 3, decay: 1, sustain: 0.8, release: 4 }
|
|
20
|
-
}).connect(verb);
|
|
21
|
-
pad.volume.value = -8;
|
|
22
|
-
|
|
23
|
-
// Crystal high texture
|
|
24
|
-
const crystal = new Tone.PolySynth(Tone.Synth, {
|
|
25
|
-
oscillator: { type: 'sine' },
|
|
26
|
-
envelope: { attack: 4, decay: 0.5, sustain: 1, release: 3 }
|
|
27
|
-
}).connect(delay);
|
|
28
|
-
crystal.volume.value = -18;
|
|
29
|
-
|
|
30
|
-
// D minor progression — hypnotic and spacious
|
|
31
|
-
const chords = [
|
|
32
|
-
['D3', 'F3', 'A3'], // Dm
|
|
33
|
-
['C3', 'E3', 'G3'], // C
|
|
34
|
-
['Bb2', 'D3', 'F3'], // Bb
|
|
35
|
-
['A2', 'C3', 'E3'], // Am
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
const beat = Tone.Time('1n').toSeconds();
|
|
39
|
-
|
|
40
|
-
chords.forEach((chord, i) => {
|
|
41
|
-
pad.triggerAttackRelease(chord, '1n', i * beat);
|
|
42
|
-
// Add crystal shimmer an octave up, delayed
|
|
43
|
-
crystal.triggerAttackRelease(
|
|
44
|
-
chord.map(n => {
|
|
45
|
-
const octave = parseInt(n.match(/\d/)[0]);
|
|
46
|
-
return n.replace(/\d/, octave + 1);
|
|
47
|
-
}),
|
|
48
|
-
'2n', i * beat + 0.05
|
|
49
|
-
);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// Slow bass drone
|
|
53
|
-
const drone = new Tone.MonoSynth({
|
|
54
|
-
oscillator: { type: 'sine' },
|
|
55
|
-
envelope: { attack: 2, decay: 0, sustain: 1, release: 3 }
|
|
56
|
-
}).toDestination();
|
|
57
|
-
drone.volume.value = -20;
|
|
58
|
-
drone.triggerAttackRelease('D1', '1n', 0);
|
|
59
|
-
drone.triggerAttackRelease('D1', '1n', beat * 2);
|
|
60
|
-
}
|
|
61
|
-
</script>
|
|
62
|
-
</body>
|
|
63
|
-
</html>
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>TuneFrames — Ambient</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":60,"duration":"16s"}</div>
|
|
9
|
+
<script>
|
|
10
|
+
async function main() {
|
|
11
|
+
await Tone.start();
|
|
12
|
+
|
|
13
|
+
const verb = new Tone.Reverb({ decay: 8, wet: 0.8 }).toDestination();
|
|
14
|
+
const delay = new Tone.PingPongDelay('4n', 0.3).toDestination();
|
|
15
|
+
|
|
16
|
+
// Warm pad
|
|
17
|
+
const pad = new Tone.PolySynth(Tone.Synth, {
|
|
18
|
+
oscillator: { type: 'sine' },
|
|
19
|
+
envelope: { attack: 3, decay: 1, sustain: 0.8, release: 4 }
|
|
20
|
+
}).connect(verb);
|
|
21
|
+
pad.volume.value = -8;
|
|
22
|
+
|
|
23
|
+
// Crystal high texture
|
|
24
|
+
const crystal = new Tone.PolySynth(Tone.Synth, {
|
|
25
|
+
oscillator: { type: 'sine' },
|
|
26
|
+
envelope: { attack: 4, decay: 0.5, sustain: 1, release: 3 }
|
|
27
|
+
}).connect(delay);
|
|
28
|
+
crystal.volume.value = -18;
|
|
29
|
+
|
|
30
|
+
// D minor progression — hypnotic and spacious
|
|
31
|
+
const chords = [
|
|
32
|
+
['D3', 'F3', 'A3'], // Dm
|
|
33
|
+
['C3', 'E3', 'G3'], // C
|
|
34
|
+
['Bb2', 'D3', 'F3'], // Bb
|
|
35
|
+
['A2', 'C3', 'E3'], // Am
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const beat = Tone.Time('1n').toSeconds();
|
|
39
|
+
|
|
40
|
+
chords.forEach((chord, i) => {
|
|
41
|
+
pad.triggerAttackRelease(chord, '1n', i * beat);
|
|
42
|
+
// Add crystal shimmer an octave up, delayed
|
|
43
|
+
crystal.triggerAttackRelease(
|
|
44
|
+
chord.map(n => {
|
|
45
|
+
const octave = parseInt(n.match(/\d/)[0]);
|
|
46
|
+
return n.replace(/\d/, octave + 1);
|
|
47
|
+
}),
|
|
48
|
+
'2n', i * beat + 0.05
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Slow bass drone
|
|
53
|
+
const drone = new Tone.MonoSynth({
|
|
54
|
+
oscillator: { type: 'sine' },
|
|
55
|
+
envelope: { attack: 2, decay: 0, sustain: 1, release: 3 }
|
|
56
|
+
}).toDestination();
|
|
57
|
+
drone.volume.value = -20;
|
|
58
|
+
drone.triggerAttackRelease('D1', '1n', 0);
|
|
59
|
+
drone.triggerAttackRelease('D1', '1n', beat * 2);
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
</body>
|
|
63
|
+
</html>
|
|
Binary file
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>TuneFrames — Bass</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":120,"duration":"4s"}</div>
|
|
9
|
+
<script>
|
|
10
|
+
async function main() {
|
|
11
|
+
await Tone.start();
|
|
12
|
+
const beat = Tone.Time('4n').toSeconds(); // 0.5s at 120 BPM
|
|
13
|
+
|
|
14
|
+
// Warm, rounded bass tone
|
|
15
|
+
const bass = new Tone.MonoSynth({
|
|
16
|
+
oscillator: { type: 'sawtooth' },
|
|
17
|
+
filter: { Q: 2, type: 'lowpass', rolloff: -12 },
|
|
18
|
+
envelope: { attack: 0.01, decay: 0.2, sustain: 0.4, release: 0.3 },
|
|
19
|
+
filterEnvelope: { attack: 0.01, decay: 0.1, sustain: 0.3, release: 0.2, baseFrequency: 100, octaves: 2 }
|
|
20
|
+
}).toDestination();
|
|
21
|
+
bass.volume.value = -6;
|
|
22
|
+
|
|
23
|
+
// Walking bass line: beats 0-4 (0 to 2s) = Dm, beats 4-7 (2 to 3.5s) = Gm
|
|
24
|
+
// All notes scheduled within duration=4s — notes at time >= 4s are dropped
|
|
25
|
+
const walk = [
|
|
26
|
+
// Bar 1 — Dm: root, 5th, 7th, root / approach, 3rd, 4th, 5th, root
|
|
27
|
+
{ note: 'D2', time: 0 },
|
|
28
|
+
{ note: 'A2', time: beat },
|
|
29
|
+
{ note: 'C3', time: 2 * beat },
|
|
30
|
+
{ note: 'D3', time: 3 * beat },
|
|
31
|
+
{ note: 'E2', time: 4 * beat },
|
|
32
|
+
{ note: 'F2', time: 5 * beat },
|
|
33
|
+
{ note: 'G2', time: 6 * beat },
|
|
34
|
+
{ note: 'A2', time: 7 * beat },
|
|
35
|
+
// Bar 2 — Gm: root, 5th, 7th, root
|
|
36
|
+
{ note: 'G2', time: 8 * beat },
|
|
37
|
+
{ note: 'D3', time: 9 * beat },
|
|
38
|
+
{ note: 'F3', time: 10 * beat },
|
|
39
|
+
{ note: 'G3', time: 11 * beat }
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
walk.forEach(({ note, time }) => {
|
|
43
|
+
if (time < 4) bass.triggerAttackRelease(note, '8n', time);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
</body>
|
|
48
|
+
</html>
|