Parthiban97 commited on
Commit
cdfd33a
·
verified ·
1 Parent(s): c278239

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +1151 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Music Player
3
- emoji: 📉
4
- colorFrom: gray
5
- colorTo: indigo
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: music-player
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: blue
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1151 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Yamaha Keyboard Player Simulator</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
11
+
12
+ body {
13
+ font-family: 'Poppins', sans-serif;
14
+ background: linear-gradient(135deg, #1a1a2e, #16213e);
15
+ color: white;
16
+ min-height: 100vh;
17
+ }
18
+
19
+ .key {
20
+ transition: all 0.07s ease;
21
+ position: relative;
22
+ cursor: pointer;
23
+ user-select: none;
24
+ }
25
+
26
+ .white-key {
27
+ background: white;
28
+ border: 1px solid #ccc;
29
+ z-index: 1;
30
+ }
31
+
32
+ .black-key {
33
+ background: #333;
34
+ z-index: 2;
35
+ margin-left: -15px;
36
+ margin-right: -15px;
37
+ height: 60%;
38
+ }
39
+
40
+ .active {
41
+ transform: scale(0.98);
42
+ box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
43
+ }
44
+
45
+ .white-key.active {
46
+ background: #f0f0f0;
47
+ }
48
+
49
+ .black-key.active {
50
+ background: #222;
51
+ }
52
+
53
+ .recording {
54
+ animation: pulse 1.5s infinite;
55
+ }
56
+
57
+ @keyframes pulse {
58
+ 0% { box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.7); }
59
+ 70% { box-shadow: 0 0 0 10px rgba(255, 0, 0, 0); }
60
+ 100% { box-shadow: 0 0 0 0 rgba(255, 0, 0, 0); }
61
+ }
62
+
63
+ .waveform {
64
+ height: 80px;
65
+ background: rgba(255, 255, 255, 0.1);
66
+ border-radius: 8px;
67
+ position: relative;
68
+ overflow: hidden;
69
+ }
70
+
71
+ .knob {
72
+ width: 40px;
73
+ height: 40px;
74
+ border-radius: 50%;
75
+ background: linear-gradient(135deg, #555, #222);
76
+ border: 2px solid #444;
77
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
78
+ position: relative;
79
+ cursor: pointer;
80
+ }
81
+
82
+ .knob::after {
83
+ content: '';
84
+ position: absolute;
85
+ top: 5px;
86
+ left: 50%;
87
+ width: 3px;
88
+ height: 15px;
89
+ background: #ffcc00;
90
+ transform-origin: bottom center;
91
+ transform: translateX(-50%) rotate(0deg);
92
+ }
93
+
94
+ .display {
95
+ background: linear-gradient(135deg, #2c3e50, #4ca1af);
96
+ border-radius: 10px;
97
+ box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5);
98
+ font-family: 'Courier New', monospace;
99
+ text-shadow: 0 0 5px rgba(0, 255, 255, 0.7);
100
+ }
101
+
102
+ .slider {
103
+ -webkit-appearance: none;
104
+ width: 100%;
105
+ height: 8px;
106
+ border-radius: 4px;
107
+ background: #555;
108
+ outline: none;
109
+ }
110
+
111
+ .slider::-webkit-slider-thumb {
112
+ -webkit-appearance: none;
113
+ appearance: none;
114
+ width: 18px;
115
+ height: 18px;
116
+ border-radius: 50%;
117
+ background: #ffcc00;
118
+ cursor: pointer;
119
+ }
120
+
121
+ #waveform-visualizer {
122
+ position: absolute;
123
+ top: 0;
124
+ left: 0;
125
+ width: 100%;
126
+ height: 100%;
127
+ }
128
+ </style>
129
+ </head>
130
+ <body class="flex flex-col items-center py-8">
131
+ <div class="w-full max-w-6xl px-4">
132
+ <h1 class="text-4xl font-bold text-center mb-2 text-yellow-400">Yamaha PSR-SX900 Keyboard Simulator</h1>
133
+ <p class="text-center text-gray-300 mb-8">Professional Arranger Workstation with Premium Sounds</p>
134
+
135
+ <!-- Display Panel -->
136
+ <div class="display p-4 mb-6 flex justify-between items-center">
137
+ <div class="text-xl">
138
+ <span id="current-preset">Grand Piano</span>
139
+ <span id="current-octave" class="ml-4">Octave: 4</span>
140
+ </div>
141
+ <div class="text-right">
142
+ <div id="bpm-display">BPM: 120</div>
143
+ <div id="recording-status">Ready</div>
144
+ </div>
145
+ </div>
146
+
147
+ <!-- Controls -->
148
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
149
+ <!-- Instrument Selection -->
150
+ <div class="bg-gray-800 p-4 rounded-lg">
151
+ <h2 class="text-xl font-semibold mb-4 text-yellow-300">Instrument Presets</h2>
152
+ <div class="grid grid-cols-2 gap-3">
153
+ <button class="preset-btn bg-blue-600 hover:bg-blue-700 py-2 px-3 rounded" data-preset="piano">Grand Piano</button>
154
+ <button class="preset-btn bg-blue-600 hover:bg-blue-700 py-2 px-3 rounded" data-preset="electric">Electric Piano</button>
155
+ <button class="preset-btn bg-blue-600 hover:bg-blue-700 py-2 px-3 rounded" data-preset="organ">Church Organ</button>
156
+ <button class="preset-btn bg-blue-600 hover:bg-blue-700 py-2 px-3 rounded" data-preset="strings">Orchestral Strings</button>
157
+ <button class="preset-btn bg-blue-600 hover:bg-blue-700 py-2 px-3 rounded" data-preset="synth">Synth Lead</button>
158
+ <button class="preset-btn bg-blue-600 hover:bg-blue-700 py-2 px-3 rounded" data-preset="guitar">Acoustic Guitar</button>
159
+ <button class="preset-btn bg-blue-600 hover:bg-blue-700 py-2 px-3 rounded" data-preset="bass">Electric Bass</button>
160
+ <button class="preset-btn bg-blue-600 hover:bg-blue-700 py-2 px-3 rounded" data-preset="drums">Drum Kit</button>
161
+ </div>
162
+ </div>
163
+
164
+ <!-- Effects -->
165
+ <div class="bg-gray-800 p-4 rounded-lg">
166
+ <h2 class="text-xl font-semibold mb-4 text-yellow-300">Effects</h2>
167
+ <div class="space-y-4">
168
+ <div>
169
+ <label class="block mb-1">Reverb</label>
170
+ <input type="range" min="0" max="100" value="30" class="slider" id="reverb-slider">
171
+ </div>
172
+ <div>
173
+ <label class="block mb-1">Chorus</label>
174
+ <input type="range" min="0" max="100" value="20" class="slider" id="chorus-slider">
175
+ </div>
176
+ <div>
177
+ <label class="block mb-1">Delay</label>
178
+ <input type="range" min="0" max="100" value="15" class="slider" id="delay-slider">
179
+ </div>
180
+ <div class="flex items-center space-x-4">
181
+ <div class="knob" id="eq-knob"></div>
182
+ <span>EQ</span>
183
+ </div>
184
+ </div>
185
+ </div>
186
+
187
+ <!-- Recording & Playback -->
188
+ <div class="bg-gray-800 p-4 rounded-lg">
189
+ <h2 class="text-xl font-semibold mb-4 text-yellow-300">Recording</h2>
190
+ <div class="waveform mb-4" id="waveform">
191
+ <canvas id="waveform-visualizer"></canvas>
192
+ </div>
193
+ <div class="flex space-x-3">
194
+ <button id="record-btn" class="flex-1 bg-red-600 hover:bg-red-700 py-2 px-4 rounded flex items-center justify-center">
195
+ <i class="fas fa-circle mr-2"></i> Record
196
+ </button>
197
+ <button id="play-btn" class="flex-1 bg-green-600 hover:bg-green-700 py-2 px-4 rounded flex items-center justify-center" disabled>
198
+ <i class="fas fa-play mr-2"></i> Play
199
+ </button>
200
+ <button id="stop-btn" class="flex-1 bg-gray-600 hover:bg-gray-700 py-2 px-4 rounded flex items-center justify-center" disabled>
201
+ <i class="fas fa-stop mr-2"></i> Stop
202
+ </button>
203
+ </div>
204
+ <div class="mt-4">
205
+ <button id="save-btn" class="bg-blue-600 hover:bg-blue-700 py-2 px-4 rounded w-full">
206
+ <i class="fas fa-save mr-2"></i> Save Recording
207
+ </button>
208
+ </div>
209
+ </div>
210
+ </div>
211
+
212
+ <!-- Keyboard -->
213
+ <div class="relative mb-8" style="height: 200px;">
214
+ <!-- White Keys -->
215
+ <div class="flex h-full">
216
+ <div class="white-key key w-12 border rounded-b-lg flex items-end justify-center pb-2" data-note="C">C</div>
217
+ <div class="white-key key w-12 border rounded-b-lg flex items-end justify-center pb-2" data-note="D">D</div>
218
+ <div class="white-key key w-12 border rounded-b-lg flex items-end justify-center pb-2" data-note="E">E</div>
219
+ <div class="white-key key w-12 border rounded-b-lg flex items-end justify-center pb-2" data-note="F">F</div>
220
+ <div class="white-key key w-12 border rounded-b-lg flex items-end justify-center pb-2" data-note="G">G</div>
221
+ <div class="white-key key w-12 border rounded-b-lg flex items-end justify-center pb-2" data-note="A">A</div>
222
+ <div class="white-key key w-12 border rounded-b-lg flex items-end justify-center pb-2" data-note="B">B</div>
223
+ <div class="white-key key w-12 border rounded-b-lg flex items-end justify-center pb-2" data-note="C5">C</div>
224
+ </div>
225
+
226
+ <!-- Black Keys -->
227
+ <div class="flex absolute top-0" style="left: 30px;">
228
+ <div class="black-key key w-8 rounded-b-lg" data-note="C#"></div>
229
+ <div class="black-key key w-8 rounded-b-lg" data-note="D#"></div>
230
+ <div style="width: 48px;"></div> <!-- Space for F-G -->
231
+ <div class="black-key key w-8 rounded-b-lg" data-note="F#"></div>
232
+ <div class="black-key key w-8 rounded-b-lg" data-note="G#"></div>
233
+ <div class="black-key key w-8 rounded-b-lg" data-note="A#"></div>
234
+ </div>
235
+ </div>
236
+
237
+ <!-- Octave Controls -->
238
+ <div class="flex justify-center mb-8">
239
+ <button id="octave-down" class="bg-gray-700 hover:bg-gray-600 py-2 px-4 rounded-l">
240
+ <i class="fas fa-arrow-down"></i> Octave Down
241
+ </button>
242
+ <button id="octave-up" class="bg-gray-700 hover:bg-gray-600 py-2 px-4 rounded-r">
243
+ Octave Up <i class="fas fa-arrow-up"></i>
244
+ </button>
245
+ </div>
246
+
247
+ <!-- Metronome -->
248
+ <div class="bg-gray-800 p-4 rounded-lg mb-8">
249
+ <h2 class="text-xl font-semibold mb-4 text-yellow-300">Metronome</h2>
250
+ <div class="flex items-center space-x-4">
251
+ <button id="metronome-btn" class="bg-purple-600 hover:bg-purple-700 py-2 px-4 rounded">
252
+ <i class="fas fa-music mr-2"></i> Toggle Metronome
253
+ </button>
254
+ <div class="flex-1">
255
+ <label class="block mb-1">BPM</label>
256
+ <input type="range" min="40" max="240" value="120" class="slider" id="bpm-slider">
257
+ </div>
258
+ <div class="w-24 text-center">
259
+ <div class="text-2xl font-bold" id="metronome-display">♩ = 120</div>
260
+ </div>
261
+ </div>
262
+ </div>
263
+
264
+ <!-- Practice Tools -->
265
+ <div class="bg-gray-800 p-4 rounded-lg">
266
+ <h2 class="text-xl font-semibold mb-4 text-yellow-300">Practice Tools</h2>
267
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
268
+ <div class="bg-gray-700 p-3 rounded">
269
+ <h3 class="font-medium mb-2">Chord Trainer</h3>
270
+ <button class="chord-btn bg-green-600 hover:bg-green-700 py-1 px-3 rounded text-sm w-full" data-chord="C">
271
+ Play C Major
272
+ </button>
273
+ </div>
274
+ <div class="bg-gray-700 p-3 rounded">
275
+ <h3 class="font-medium mb-2">Scale Practice</h3>
276
+ <select class="scale-select w-full bg-gray-800 rounded p-1 text-sm mb-2">
277
+ <option value="C-major">C Major Scale</option>
278
+ <option value="A-minor">A Minor Scale</option>
279
+ <option value="G-pentatonic">G Pentatonic</option>
280
+ <option value="A-blues">A Blues Scale</option>
281
+ </select>
282
+ <button class="scale-btn bg-green-600 hover:bg-green-700 py-1 px-3 rounded text-sm w-full">
283
+ Start Practice
284
+ </button>
285
+ </div>
286
+ <div class="bg-gray-700 p-3 rounded">
287
+ <h3 class="font-medium mb-2">Song Learning</h3>
288
+ <select class="song-select w-full bg-gray-800 rounded p-1 text-sm mb-2">
289
+ <option value="twinkle">Twinkle Twinkle</option>
290
+ <option value="happy">Happy Birthday</option>
291
+ <option value="fur-elise">Fur Elise</option>
292
+ <option value="canon">Canon in D</option>
293
+ </select>
294
+ <button class="song-btn bg-green-600 hover:bg-green-700 py-1 px-3 rounded text-sm w-full">
295
+ Load Song
296
+ </button>
297
+ </div>
298
+ </div>
299
+ </div>
300
+ </div>
301
+
302
+ <script>
303
+ // Audio Context Setup
304
+ const AudioContext = window.AudioContext || window.webkitAudioContext;
305
+ const audioContext = new AudioContext();
306
+ const audioElements = {};
307
+
308
+ // Global variables
309
+ let currentPreset = 'piano';
310
+ let currentOctave = 4;
311
+ let isRecording = false;
312
+ let recordingStartTime;
313
+ let recordedNotes = [];
314
+ let isPlayingRecording = false;
315
+ let metronomeInterval;
316
+ let isMetronomeOn = false;
317
+ let activeOscillators = {};
318
+
319
+ // DOM Elements
320
+ const keys = document.querySelectorAll('.key');
321
+ const presetButtons = document.querySelectorAll('.preset-btn');
322
+ const currentPresetDisplay = document.getElementById('current-preset');
323
+ const currentOctaveDisplay = document.getElementById('current-octave');
324
+ const recordBtn = document.getElementById('record-btn');
325
+ const playBtn = document.getElementById('play-btn');
326
+ const stopBtn = document.getElementById('stop-btn');
327
+ const saveBtn = document.getElementById('save-btn');
328
+ const recordingStatus = document.getElementById('recording-status');
329
+ const octaveUpBtn = document.getElementById('octave-up');
330
+ const octaveDownBtn = document.getElementById('octave-down');
331
+ const metronomeBtn = document.getElementById('metronome-btn');
332
+ const bpmSlider = document.getElementById('bpm-slider');
333
+ const bpmDisplay = document.getElementById('bpm-display');
334
+ const metronomeDisplay = document.getElementById('metronome-display');
335
+ const waveformCanvas = document.getElementById('waveform-visualizer');
336
+ const ctx = waveformCanvas.getContext('2d');
337
+
338
+ // Effects controls
339
+ const reverbSlider = document.getElementById('reverb-slider');
340
+ const chorusSlider = document.getElementById('chorus-slider');
341
+ const delaySlider = document.getElementById('delay-slider');
342
+ const eqKnob = document.getElementById('eq-knob');
343
+
344
+ // Practice tools
345
+ const chordButtons = document.querySelectorAll('.chord-btn');
346
+ const scaleSelect = document.querySelector('.scale-select');
347
+ const scaleButton = document.querySelector('.scale-btn');
348
+ const songSelect = document.querySelector('.song-select');
349
+ const songButton = document.querySelector('.song-btn');
350
+
351
+ // Initialize canvas
352
+ waveformCanvas.width = waveformCanvas.parentElement.offsetWidth;
353
+ waveformCanvas.height = waveformCanvas.parentElement.offsetHeight;
354
+
355
+ // Note frequencies (A4 = 440Hz)
356
+ const noteFrequencies = {
357
+ 'C': 261.63, 'C#': 277.18, 'D': 293.66, 'D#': 311.13,
358
+ 'E': 329.63, 'F': 349.23, 'F#': 369.99, 'G': 392.00,
359
+ 'G#': 415.30, 'A': 440.00, 'A#': 466.16, 'B': 493.88,
360
+ 'C5': 523.25
361
+ };
362
+
363
+ // Initialize sound bank
364
+ function initializeAudioElements() {
365
+ const samples = {
366
+ 'piano': ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'],
367
+ 'electric': ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'],
368
+ 'organ': ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'],
369
+ 'strings': ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'],
370
+ 'synth': ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'],
371
+ 'guitar': ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'],
372
+ 'bass': ['C2', 'D2', 'E2', 'F2', 'G2', 'A2', 'B2', 'C3'],
373
+ 'drums': ['kick', 'snare', 'hihat', 'tom']
374
+ };
375
+
376
+ // In a real implementation, you would load actual audio files here
377
+ // For this demo, we're using Web Audio API oscillators instead
378
+ }
379
+
380
+ // Initialize
381
+ initializeAudioElements();
382
+ updatePresetDisplay();
383
+ drawEmptyWaveform();
384
+
385
+ // Event Listeners
386
+ keys.forEach(key => {
387
+ key.addEventListener('mousedown', () => playNote(key));
388
+ key.addEventListener('mouseup', () => releaseNote(key));
389
+ key.addEventListener('mouseleave', () => releaseNote(key));
390
+ });
391
+
392
+ document.addEventListener('keydown', (e) => {
393
+ const keyMap = {
394
+ 'a': 'C', 'w': 'C#', 's': 'D', 'e': 'D#',
395
+ 'd': 'E', 'f': 'F', 't': 'F#', 'g': 'G',
396
+ 'y': 'G#', 'h': 'A', 'u': 'A#', 'j': 'B',
397
+ 'k': 'C5'
398
+ };
399
+
400
+ const note = keyMap[e.key.toLowerCase()];
401
+ if (note) {
402
+ const keyElement = document.querySelector(`.key[data-note="${note}"]`);
403
+ if (keyElement && !keyElement.classList.contains('active')) {
404
+ playNote(keyElement);
405
+ }
406
+ }
407
+ });
408
+
409
+ document.addEventListener('keyup', (e) => {
410
+ const keyMap = {
411
+ 'a': 'C', 'w': 'C#', 's': 'D', 'e': 'D#',
412
+ 'd': 'E', 'f': 'F', 't': 'F#', 'g': 'G',
413
+ 'y': 'G#', 'h': 'A', 'u': 'A#', 'j': 'B',
414
+ 'k': 'C5'
415
+ };
416
+
417
+ const note = keyMap[e.key.toLowerCase()];
418
+ if (note) {
419
+ const keyElement = document.querySelector(`.key[data-note="${note}"]`);
420
+ if (keyElement) {
421
+ releaseNote(keyElement);
422
+ }
423
+ }
424
+ });
425
+
426
+ presetButtons.forEach(button => {
427
+ button.addEventListener('click', () => {
428
+ currentPreset = button.dataset.preset;
429
+ updatePresetDisplay();
430
+ });
431
+ });
432
+
433
+ recordBtn.addEventListener('click', toggleRecording);
434
+ playBtn.addEventListener('click', playRecording);
435
+ stopBtn.addEventListener('click', stopPlayback);
436
+ saveBtn.addEventListener('click', saveRecording);
437
+
438
+ octaveUpBtn.addEventListener('click', () => {
439
+ if (currentOctave < 7) {
440
+ currentOctave++;
441
+ updateOctaveDisplay();
442
+ }
443
+ });
444
+
445
+ octaveDownBtn.addEventListener('click', () => {
446
+ if (currentOctave > 1) {
447
+ currentOctave--;
448
+ updateOctaveDisplay();
449
+ }
450
+ });
451
+
452
+ metronomeBtn.addEventListener('click', toggleMetronome);
453
+ bpmSlider.addEventListener('input', updateBPM);
454
+
455
+ // Chord buttons
456
+ chordButtons.forEach(button => {
457
+ button.addEventListener('click', () => playChord(button.dataset.chord));
458
+ });
459
+
460
+ scaleButton.addEventListener('click', () => playScale(scaleSelect.value));
461
+ songButton.addEventListener('click', () => playSong(songSelect.value));
462
+
463
+ // Audio Functions
464
+ function playNote(keyElement) {
465
+ const note = keyElement.dataset.note;
466
+
467
+ // Check if this key is already playing (from another source)
468
+ if (activeOscillators[note]) {
469
+ return;
470
+ }
471
+
472
+ keyElement.classList.add('active');
473
+
474
+ if (currentPreset === 'drums') {
475
+ playDrumSound(note);
476
+ return;
477
+ }
478
+
479
+ // For other instruments, use Web Audio API oscillators
480
+ const oscillator = audioContext.createOscillator();
481
+ const gainNode = audioContext.createGain();
482
+
483
+ // Create a simple ADSR envelope
484
+ const now = audioContext.currentTime;
485
+ gainNode.gain.setValueAtTime(0, now);
486
+
487
+ // Attack
488
+ gainNode.gain.linearRampToValueAtTime(1, now + 0.05);
489
+
490
+ // Sustain (we'll handle release in releaseNote)
491
+ oscillator.type = getOscillatorTypeForPreset();
492
+ oscillator.frequency.value = noteFrequencies[note] * (currentPreset === 'bass' ? 0.25 : 1);
493
+
494
+ // Connect nodes with effects
495
+ const nodes = applyEffects(oscillator, gainNode);
496
+ nodes[nodes.length - 1].connect(audioContext.destination);
497
+
498
+ oscillator.start();
499
+
500
+ // Store reference to stop later
501
+ activeOscillators[note] = {
502
+ oscillator: oscillator,
503
+ gainNode: gainNode,
504
+ keyElement: keyElement
505
+ };
506
+
507
+ // Record the note if recording
508
+ if (isRecording) {
509
+ recordedNotes.push({
510
+ type: 'note',
511
+ note: note,
512
+ time: audioContext.currentTime - recordingStartTime,
513
+ action: 'start',
514
+ preset: currentPreset,
515
+ octave: currentOctave
516
+ });
517
+ updateWaveform();
518
+ }
519
+ }
520
+
521
+ function releaseNote(keyElement) {
522
+ const note = keyElement.dataset.note;
523
+
524
+ if (!activeOscillators[note]) return;
525
+
526
+ keyElement.classList.remove('active');
527
+
528
+ if (currentPreset === 'drums') return;
529
+
530
+ // Release the note
531
+ const now = audioContext.currentTime;
532
+ activeOscillators[note].gainNode.gain.cancelScheduledValues(now);
533
+ activeOscillators[note].gainNode.gain.setValueAtTime(activeOscillators[note].gainNode.gain.value, now);
534
+ activeOscillators[note].gainNode.gain.exponentialRampToValueAtTime(0.001, now + 0.2);
535
+
536
+ // Clean up after release
537
+ setTimeout(() => {
538
+ if (activeOscillators[note]) {
539
+ activeOscillators[note].oscillator.stop();
540
+ delete activeOscillators[note];
541
+ }
542
+ }, 200);
543
+
544
+ // Record the note release if recording
545
+ if (isRecording) {
546
+ recordedNotes.push({
547
+ type: 'note',
548
+ note: note,
549
+ time: audioContext.currentTime - recordingStartTime,
550
+ action: 'stop'
551
+ });
552
+ updateWaveform();
553
+ }
554
+ }
555
+
556
+ function playDrumSound(type) {
557
+ const oscillator = audioContext.createOscillator();
558
+ const gainNode = audioContext.createGain();
559
+
560
+ let frequency, oscType, duration;
561
+
562
+ // Basic drum mapping
563
+ switch(type) {
564
+ case 'C': // Kick
565
+ frequency = 80;
566
+ oscType = 'sine';
567
+ duration = 0.3;
568
+ break;
569
+ case 'D': // Snare
570
+ frequency = 150;
571
+ oscType = 'white';
572
+ duration = 0.2;
573
+ break;
574
+ case 'E': // Hi-hat
575
+ frequency = 800;
576
+ oscType = 'white';
577
+ duration = 0.05;
578
+ break;
579
+ default: // Tom
580
+ frequency = 200;
581
+ oscType = 'sine';
582
+ duration = 0.15;
583
+ }
584
+
585
+ const now = audioContext.currentTime;
586
+
587
+ if (oscType === 'white') {
588
+ // White noise for snare/hi-hat
589
+ const bufferSize = audioContext.sampleRate * duration;
590
+ const noiseBuffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
591
+ const output = noiseBuffer.getChannelData(0);
592
+
593
+ for (let i = 0; i < bufferSize; i++) {
594
+ output[i] = Math.random() * 2 - 1;
595
+ }
596
+
597
+ const noise = audioContext.createBufferSource();
598
+ noise.buffer = noiseBuffer;
599
+
600
+ const filter = audioContext.createBiquadFilter();
601
+ filter.type = 'highpass';
602
+ filter.frequency.value = frequency;
603
+
604
+ noise.connect(filter);
605
+ filter.connect(gainNode);
606
+
607
+ // Envelope
608
+ gainNode.gain.setValueAtTime(1, now);
609
+ gainNode.gain.exponentialRampToValueAtTime(0.01, now + duration);
610
+
611
+ // Apply effects and connect to output
612
+ const nodes = applyEffects(noise, gainNode);
613
+ nodes[nodes.length - 1].connect(audioContext.destination);
614
+
615
+ noise.start();
616
+ noise.stop(now + duration);
617
+ } else {
618
+ // Regular oscillator for kick/tom
619
+ oscillator.type = oscType;
620
+ oscillator.frequency.value = frequency;
621
+
622
+ // Frequency envelope for kick
623
+ oscillator.frequency.exponentialRampToValueAtTime(1, now + duration);
624
+
625
+ // Gain envelope
626
+ gainNode.gain.setValueAtTime(1, now);
627
+ gainNode.gain.exponentialRampToValueAtTime(0.01, now + duration);
628
+
629
+ // Apply effects and connect to output
630
+ const nodes = applyEffects(oscillator, gainNode);
631
+ nodes[nodes.length - 1].connect(audioContext.destination);
632
+
633
+ oscillator.start();
634
+ oscillator.stop(now + duration);
635
+ }
636
+
637
+ // Record the drum hit if recording
638
+ if (isRecording) {
639
+ recordedNotes.push({
640
+ type: 'drum',
641
+ note: type,
642
+ time: audioContext.currentTime - recordingStartTime,
643
+ frequency: frequency,
644
+ duration: duration
645
+ });
646
+ updateWaveform();
647
+ }
648
+ }
649
+
650
+ function applyEffects(sourceNode, gainNode) {
651
+ const nodes = [sourceNode];
652
+
653
+ // Reverb
654
+ const reverb = audioContext.createConvolver();
655
+ const reverbGain = audioContext.createGain();
656
+ reverbGain.gain.value = reverbSlider.value / 100 * 0.3;
657
+
658
+ // Simple impulse response for reverb
659
+ const length = audioContext.sampleRate * 1;
660
+ const impulse = audioContext.createBuffer(2, length, audioContext.sampleRate);
661
+ const left = impulse.getChannelData(0);
662
+ const right = impulse.getChannelData(1);
663
+
664
+ for (let i = 0; i < length; i++) {
665
+ const n = length - i;
666
+ left[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, 3);
667
+ right[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, 3);
668
+ }
669
+
670
+ reverb.buffer = impulse;
671
+
672
+ // Chorus
673
+ const chorus = audioContext.createDelay(0.1);
674
+ chorus.delayTime.value = 0.03;
675
+
676
+ const chorusLFO = audioContext.createOscillator();
677
+ chorusLFO.frequency.value = 2;
678
+
679
+ const chorusDepth = audioContext.createGain();
680
+ chorusDepth.gain.value = chorusSlider.value / 100 * 0.01;
681
+
682
+ chorusLFO.connect(chorusDepth);
683
+ chorusDepth.connect(chorus.delayTime);
684
+ chorusLFO.start();
685
+
686
+ const chorusGain = audioContext.createGain();
687
+ chorusGain.gain.value = chorusSlider.value / 100 * 0.5;
688
+
689
+ // Delay
690
+ const delay = audioContext.createDelay(1.0);
691
+ delay.delayTime.value = 0.5;
692
+
693
+ const feedback = audioContext.createGain();
694
+ feedback.gain.value = delaySlider.value / 100 * 0.6;
695
+
696
+ const delayGain = audioContext.createGain();
697
+ delayGain.gain.value = delaySlider.value / 100 * 0.3;
698
+
699
+ // Connect nodes
700
+ const lastNode = nodes[nodes.length - 1];
701
+
702
+ // Main path (dry + effects)
703
+ if (gainNode) {
704
+ lastNode.connect(gainNode);
705
+ nodes.push(gainNode);
706
+ }
707
+
708
+ // Reverb path
709
+ nodes[nodes.length - 1].connect(reverb);
710
+ reverb.connect(reverbGain);
711
+ nodes.push(reverbGain);
712
+
713
+ // Chorus path
714
+ nodes[0].connect(chorus);
715
+ chorus.connect(chorusGain);
716
+ nodes.push(chorusGain);
717
+
718
+ // Delay path (feedback loop)
719
+ nodes[0].connect(delay);
720
+ delay.connect(delayGain);
721
+ delayGain.connect(audioContext.destination);
722
+ delay.connect(feedback);
723
+ feedback.connect(delay);
724
+
725
+ return nodes;
726
+ }
727
+
728
+ function getOscillatorTypeForPreset() {
729
+ switch(currentPreset) {
730
+ case 'piano':
731
+ case 'electric':
732
+ return 'sine';
733
+ case 'organ':
734
+ return 'sine';
735
+ case 'strings':
736
+ return 'sawtooth';
737
+ case 'synth':
738
+ return 'square';
739
+ case 'guitar':
740
+ return 'sine';
741
+ case 'bass':
742
+ return 'sine';
743
+ default:
744
+ return 'sine';
745
+ }
746
+ }
747
+
748
+ // Recording Functions
749
+ function toggleRecording() {
750
+ if (isRecording) {
751
+ // Stop recording
752
+ isRecording = false;
753
+ recordBtn.classList.remove('recording');
754
+ recordingStatus.textContent = 'Recording saved';
755
+ playBtn.disabled = recordedNotes.length === 0;
756
+ stopBtn.disabled = true;
757
+ recordBtn.innerHTML = '<i class="fas fa-circle mr-2"></i> Record';
758
+ } else {
759
+ // Start recording
760
+ isRecording = true;
761
+ recordedNotes = [];
762
+ recordingStartTime = audioContext.currentTime;
763
+ recordBtn.classList.add('recording');
764
+ recordingStatus.textContent = 'Recording...';
765
+ playBtn.disabled = true;
766
+ stopBtn.disabled = true;
767
+ recordBtn.innerHTML = '<i class="fas fa-stop mr-2"></i> Stop';
768
+ drawEmptyWaveform();
769
+ }
770
+ }
771
+
772
+ function playRecording() {
773
+ if (recordedNotes.length === 0 || isPlayingRecording) return;
774
+
775
+ isPlayingRecording = true;
776
+ playBtn.disabled = true;
777
+ stopBtn.disabled = false;
778
+ recordingStatus.textContent = 'Playing back...';
779
+
780
+ const playStartTime = audioContext.currentTime;
781
+
782
+ // Play each recorded note
783
+ recordedNotes.forEach(note => {
784
+ const delay = note.time * 1000; // Convert to milliseconds
785
+
786
+ setTimeout(() => {
787
+ if (note.type === 'drum') {
788
+ // Play drum sound
789
+ const oscillator = audioContext.createOscillator();
790
+ const gainNode = audioContext.createGain();
791
+
792
+ if (note.frequency > 500) { // Hi-hat
793
+ const bufferSize = audioContext.sampleRate * note.duration;
794
+ const noiseBuffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
795
+ const output = noiseBuffer.getChannelData(0);
796
+
797
+ for (let i = 0; i < bufferSize; i++) {
798
+ output[i] = Math.random() * 2 - 1;
799
+ }
800
+
801
+ const noise = audioContext.createBufferSource();
802
+ noise.buffer = noiseBuffer;
803
+
804
+ const filter = audioContext.createBiquadFilter();
805
+ filter.type = 'highpass';
806
+ filter.frequency.value = note.frequency;
807
+
808
+ noise.connect(filter);
809
+ filter.connect(gainNode);
810
+
811
+ gainNode.gain.setValueAtTime(1, audioContext.currentTime);
812
+ gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + note.duration);
813
+
814
+ noise.start();
815
+ noise.stop(audioContext.currentTime + note.duration);
816
+ } else {
817
+ oscillator.type = note.frequency > 150 ? 'sine' : 'sine';
818
+ oscillator.frequency.value = note.frequency;
819
+ oscillator.connect(gainNode);
820
+
821
+ gainNode.gain.setValueAtTime(1, audioContext.currentTime);
822
+ gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + note.duration);
823
+
824
+ oscillator.start();
825
+ oscillator.stop(audioContext.currentTime + note.duration);
826
+ }
827
+
828
+ gainNode.connect(audioContext.destination);
829
+ } else if (note.action === 'start') {
830
+ // Play regular note
831
+ const keyElement = document.querySelector(`.key[data-note="${note.note}"]`);
832
+ if (keyElement) {
833
+ playNote(keyElement);
834
+ }
835
+ } else if (note.action === 'stop') {
836
+ // Release note
837
+ const keyElement = document.querySelector(`.key[data-note="${note.note}"]`);
838
+ if (keyElement) {
839
+ releaseNote(keyElement);
840
+ }
841
+ }
842
+ }, delay);
843
+ });
844
+
845
+ // Stop the playback after the last note
846
+ const totalDuration = recordedNotes[recordedNotes.length - 1].time * 1000 + 1000;
847
+ setTimeout(() => {
848
+ if (isPlayingRecording) {
849
+ stopPlayback();
850
+ }
851
+ }, totalDuration);
852
+ }
853
+
854
+ function stopPlayback() {
855
+ isPlayingRecording = false;
856
+ playBtn.disabled = recordedNotes.length === 0;
857
+ stopBtn.disabled = true;
858
+ recordingStatus.textContent = 'Ready';
859
+
860
+ // Stop all playing notes
861
+ Object.keys(activeOscillators).forEach(note => {
862
+ if (activeOscillators[note]) {
863
+ releaseNote(activeOscillators[note].keyElement);
864
+ }
865
+ });
866
+ }
867
+
868
+ function saveRecording() {
869
+ if (recordedNotes.length === 0) {
870
+ alert('No recording to save!');
871
+ return;
872
+ }
873
+
874
+ const recordingName = prompt('Enter a name for your recording:', `Recording ${new Date().toLocaleString()}`);
875
+ if (recordingName) {
876
+ // In a real implementation, you would save to localStorage or a server
877
+ alert(`Recording "${recordingName}" saved! (This is a demo - recording is not actually saved)`);
878
+ }
879
+ }
880
+
881
+ // Helper Functions
882
+ function updatePresetDisplay() {
883
+ const presetNames = {
884
+ 'piano': 'Grand Piano',
885
+ 'electric': 'Electric Piano',
886
+ 'organ': 'Church Organ',
887
+ 'strings': 'Orchestral Strings',
888
+ 'synth': 'Synth Lead',
889
+ 'guitar': 'Acoustic Guitar',
890
+ 'bass': 'Electric Bass',
891
+ 'drums': 'Drum Kit'
892
+ };
893
+ currentPresetDisplay.textContent = presetNames[currentPreset];
894
+ }
895
+
896
+ function updateOctaveDisplay() {
897
+ currentOctaveDisplay.textContent = `Octave: ${currentOctave}`;
898
+ }
899
+
900
+ function updateBPM() {
901
+ const bpm = bpmSlider.value;
902
+ bpmDisplay.textContent = `BPM: ${bpm}`;
903
+ metronomeDisplay.textContent = `♩ = ${bpm}`;
904
+
905
+ if (isMetronomeOn) {
906
+ clearInterval(metronomeInterval);
907
+ startMetronome();
908
+ }
909
+ }
910
+
911
+ function toggleMetronome() {
912
+ isMetronomeOn = !isMetronomeOn;
913
+
914
+ if (isMetronomeOn) {
915
+ metronomeBtn.classList.add('bg-purple-700');
916
+ metronomeBtn.classList.remove('bg-purple-600');
917
+ startMetronome();
918
+ } else {
919
+ metronomeBtn.classList.remove('bg-purple-700');
920
+ metronomeBtn.classList.add('bg-purple-600');
921
+ clearInterval(metronomeInterval);
922
+ }
923
+ }
924
+
925
+ function startMetronome() {
926
+ const bpm = parseInt(bpmSlider.value);
927
+ const interval = 60000 / bpm; // Convert BPM to milliseconds
928
+
929
+ // Play first click immediately
930
+ playMetronomeClick();
931
+
932
+ // Then set up the interval
933
+ metronomeInterval = setInterval(playMetronomeClick, interval);
934
+ }
935
+
936
+ function playMetronomeClick() {
937
+ const oscillator = audioContext.createOscillator();
938
+ const gainNode = audioContext.createGain();
939
+
940
+ oscillator.type = 'square';
941
+ oscillator.frequency.value = 800;
942
+
943
+ const now = audioContext.currentTime;
944
+ gainNode.gain.setValueAtTime(1, now);
945
+ gainNode.gain.exponentialRampToValueAtTime(0.001, now + 0.05);
946
+
947
+ oscillator.connect(gainNode);
948
+ gainNode.connect(audioContext.destination);
949
+
950
+ oscillator.start();
951
+ oscillator.stop(now + 0.1);
952
+ }
953
+
954
+ function updateWaveform() {
955
+ const width = waveformCanvas.width;
956
+ const height = waveformCanvas.height;
957
+
958
+ ctx.clearRect(0, 0, width, height);
959
+
960
+ // Simple visualization of notes
961
+ const maxTime = Math.max(1, ...recordedNotes.map(n => n.time));
962
+ const scaleX = width / maxTime;
963
+
964
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
965
+ ctx.strokeStyle = '#4ade80';
966
+ ctx.lineWidth = 2;
967
+
968
+ recordedNotes.forEach(note => {
969
+ if (note.action === 'start' || note.type === 'drum') {
970
+ const x = note.time * scaleX;
971
+ const noteHeight = note.type === 'drum' ? 20 : mapNoteToHeight(note.note);
972
+
973
+ ctx.beginPath();
974
+ ctx.arc(x, height - noteHeight - 10, 5, 0, Math.PI * 2);
975
+ ctx.fill();
976
+
977
+ // Draw line to connect notes
978
+ const endNote = recordedNotes.find(
979
+ n => n.note === note.note && n.action === 'stop' && n.time > note.time
980
+ );
981
+
982
+ if (endNote) {
983
+ const endX = endNote.time * scaleX;
984
+ ctx.beginPath();
985
+ ctx.moveTo(x, height - noteHeight - 10);
986
+ ctx.lineTo(endX, height - noteHeight - 10);
987
+ ctx.stroke();
988
+ }
989
+ }
990
+ });
991
+ }
992
+
993
+ function drawEmptyWaveform() {
994
+ const width = waveformCanvas.width;
995
+ const height = waveformCanvas.height;
996
+
997
+ ctx.clearRect(0, 0, width, height);
998
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
999
+ ctx.fillRect(0, 0, width, height);
1000
+
1001
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
1002
+ ctx.textAlign = 'center';
1003
+ ctx.font = '14px Poppins';
1004
+ ctx.fillText(isRecording ? 'Recording...' : 'No recording yet', width / 2, height / 2);
1005
+ }
1006
+
1007
+ function mapNoteToHeight(note) {
1008
+ const noteOrder = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B', 'C5'];
1009
+ const index = noteOrder.indexOf(note);
1010
+ return 10 + (index * 5);
1011
+ }
1012
+
1013
+ // Practice Tools Functions
1014
+ function playChord(chord) {
1015
+ const chordNotes = {
1016
+ 'C': ['C', 'E', 'G'],
1017
+ 'D': ['D', 'F#', 'A'],
1018
+ 'E': ['E', 'G#', 'B'],
1019
+ 'F': ['F', 'A', 'C'],
1020
+ 'G': ['G', 'B', 'D'],
1021
+ 'A': ['A', 'C#', 'E'],
1022
+ 'B': ['B', 'D#', 'F#']
1023
+ };
1024
+
1025
+ const notes = chordNotes[chord] || chordNotes['C'];
1026
+ notes.forEach(note => {
1027
+ const keyElement = document.querySelector(`.key[data-note="${note}"]`);
1028
+ if (keyElement) {
1029
+ playNote(keyElement);
1030
+
1031
+ // Release the note after 1 second
1032
+ setTimeout(() => {
1033
+ releaseNote(keyElement);
1034
+ }, 1000);
1035
+ }
1036
+ });
1037
+ }
1038
+
1039
+ function playScale(scale) {
1040
+ const scalePatterns = {
1041
+ 'C-major': ['C', 'D', 'E', 'F', 'G', 'A', 'B', 'C5'],
1042
+ 'A-minor': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'A'],
1043
+ 'G-pentatonic': ['G', 'A', 'B', 'D', 'E', 'G'],
1044
+ 'A-blues': ['A', 'C', 'D', 'D#', 'E', 'G', 'A']
1045
+ };
1046
+
1047
+ const notes = scalePatterns[scale] || scalePatterns['C-major'];
1048
+ playNotesWithTiming(notes);
1049
+ }
1050
+
1051
+ function playSong(song) {
1052
+ const songPatterns = {
1053
+ 'twinkle': [
1054
+ {note: 'C', duration: 0.5}, {note: 'C', duration: 0.5},
1055
+ {note: 'G', duration: 0.5}, {note: 'G', duration: 0.5},
1056
+ {note: 'A', duration: 0.5}, {note: 'A', duration: 0.5},
1057
+ {note: 'G', duration: 1},
1058
+
1059
+ {note: 'F', duration: 0.5}, {note: 'F', duration: 0.5},
1060
+ {note: 'E', duration: 0.5}, {note: 'E', duration: 0.5},
1061
+ {note: 'D', duration: 0.5}, {note: 'D', duration: 0.5},
1062
+ {note: 'C', duration: 1}
1063
+ ],
1064
+ 'happy': [
1065
+ {note: 'G', duration: 0.25}, {note: 'G', duration: 0.25},
1066
+ {note: 'A', duration: 0.5}, {note: 'G', duration: 0.5},
1067
+ {note: 'C', duration: 0.5}, {note: 'B', duration: 1},
1068
+
1069
+ {note: 'G', duration: 0.25}, {note: 'G', duration: 0.25},
1070
+ {note: 'A', duration: 0.5}, {note: 'G', duration: 0.5},
1071
+ {note: 'D', duration: 0.5}, {note: 'C', duration: 1}
1072
+ ],
1073
+ 'fur-elise': [
1074
+ {note: 'E', duration: 0.25}, {note: 'D#', duration: 0.25},
1075
+ {note: 'E', duration: 0.25}, {note: 'D#', duration: 0.25},
1076
+ {note: 'E', duration: 0.25}, {note: 'B', duration: 0.25},
1077
+ {note: 'D', duration: 0.25}, {note: 'C', duration: 0.25},
1078
+ {note: 'A', duration: 0.5}
1079
+ ],
1080
+ 'canon': [
1081
+ {note: 'G', duration: 0.5}, {note: 'D', duration: 0.5},
1082
+ {note: 'B', duration: 0.5}, {note: 'A', duration: 0.5},
1083
+ {note: 'G', duration: 0.5}, {note: 'D', duration: 0.5},
1084
+ {note: 'B', duration: 0.5}, {note: 'A', duration: 0.5}
1085
+ ]
1086
+ };
1087
+
1088
+ const notes = songPatterns[song] || songPatterns['twinkle'];
1089
+ let time = 0;
1090
+
1091
+ notes.forEach(({note, duration}) => {
1092
+ setTimeout(() => {
1093
+ const keyElement = document.querySelector(`.key[data-note="${note}"]`);
1094
+ if (keyElement) {
1095
+ playNote(keyElement);
1096
+
1097
+ // Release the note after the duration
1098
+ setTimeout(() => {
1099
+ releaseNote(keyElement);
1100
+ }, duration * 800); // Slightly shorter than the full duration
1101
+ }
1102
+ }, time * 1000);
1103
+
1104
+ time += duration;
1105
+ });
1106
+ }
1107
+
1108
+ function playNotesWithTiming(notes, tempo = 1) {
1109
+ let time = 0;
1110
+ const noteDuration = 0.5 * tempo;
1111
+
1112
+ notes.forEach(note => {
1113
+ setTimeout(() => {
1114
+ const keyElement = document.querySelector(`.key[data-note="${note}"]`);
1115
+ if (keyElement) {
1116
+ playNote(keyElement);
1117
+
1118
+ // Release the note after the duration
1119
+ setTimeout(() => {
1120
+ releaseNote(keyElement);
1121
+ }, noteDuration * 800); // Slightly shorter than the full duration
1122
+ }
1123
+ }, time * 1000);
1124
+
1125
+ time += noteDuration;
1126
+ });
1127
+ }
1128
+
1129
+ // Initialize knob rotation
1130
+ let rotation = 0;
1131
+ eqKnob.addEventListener('mousedown', (e) => {
1132
+ const startY = e.clientY;
1133
+ const startRotation = rotation;
1134
+
1135
+ function rotateKnob(e) {
1136
+ const deltaY = startY - e.clientY;
1137
+ rotation = Math.min(90, Math.max(-90, startRotation + deltaY));
1138
+ eqKnob.style.transform = `rotate(${rotation}deg)`;
1139
+ }
1140
+
1141
+ function stopRotating() {
1142
+ document.removeEventListener('mousemove', rotateKnob);
1143
+ document.removeEventListener('mouseup', stopRotating);
1144
+ }
1145
+
1146
+ document.addEventListener('mousemove', rotateKnob);
1147
+ document.addEventListener('mouseup', stopRotating);
1148
+ });
1149
+ </script>
1150
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=Parthiban97/music-player" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
1151
+ </html>