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.
Files changed (80) hide show
  1. package/LICENSE +166 -166
  2. package/README.md +158 -55
  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 +47 -47
  11. package/examples/example-lofi.html +45 -45
  12. package/examples/example-lofi.mp3 +0 -0
  13. package/examples/example-minimal.html +20 -20
  14. package/examples/example-orchestral.html +67 -67
  15. package/examples/example-orchestral.mp3 +0 -0
  16. package/examples/example-piano.html +52 -52
  17. package/examples/example-piano.mp3 +0 -0
  18. package/examples/example-techno.html +69 -69
  19. package/examples/example-techno.mp3 +0 -0
  20. package/package.json +42 -37
  21. package/registry/presets/bass-electric.html +67 -0
  22. package/registry/presets/bass-saw.html +22 -0
  23. package/registry/presets/chord-progression.html +22 -0
  24. package/registry/presets/drums-808.html +85 -0
  25. package/registry/presets/drums-lofi.html +35 -0
  26. package/registry/presets/lead-piano.html +26 -0
  27. package/registry/presets/piano-salamander.html +69 -0
  28. package/registry/presets/reverb-warm.html +11 -0
  29. package/registry/samples.json +226 -0
  30. package/skills/audio-ambient/SKILL.md +141 -0
  31. package/skills/audio-ambient/example.html +113 -0
  32. package/skills/audio-boss-battle/SKILL.md +157 -0
  33. package/skills/audio-boss-battle/example.html +185 -0
  34. package/skills/audio-boss-battle/example.mp3 +0 -0
  35. package/skills/audio-chillwave/SKILL.md +142 -0
  36. package/skills/audio-chillwave/example.html +144 -0
  37. package/skills/audio-cinematic/SKILL.md +147 -0
  38. package/skills/audio-cinematic/example.html +123 -0
  39. package/skills/audio-classical/SKILL.md +138 -0
  40. package/skills/audio-classical/example.html +145 -0
  41. package/skills/audio-dnb/SKILL.md +142 -0
  42. package/skills/audio-dnb/example.html +124 -0
  43. package/skills/audio-downtempo/SKILL.md +152 -0
  44. package/skills/audio-downtempo/example.html +164 -0
  45. package/skills/audio-folk/SKILL.md +139 -0
  46. package/skills/audio-folk/example.html +132 -0
  47. package/skills/audio-funk/SKILL.md +149 -0
  48. package/skills/audio-funk/example.html +144 -0
  49. package/skills/audio-future-bass/SKILL.md +163 -0
  50. package/skills/audio-future-bass/example.html +164 -0
  51. package/skills/audio-hip-hop/SKILL.md +133 -0
  52. package/skills/audio-hip-hop/example.html +129 -0
  53. package/skills/audio-house/SKILL.md +147 -0
  54. package/skills/audio-house/example.html +128 -0
  55. package/skills/audio-indie-pop/SKILL.md +150 -0
  56. package/skills/audio-indie-pop/example.html +121 -0
  57. package/skills/audio-jazz/SKILL.md +141 -0
  58. package/skills/audio-jazz/example.html +146 -0
  59. package/skills/audio-lofi/SKILL.md +140 -0
  60. package/skills/audio-lofi/example.html +135 -0
  61. package/skills/audio-minimal/SKILL.md +155 -0
  62. package/skills/audio-minimal/example.html +118 -0
  63. package/skills/audio-orchestral/SKILL.md +156 -0
  64. package/skills/audio-orchestral/example.html +140 -0
  65. package/skills/audio-r-and-b/SKILL.md +134 -0
  66. package/skills/audio-r-and-b/example.html +154 -0
  67. package/skills/audio-r-and-b/example.mp3 +0 -0
  68. package/skills/audio-techno/SKILL.md +140 -0
  69. package/skills/audio-techno/example.html +125 -0
  70. package/skills/audio-trap/SKILL.md +123 -0
  71. package/skills/audio-trap/example.html +119 -0
  72. package/skills/render-retest/example.html +115 -0
  73. package/skills/tuneframes/SKILL.md +221 -0
  74. package/skills/tuneframes-cli/SKILL.md +46 -0
  75. package/skills/verify/example.html +104 -0
  76. package/src/cli.js +260 -151
  77. package/src/render.js +134 -7
  78. package/examples/example-bass.mp3 +0 -0
  79. package/examples/example-demo-beat.wav +0 -0
  80. package/examples/example-orchestral.wav +0 -0
@@ -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 = 'chill'; // 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 = 'dark'; // 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 = '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>