Spaces:
Running
Running
Update index.html
Browse files- index.html +521 -19
index.html
CHANGED
@@ -1,19 +1,521 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
2 |
+
import * as Tone from 'tone'; // Import Tone.js
|
3 |
+
|
4 |
+
// Song data parsed from your previous responses
|
5 |
+
const songsData = [
|
6 |
+
{
|
7 |
+
title: "Talkin' 'bout a Revolution",
|
8 |
+
artist: "Tracy Chapman",
|
9 |
+
sections: [
|
10 |
+
{ type: "Intro", lines: [{ chords: "G C D G", lyrics: "" }] },
|
11 |
+
{ type: "Verse 1", lines: [
|
12 |
+
{ chords: "G C D G", lyrics: "While outside a revolution's talking..." },
|
13 |
+
{ chords: "G C D G", lyrics: "It's gonna come, it's gonna come..." }
|
14 |
+
]},
|
15 |
+
{ type: "Chorus 1", lines: [
|
16 |
+
{ chords: "G C D G", lyrics: "Talkin' 'bout a revolution, oh, no" },
|
17 |
+
{ chords: "G C D G", lyrics: "Talkin' 'bout a revolution..." }
|
18 |
+
]},
|
19 |
+
{ type: "Verse 2", lines: [
|
20 |
+
{ chords: "G C D G", lyrics: "While outside a revolution's talking..." },
|
21 |
+
{ chords: "G C D G", lyrics: "It's gonna come, it's gonna come..." }
|
22 |
+
]},
|
23 |
+
{ type: "Chorus 2", lines: [
|
24 |
+
{ chords: "G C D G", lyrics: "Talkin' 'bout a revolution, oh, no" },
|
25 |
+
{ chords: "G C D G", lyrics: "Talkin' 'bout a revolution..." }
|
26 |
+
]},
|
27 |
+
{ type: "Outro", lines: [{ chords: "G C D G", lyrics: "(repeat and fade)" }] }
|
28 |
+
]
|
29 |
+
},
|
30 |
+
{
|
31 |
+
title: "Fast Car",
|
32 |
+
artist: "Tracy Chapman",
|
33 |
+
sections: [
|
34 |
+
{ type: "Intro", lines: [{ chords: "C G Am F", lyrics: "" }] },
|
35 |
+
{ type: "Verse 1", lines: [
|
36 |
+
{ chords: "C G Am F", lyrics: "You got a fast car, I want a ticket to anywhere..." },
|
37 |
+
{ chords: "C G Am F", lyrics: "We go driving in it, anywhere, maybe we'll make a deal..." }
|
38 |
+
]},
|
39 |
+
{ type: "Chorus 1", lines: [
|
40 |
+
{ chords: "C G Am F", lyrics: "So remember when we were driving, driving in your car..." },
|
41 |
+
{ chords: "C G Am F", lyrics: "The speed of light, we gotta go, go, go, go, go..." }
|
42 |
+
]},
|
43 |
+
{ type: "Verse 2", lines: [
|
44 |
+
{ chords: "C G Am F", lyrics: "You got a fast car, I got a plan to get us out of here..." },
|
45 |
+
{ chords: "C G Am F", lyrics: "I been working at the convenience store, so slow..." }
|
46 |
+
]},
|
47 |
+
{ type: "Chorus 2", lines: [
|
48 |
+
{ chords: "C G Am F", lyrics: "So remember when we were driving, driving in your car..." },
|
49 |
+
{ chords: "C G Am F", lyrics: "The speed of light, we gotta go, go, go, go, go..." }
|
50 |
+
]},
|
51 |
+
{ type: "Outro", lines: [{ chords: "C G Am F", lyrics: "(repeat and fade)" }] }
|
52 |
+
]
|
53 |
+
},
|
54 |
+
{
|
55 |
+
title: "Cult of Personality",
|
56 |
+
artist: "Living Colour",
|
57 |
+
sections: [
|
58 |
+
{ type: "Intro", lines: [{ chords: "Em G C D", lyrics: "" }] },
|
59 |
+
{ type: "Verse 1", lines: [
|
60 |
+
{ chords: "Em G C D", lyrics: "Look in my eyes, what do you see?" },
|
61 |
+
{ chords: "Em G C D", lyrics: "The cult of personality." }
|
62 |
+
]},
|
63 |
+
{ type: "Chorus 1", lines: [
|
64 |
+
{ chords: "Em G C D", lyrics: "Cult of Personality! Cult of Personality!" }
|
65 |
+
]},
|
66 |
+
{ type: "Verse 2", lines: [
|
67 |
+
{ chords: "Em G C D", lyrics: "I look in your eyes, what do I see?" },
|
68 |
+
{ chords: "Em G C D", lyrics: "The cult of personality." }
|
69 |
+
]},
|
70 |
+
{ type: "Chorus 2", lines: [
|
71 |
+
{ chords: "Em G C D", lyrics: "Cult of Personality! Cult of Personality!" }
|
72 |
+
]},
|
73 |
+
{ type: "Outro", lines: [{ chords: "Em G C D", lyrics: "(repeated, building to a final hit on Em)" }] }
|
74 |
+
]
|
75 |
+
},
|
76 |
+
{
|
77 |
+
title: "Glamour Boys",
|
78 |
+
artist: "Living Colour",
|
79 |
+
sections: [
|
80 |
+
{ type: "Intro", lines: [{ chords: "E B A E", lyrics: "" }] },
|
81 |
+
{ type: "Verse 1", lines: [
|
82 |
+
{ chords: "E B", lyrics: "The Glamour Boys, they got the girls" },
|
83 |
+
{ chords: "A E", lyrics: "They got the cars, they got the pearls" }
|
84 |
+
]},
|
85 |
+
{ type: "Chorus 1", lines: [
|
86 |
+
{ chords: "E B A E", lyrics: "Glamour Boys, oh, the Glamour Boys, what makes you think you're so cool?" }
|
87 |
+
]},
|
88 |
+
{ type: "Verse 2", lines: [
|
89 |
+
{ chords: "E B", lyrics: "The Glamour Boys, they're so hip" },
|
90 |
+
{ chords: "A E", lyrics: "They got the style, they got the trip" }
|
91 |
+
]},
|
92 |
+
{ type: "Chorus 2", lines: [
|
93 |
+
{ chords: "E B A E", lyrics: "Glamour Boys, oh, the Glamour Boys, what makes you think you're so cool?" }
|
94 |
+
]},
|
95 |
+
{ type: "Outro", lines: [{ chords: "E B A E", lyrics: "(repeat and fade with guitar licks)" }] }
|
96 |
+
]
|
97 |
+
},
|
98 |
+
{
|
99 |
+
title: "Ocean Size",
|
100 |
+
artist: "Jane's Addiction",
|
101 |
+
sections: [
|
102 |
+
{ type: "Intro", lines: [{ chords: "C G Am F", lyrics: "" }] },
|
103 |
+
{ type: "Verse 1", lines: [
|
104 |
+
{ chords: "C G Am F", lyrics: "Day by day, as the weeks turn to months, I watch you..." },
|
105 |
+
{ chords: "C G Am F", lyrics: "Growing taller, growing wiser, growing stronger..." }
|
106 |
+
]},
|
107 |
+
{ type: "Chorus 1", lines: [
|
108 |
+
{ chords: "C G D F", lyrics: "Ocean size, it's the ocean size" },
|
109 |
+
{ chords: "C G D F", lyrics: "The ocean size, it's the ocean size" }
|
110 |
+
]},
|
111 |
+
{ type: "Verse 2", lines: [
|
112 |
+
{ chords: "C G Am F", lyrics: "Day by day, as the weeks turn to months, I watch you..." },
|
113 |
+
{ chords: "C G Am F", lyrics: "Growing stronger, growing wiser, growing taller..." }
|
114 |
+
]},
|
115 |
+
{ type: "Chorus 2", lines: [
|
116 |
+
{ chords: "C G D F", lyrics: "Ocean size, it's the ocean size" },
|
117 |
+
{ chords: "C G D F", lyrics: "The ocean size, it's the ocean size" }
|
118 |
+
]},
|
119 |
+
{ type: "Outro", lines: [{ chords: "C G Am F", lyrics: "(repeat and fade)" }] }
|
120 |
+
]
|
121 |
+
},
|
122 |
+
{
|
123 |
+
title: "Mountain Song",
|
124 |
+
artist: "Jane's Addiction",
|
125 |
+
sections: [
|
126 |
+
{ type: "Intro", lines: [{ chords: "Em C G D", lyrics: "" }] },
|
127 |
+
{ type: "Verse 1", lines: [
|
128 |
+
{ chords: "Em C G D", lyrics: "Coming down the mountain, I saw a girl" },
|
129 |
+
{ chords: "Em C G D", lyrics: "She was looking at me, in my world" }
|
130 |
+
]},
|
131 |
+
{ type: "Chorus 1", lines: [
|
132 |
+
{ chords: "Em C G D", lyrics: "Mountain Song! Mountain Song!" }
|
133 |
+
]},
|
134 |
+
{ type: "Verse 2", lines: [
|
135 |
+
{ chords: "Em C G D", lyrics: "She said, \"Where'd you come from? Where'd you go?\"" },
|
136 |
+
{ chords: "Em C G D", lyrics: "\"I came from the mountain, don't you know?\"" }
|
137 |
+
]},
|
138 |
+
{ type: "Chorus 2", lines: [
|
139 |
+
{ chords: "Em C G D", lyrics: "Mountain Song! Mountain Song!" }
|
140 |
+
]},
|
141 |
+
{ type: "Outro", lines: [{ chords: "Em C G D", lyrics: "(repeat and fade)" }] }
|
142 |
+
]
|
143 |
+
},
|
144 |
+
{
|
145 |
+
title: "Where Is My Mind?",
|
146 |
+
artist: "Pixies",
|
147 |
+
sections: [
|
148 |
+
{ type: "Intro", lines: [{ chords: "C G Am F", lyrics: "" }] },
|
149 |
+
{ type: "Verse 1", lines: [
|
150 |
+
{ chords: "C G Am F", lyrics: "With your feet on the air and your head on the ground..." },
|
151 |
+
{ chords: "C G Am F", lyrics: "Try this trick and spin it, yeah..." }
|
152 |
+
]},
|
153 |
+
{ type: "Chorus 1", lines: [
|
154 |
+
{ chords: "C G Am F", lyrics: "Where is my mind? Where is my mind?" },
|
155 |
+
{ chords: "C G Am F", lyrics: "Where is my mind? Way out in the water, see it swimming..." }
|
156 |
+
]},
|
157 |
+
{ type: "Verse 2", lines: [
|
158 |
+
{ chords: "C G Am F", lyrics: "I was thinking about you and the things we've done..." },
|
159 |
+
{ chords: "C G Am F", lyrics: "And all the places we've been to, oh yeah..." }
|
160 |
+
]},
|
161 |
+
{ type: "Chorus 2", lines: [
|
162 |
+
{ chords: "C G Am F", lyrics: "Where is my mind? Where is my mind?" },
|
163 |
+
{ chords: "C G Am F", lyrics: "Where is my mind? Way out in the water, see it swimming..." }
|
164 |
+
]},
|
165 |
+
{ type: "Outro", lines: [{ chords: "C G Am F", lyrics: "(repeat and fade)" }] }
|
166 |
+
]
|
167 |
+
},
|
168 |
+
{
|
169 |
+
title: "Fisherman's Blues",
|
170 |
+
artist: "The Waterboys",
|
171 |
+
sections: [
|
172 |
+
{ type: "Intro", lines: [{ chords: "G C D G", lyrics: "" }] },
|
173 |
+
{ type: "Verse 1", lines: [
|
174 |
+
{ chords: "G C D G", lyrics: "I wish I was a fisherman, tumbling on the sea" },
|
175 |
+
{ chords: "G C D G", lyrics: "Far away from dry land, and its bitter misery" }
|
176 |
+
]},
|
177 |
+
{ type: "Chorus 1", lines: [
|
178 |
+
{ chords: "G C D G", lyrics: "I'm gonna make a record, a record of my dreams" },
|
179 |
+
{ chords: "G C D G", lyrics: "And let the wind and the waves sing along to the themes" }
|
180 |
+
]},
|
181 |
+
{ type: "Verse 2", lines: [
|
182 |
+
{ chords: "G C D G", lyrics: "I wish I was a fisherman, out on the rolling deep" },
|
183 |
+
{ chords: "G C D G", lyrics: "With nothing but the stars to guide me, while the city sleeps" }
|
184 |
+
]},
|
185 |
+
{ type: "Chorus 2", lines: [
|
186 |
+
{ chords: "G C D G", lyrics: "I'm gonna make a record, a record of my dreams" },
|
187 |
+
{ chords: "G C D G", lyrics: "And let the wind and the waves sing along to the themes" }
|
188 |
+
]},
|
189 |
+
{ type: "Outro", lines: [{ chords: "G C D G", lyrics: "(repeat and fade)" }] }
|
190 |
+
]
|
191 |
+
},
|
192 |
+
{
|
193 |
+
title: "Express Yourself",
|
194 |
+
artist: "N.W.A.",
|
195 |
+
sections: [
|
196 |
+
{ type: "Intro", lines: [{ chords: "F Am Dm C", lyrics: "" }] },
|
197 |
+
{ type: "Verse 1", lines: [
|
198 |
+
{ chords: "F Am Dm C", lyrics: "I'm expressing with my full capabilities, and now I'm living in reality..." },
|
199 |
+
{ chords: "F Am Dm C", lyrics: "The only solution is to get involved and move it..." }
|
200 |
+
]},
|
201 |
+
{ type: "Chorus 1", lines: [
|
202 |
+
{ chords: "F Am Dm C", lyrics: "Express yourself! Express yourself!" },
|
203 |
+
{ chords: "F Am Dm C", lyrics: "Express yourself! It's a brand new thing..." }
|
204 |
+
]},
|
205 |
+
{ type: "Verse 2", lines: [
|
206 |
+
{ chords: "F Am Dm C", lyrics: "Now I'm the one, I'm the one, I'm the one that you know..." },
|
207 |
+
{ chords: "F Am Dm C", lyrics: "Coming to get you, coming to get you, coming to get you, watch me go..." }
|
208 |
+
]},
|
209 |
+
{ type: "Chorus 2", lines: [
|
210 |
+
{ chords: "F Am Dm C", lyrics: "Express yourself! Express yourself!" },
|
211 |
+
{ chords: "F Am Dm C", lyrics: "Express yourself! It's a brand new thing..." }
|
212 |
+
]},
|
213 |
+
{ type: "Outro", lines: [{ chords: "F Am Dm C", lyrics: "(repeat and fade)" }] }
|
214 |
+
]
|
215 |
+
},
|
216 |
+
{
|
217 |
+
title: "One",
|
218 |
+
artist: "Metallica",
|
219 |
+
sections: [
|
220 |
+
{ type: "Intro", lines: [{ chords: "Am G C F", lyrics: "(Acoustic Intro/Verse Part)" }] },
|
221 |
+
{ type: "Verse 1", lines: [
|
222 |
+
{ chords: "Am G C F", lyrics: "I can't remember anything, can't tell if this is true or dream..." },
|
223 |
+
{ chords: "Am G C F", lyrics: "Deep down inside I feel the scream..." }
|
224 |
+
]},
|
225 |
+
{ type: "Chorus 1", lines: [ // Note: This is not a traditional chorus for this song, but the repeated progression
|
226 |
+
{ chords: "Am G C F", lyrics: "Hold my breath as I wish for death..." }
|
227 |
+
]},
|
228 |
+
{ type: "Verse 2", lines: [
|
229 |
+
{ chords: "Am G C F", lyrics: "Life it seems will fade away, drifting further every day..." },
|
230 |
+
{ chords: "Am G C F", lyrics: "Getting lost within myself, nothing matters, no one else..." }
|
231 |
+
]},
|
232 |
+
{ type: "Chorus 2", lines: [ // Note: This is not a traditional chorus for this song, but the repeated progression
|
233 |
+
{ chords: "Am G C F", lyrics: "All the thoughts that I have now..." }
|
234 |
+
]},
|
235 |
+
{ type: "Outro", lines: [{ chords: "E5 - D5 - C5 - A5", lyrics: "(heavy riffing, fast tempo, ends on E5)" }] }
|
236 |
+
]
|
237 |
+
},
|
238 |
+
{
|
239 |
+
title: "Handle with Care",
|
240 |
+
artist: "Traveling Wilburys",
|
241 |
+
sections: [
|
242 |
+
{ type: "Intro", lines: [{ chords: "G D Em C", lyrics: "" }] },
|
243 |
+
{ type: "Verse 1", lines: [
|
244 |
+
{ chords: "G D Em C", lyrics: "Been beat up and battered 'round, been sent up, and I've been shot down..." },
|
245 |
+
{ chords: "G D Em C", lyrics: "You're the best thing that I've found, handle me with care." }
|
246 |
+
]},
|
247 |
+
{ type: "Chorus 1", lines: [
|
248 |
+
{ chords: "G D Em C", lyrics: "Handle me with care, handle me with care" },
|
249 |
+
{ chords: "G D Em C", lyrics: "Handle me with care, handle me with care" }
|
250 |
+
]},
|
251 |
+
{ type: "Verse 2", lines: [
|
252 |
+
{ chords: "G D Em C", lyrics: "I been stuck in so much traffic, I'm a mess, a nervous wreck..." },
|
253 |
+
{ chords: "G D Em C", lyrics: "I could use some tender love and care, handle me with care." }
|
254 |
+
]},
|
255 |
+
{ type: "Chorus 2", lines: [
|
256 |
+
{ chords: "G D Em C", lyrics: "Handle me with care, handle me with care" },
|
257 |
+
{ chords: "G D Em C", lyrics: "Handle me with care, handle me with care" }
|
258 |
+
]},
|
259 |
+
{ type: "Outro", lines: [{ chords: "G D Em C", lyrics: "(repeat and fade)" }] }
|
260 |
+
]
|
261 |
+
}
|
262 |
+
];
|
263 |
+
|
264 |
+
|
265 |
+
// Simplified chord to MIDI note mapping
|
266 |
+
// Maps chord root to MIDI note number (C4 = 60) and then adds intervals for Major/Minor triad
|
267 |
+
const chordToMidi = (chordName) => {
|
268 |
+
const rootMap = {
|
269 |
+
'C': 60, 'C#': 61, 'Db': 61, 'D': 62, 'D#': 63, 'Eb': 63, 'E': 64, 'F': 65,
|
270 |
+
'F#': 66, 'Gb': 66, 'G': 67, 'G#': 68, 'Ab': 68, 'A': 69, 'A#': 70, 'Bb': 70, 'B': 71
|
271 |
+
};
|
272 |
+
|
273 |
+
let root = null;
|
274 |
+
let type = 'major'; // Default to major
|
275 |
+
let octaveOffset = 0; // Default to 4th octave for root
|
276 |
+
|
277 |
+
// Parse root and accidental (e.g., C#, Eb)
|
278 |
+
let baseChord = chordName.trim().replace('5', ''); // Handle power chords as root
|
279 |
+
const match = baseChord.match(/^([A-G][b#]?)/);
|
280 |
+
if (match) {
|
281 |
+
root = rootMap[match[1]];
|
282 |
+
baseChord = baseChord.substring(match[1].length); // Remove root for type parsing
|
283 |
+
} else {
|
284 |
+
// Fallback for unparseable roots, or just return empty
|
285 |
+
return [];
|
286 |
+
}
|
287 |
+
|
288 |
+
// Determine chord type (simple major/minor/power for now)
|
289 |
+
if (baseChord.includes('m') || baseChord.includes('min')) {
|
290 |
+
type = 'minor';
|
291 |
+
} else if (baseChord.includes('5')) { // Explicit power chord
|
292 |
+
type = 'power';
|
293 |
+
}
|
294 |
+
|
295 |
+
if (root === null) return []; // Should not happen with match check above
|
296 |
+
|
297 |
+
const notes = [root + octaveOffset];
|
298 |
+
if (type === 'major') {
|
299 |
+
notes.push(root + 4 + octaveOffset); // Major third
|
300 |
+
notes.push(root + 7 + octaveOffset); // Perfect fifth
|
301 |
+
} else if (type === 'minor') {
|
302 |
+
notes.push(root + 3 + octaveOffset); // Minor third
|
303 |
+
notes.push(root + 7 + octaveOffset); // Perfect fifth
|
304 |
+
} else if (type === 'power') {
|
305 |
+
notes.push(root + 7 + octaveOffset); // Perfect fifth
|
306 |
+
}
|
307 |
+
|
308 |
+
// Add higher octave for fuller sound, if it makes sense
|
309 |
+
if (notes.length > 0) {
|
310 |
+
notes.push(notes[0] + 12); // Add root an octave higher
|
311 |
+
}
|
312 |
+
|
313 |
+
return notes;
|
314 |
+
};
|
315 |
+
|
316 |
+
|
317 |
+
// Main App Component
|
318 |
+
const App = () => {
|
319 |
+
const [currentSongIndex, setCurrentSongIndex] = useState(0);
|
320 |
+
const [currentTempo, setCurrentTempo] = useState(90); // Default to Medium tempo
|
321 |
+
const synthRef = useRef(null);
|
322 |
+
const playSequenceRef = useRef(null);
|
323 |
+
|
324 |
+
// Define tempo presets
|
325 |
+
const tempos = {
|
326 |
+
slow: 30,
|
327 |
+
medium: 90, // Changed from previous default for distinct steps
|
328 |
+
fast: 128
|
329 |
+
};
|
330 |
+
|
331 |
+
// Initialize Tone.js synth once
|
332 |
+
useEffect(() => {
|
333 |
+
// Only create synth if it doesn't exist
|
334 |
+
if (!synthRef.current) {
|
335 |
+
synthRef.current = new Tone.PolySynth(Tone.Synth, {
|
336 |
+
envelope: {
|
337 |
+
attack: 0.02,
|
338 |
+
decay: 0.1,
|
339 |
+
sustain: 0.3,
|
340 |
+
release: 1
|
341 |
+
}
|
342 |
+
}).toDestination();
|
343 |
+
}
|
344 |
+
|
345 |
+
// Cleanup function
|
346 |
+
return () => {
|
347 |
+
if (synthRef.current) {
|
348 |
+
synthRef.current.dispose();
|
349 |
+
synthRef.current = null;
|
350 |
+
}
|
351 |
+
if (playSequenceRef.current) {
|
352 |
+
playSequenceRef.current.stop();
|
353 |
+
playSequenceRef.current.dispose();
|
354 |
+
playSequenceRef.current = null;
|
355 |
+
}
|
356 |
+
Tone.Transport.stop(); // Stop transport on unmount
|
357 |
+
};
|
358 |
+
}, []); // Run once on mount
|
359 |
+
|
360 |
+
const currentSong = songsData[currentSongIndex];
|
361 |
+
|
362 |
+
const handleNextSong = () => {
|
363 |
+
if (playSequenceRef.current) {
|
364 |
+
playSequenceRef.current.stop(); // Stop any current playback
|
365 |
+
}
|
366 |
+
setCurrentSongIndex((prevIndex) => (prevIndex + 1) % songsData.length);
|
367 |
+
};
|
368 |
+
|
369 |
+
const handlePrevSong = () => {
|
370 |
+
if (playSequenceRef.current) {
|
371 |
+
playSequenceRef.current.stop(); // Stop any current playback
|
372 |
+
}
|
373 |
+
setCurrentSongIndex((prevIndex) => (prevIndex - 1 + songsData.length) % songsData.length);
|
374 |
+
};
|
375 |
+
|
376 |
+
const setTempo = (tempoValue) => {
|
377 |
+
setCurrentTempo(tempoValue);
|
378 |
+
Tone.Transport.bpm.value = tempoValue; // Update Tone.js Transport BPM
|
379 |
+
if (playSequenceRef.current && Tone.Transport.state === 'started') {
|
380 |
+
// If playing, restart the sequence to apply tempo immediately
|
381 |
+
playChords(); // This will stop and restart with new tempo
|
382 |
+
}
|
383 |
+
};
|
384 |
+
|
385 |
+
|
386 |
+
const playChords = useCallback(async () => {
|
387 |
+
if (!synthRef.current) {
|
388 |
+
console.error("Synth not initialized.");
|
389 |
+
return;
|
390 |
+
}
|
391 |
+
|
392 |
+
// Start Tone.js audio context if not already started
|
393 |
+
if (Tone.context.state !== 'running') {
|
394 |
+
await Tone.start();
|
395 |
+
console.log('Audio context started.');
|
396 |
+
}
|
397 |
+
|
398 |
+
// Stop any existing sequence and transport
|
399 |
+
if (playSequenceRef.current) {
|
400 |
+
playSequenceRef.current.stop();
|
401 |
+
playSequenceRef.current.dispose();
|
402 |
+
playSequenceRef.current = null;
|
403 |
+
}
|
404 |
+
Tone.Transport.stop();
|
405 |
+
Tone.Transport.cancel(); // Clear any scheduled events
|
406 |
+
|
407 |
+
// Set the tempo before starting the transport
|
408 |
+
Tone.Transport.bpm.value = currentTempo;
|
409 |
+
|
410 |
+
const allChords = [];
|
411 |
+
currentSong.sections.forEach(section => {
|
412 |
+
section.lines.forEach(line => {
|
413 |
+
// Simple regex to extract individual chord names, assume space-separated
|
414 |
+
const chordsInLine = line.chords.split(/\s+/).filter(c => c.length > 0);
|
415 |
+
chordsInLine.forEach(chord => allChords.push(chord));
|
416 |
+
});
|
417 |
+
});
|
418 |
+
|
419 |
+
if (allChords.length === 0) {
|
420 |
+
console.warn("No chords found for this song.");
|
421 |
+
return;
|
422 |
+
}
|
423 |
+
|
424 |
+
// Create a new sequence for playback
|
425 |
+
// The interval '1n' means one whole note per chord in this sequence
|
426 |
+
// Tone.js Transport BPM controls the actual duration of '1n'
|
427 |
+
playSequenceRef.current = new Tone.Sequence((time, chordName) => {
|
428 |
+
const midiNotes = chordToMidi(chordName);
|
429 |
+
if (midiNotes.length > 0) {
|
430 |
+
synthRef.current.triggerAttackRelease(midiNotes.map(n => Tone.Midi(n).toNote()), "0.8n", time); // hold for 0.8 of a whole note
|
431 |
+
}
|
432 |
+
}, allChords, "1n").start(0); // Each chord plays for 1 whole note duration
|
433 |
+
|
434 |
+
Tone.Transport.start();
|
435 |
+
|
436 |
+
}, [currentSong, currentTempo]); // Recreate sequence if current song or tempo changes
|
437 |
+
|
438 |
+
// Stop playback when song index changes
|
439 |
+
useEffect(() => {
|
440 |
+
if (playSequenceRef.current) {
|
441 |
+
playSequenceRef.current.stop();
|
442 |
+
Tone.Transport.stop();
|
443 |
+
}
|
444 |
+
}, [currentSongIndex]);
|
445 |
+
|
446 |
+
|
447 |
+
return (
|
448 |
+
<div className="flex flex-col h-screen bg-gray-900 text-gray-100 font-inter">
|
449 |
+
{/* Header with song title and artist */}
|
450 |
+
<header className="p-4 bg-gray-800 shadow-md text-center rounded-b-lg">
|
451 |
+
<h1 className="text-3xl font-bold text-blue-400">
|
452 |
+
{currentSong.title}
|
453 |
+
</h1>
|
454 |
+
<p className="text-xl text-gray-300">{currentSong.artist}</p>
|
455 |
+
</header>
|
456 |
+
|
457 |
+
{/* Main content area - lyrics and chords */}
|
458 |
+
<main className="flex-1 overflow-y-auto p-6 text-2xl leading-relaxed">
|
459 |
+
{currentSong.sections.map((section, secIndex) => (
|
460 |
+
<div key={secIndex} className="mb-8">
|
461 |
+
<h2 className="text-3xl font-semibold text-yellow-300 mb-4 sticky top-0 bg-gray-900 py-2 z-10">
|
462 |
+
{section.type}
|
463 |
+
</h2>
|
464 |
+
{section.lines.map((line, lineIndex) => (
|
465 |
+
<div key={lineIndex} className="mb-4">
|
466 |
+
<p className="font-bold text-green-300 whitespace-pre">
|
467 |
+
{line.chords}
|
468 |
+
</p>
|
469 |
+
<p className="text-gray-100">
|
470 |
+
{line.lyrics}
|
471 |
+
</p>
|
472 |
+
</div>
|
473 |
+
))}
|
474 |
+
</div>
|
475 |
+
))}
|
476 |
+
</main>
|
477 |
+
|
478 |
+
{/* Navigation and MIDI controls */}
|
479 |
+
<footer className="p-4 bg-gray-800 shadow-t-md flex justify-center items-center space-x-4 rounded-t-lg flex-wrap">
|
480 |
+
<button
|
481 |
+
onClick={handlePrevSong}
|
482 |
+
className="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg shadow-lg transition duration-300 ease-in-out transform hover:scale-105 my-2"
|
483 |
+
>
|
484 |
+
Previous Song
|
485 |
+
</button>
|
486 |
+
<button
|
487 |
+
onClick={() => setTempo(tempos.slow)}
|
488 |
+
className={`py-3 px-6 rounded-lg shadow-lg transition duration-300 ease-in-out transform hover:scale-105 my-2 ${currentTempo === tempos.slow ? 'bg-purple-700' : 'bg-purple-500 hover:bg-purple-600'} text-white font-bold`}
|
489 |
+
>
|
490 |
+
Slow ({tempos.slow} BPM)
|
491 |
+
</button>
|
492 |
+
<button
|
493 |
+
onClick={() => setTempo(tempos.medium)}
|
494 |
+
className={`py-3 px-6 rounded-lg shadow-lg transition duration-300 ease-in-out transform hover:scale-105 my-2 ${currentTempo === tempos.medium ? 'bg-purple-700' : 'bg-purple-500 hover:bg-purple-600'} text-white font-bold`}
|
495 |
+
>
|
496 |
+
Medium ({tempos.medium} BPM)
|
497 |
+
</button>
|
498 |
+
<button
|
499 |
+
onClick={() => setTempo(tempos.fast)}
|
500 |
+
className={`py-3 px-6 rounded-lg shadow-lg transition duration-300 ease-in-out transform hover:scale-105 my-2 ${currentTempo === tempos.fast ? 'bg-purple-700' : 'bg-purple-500 hover:bg-purple-600'} text-white font-bold`}
|
501 |
+
>
|
502 |
+
Fast ({tempos.fast} BPM)
|
503 |
+
</button>
|
504 |
+
<button
|
505 |
+
onClick={playChords}
|
506 |
+
className="bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-6 rounded-lg shadow-lg transition duration-300 ease-in-out transform hover:scale-105 my-2"
|
507 |
+
>
|
508 |
+
Play Chords
|
509 |
+
</button>
|
510 |
+
<button
|
511 |
+
onClick={handleNextSong}
|
512 |
+
className="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg shadow-lg transition duration-300 ease-in-out transform hover:scale-105 my-2"
|
513 |
+
>
|
514 |
+
Next Song
|
515 |
+
</button>
|
516 |
+
</footer>
|
517 |
+
</div>
|
518 |
+
);
|
519 |
+
};
|
520 |
+
|
521 |
+
export default App;
|