Tingchenliang commited on
Commit
fbba045
·
verified ·
1 Parent(s): 69d9105

Make the keyboard usable: when I click the notes, it'd make correct pitch - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +671 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Analog Synth
3
- emoji: 📈
4
- colorFrom: indigo
5
- colorTo: yellow
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: analog-synth
3
+ emoji: 🐳
4
+ colorFrom: green
5
+ colorTo: purple
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,671 @@
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>Vintage Analog Synth</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
+ <script>
10
+ tailwind.config = {
11
+ theme: {
12
+ extend: {
13
+ colors: {
14
+ synth: {
15
+ dark: '#1a1a2e',
16
+ panel: '#2d2d44',
17
+ knob: '#4a4a6a',
18
+ highlight: '#ff6b6b',
19
+ wood: '#8b4513',
20
+ metal: '#a9a9a9',
21
+ label: '#d1d1e9'
22
+ }
23
+ },
24
+ fontFamily: {
25
+ 'orbitron': ['Orbitron', 'sans-serif'],
26
+ 'audiowide': ['Audiowide', 'cursive']
27
+ }
28
+ }
29
+ }
30
+ }
31
+ </script>
32
+ <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700&family=Audiowide&display=swap" rel="stylesheet">
33
+ <style>
34
+ @keyframes glow {
35
+ 0% { box-shadow: 0 0 5px #ff6b6b; }
36
+ 50% { box-shadow: 0 0 20px #ff6b6b; }
37
+ 100% { box-shadow: 0 0 5px #ff6b6b; }
38
+ }
39
+
40
+ .knob {
41
+ position: relative;
42
+ width: 60px;
43
+ height: 60px;
44
+ border-radius: 50%;
45
+ background: linear-gradient(135deg, #4a4a6a 0%, #2d2d44 100%);
46
+ box-shadow:
47
+ inset 0 0 10px rgba(0, 0, 0, 0.5),
48
+ 0 5px 15px rgba(0, 0, 0, 0.5);
49
+ cursor: pointer;
50
+ transition: transform 0.1s;
51
+ }
52
+
53
+ .knob::before {
54
+ content: '';
55
+ position: absolute;
56
+ top: 5px;
57
+ left: 50%;
58
+ transform: translateX(-50%);
59
+ width: 6px;
60
+ height: 20px;
61
+ background: #ff6b6b;
62
+ border-radius: 3px;
63
+ }
64
+
65
+ .knob-label {
66
+ position: absolute;
67
+ bottom: -25px;
68
+ left: 0;
69
+ width: 100%;
70
+ text-align: center;
71
+ font-size: 0.75rem;
72
+ color: #d1d1e9;
73
+ }
74
+
75
+ .wood-panel {
76
+ background: linear-gradient(
77
+ to bottom,
78
+ #8b4513 0%,
79
+ #5d2e0a 20%,
80
+ #8b4513 40%,
81
+ #5d2e0a 60%,
82
+ #8b4513 80%,
83
+ #5d2e0a 100%
84
+ );
85
+ border: 2px solid #5d2e0a;
86
+ box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5);
87
+ }
88
+
89
+ .metal-frame {
90
+ background: linear-gradient(135deg, #c0c0c0 0%, #a9a9a9 100%);
91
+ border: 1px solid #888;
92
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
93
+ }
94
+
95
+ .key-white {
96
+ width: 40px;
97
+ height: 180px;
98
+ background: linear-gradient(to bottom, #fff 0%, #f0f0f0 100%);
99
+ border: 1px solid #ccc;
100
+ border-radius: 0 0 5px 5px;
101
+ box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);
102
+ position: relative;
103
+ z-index: 1;
104
+ }
105
+
106
+ .key-black {
107
+ width: 28px;
108
+ height: 120px;
109
+ background: linear-gradient(to bottom, #000 0%, #333 100%);
110
+ border: 1px solid #000;
111
+ border-radius: 0 0 4px 4px;
112
+ position: absolute;
113
+ z-index: 2;
114
+ margin-left: -14px;
115
+ }
116
+
117
+ .key-white.active {
118
+ background: linear-gradient(to bottom, #ffd700 0%, #ffb700 100%);
119
+ box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.3);
120
+ }
121
+
122
+ .key-black.active {
123
+ background: linear-gradient(to bottom, #ff8c00 0%, #ff6b00 100%);
124
+ box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5);
125
+ }
126
+
127
+ .led {
128
+ width: 12px;
129
+ height: 12px;
130
+ border-radius: 50%;
131
+ background: #444;
132
+ box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.5);
133
+ }
134
+
135
+ .led.active {
136
+ background: #ff6b6b;
137
+ box-shadow: 0 0 10px #ff6b6b;
138
+ animation: glow 1.5s infinite;
139
+ }
140
+
141
+ .vintage-label {
142
+ font-family: 'Audiowide', cursive;
143
+ color: #d1d1e9;
144
+ text-shadow: 0 0 5px rgba(255, 107, 107, 0.5);
145
+ letter-spacing: 1px;
146
+ }
147
+
148
+ .panel-label {
149
+ font-family: 'Audiowide', cursive;
150
+ color: #ff6b6b;
151
+ text-transform: uppercase;
152
+ letter-spacing: 1px;
153
+ font-size: 0.9rem;
154
+ }
155
+
156
+ .synth-container {
157
+ perspective: 1000px;
158
+ }
159
+
160
+ .synth-body {
161
+ transform: rotateX(5deg);
162
+ transform-style: preserve-3d;
163
+ }
164
+
165
+ .knob-container {
166
+ transform-style: preserve-3d;
167
+ }
168
+
169
+ .knob-shadow {
170
+ position: absolute;
171
+ bottom: -5px;
172
+ left: 5px;
173
+ width: 50px;
174
+ height: 10px;
175
+ background: rgba(0, 0, 0, 0.3);
176
+ border-radius: 50%;
177
+ filter: blur(5px);
178
+ z-index: -1;
179
+ }
180
+
181
+ .lcd-display {
182
+ background: linear-gradient(135deg, #0a3f0a 0%, #082908 100%);
183
+ border: 2px solid #0f5c0f;
184
+ color: #7fff7f;
185
+ font-family: 'Audiowide', cursive;
186
+ text-shadow: 0 0 5px #7fff7f;
187
+ box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5);
188
+ }
189
+
190
+ .lcd-text {
191
+ animation: flicker 0.1s infinite alternate;
192
+ }
193
+
194
+ @keyframes flicker {
195
+ 0% { opacity: 0.9; }
196
+ 100% { opacity: 1; }
197
+ }
198
+ </style>
199
+ </head>
200
+ <body class="bg-synth-dark min-h-screen flex flex-col items-center justify-center p-4 font-orbitron">
201
+ <div class="synth-container w-full max-w-4xl">
202
+ <div class="synth-body bg-synth-panel rounded-xl p-6 shadow-2xl">
203
+ <!-- Header -->
204
+ <div class="text-center mb-6">
205
+ <h1 class="vintage-label text-3xl md:text-4xl mb-2">ANALOG SYNTHESIZER</h1>
206
+ <div class="lcd-display rounded-lg p-3 mx-auto max-w-md">
207
+ <div class="lcd-text text-center text-lg">MODEL: V-1978</div>
208
+ </div>
209
+ </div>
210
+
211
+ <!-- Main Controls -->
212
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
213
+ <!-- Oscillator Section -->
214
+ <div class="bg-synth-dark rounded-lg p-4 wood-panel">
215
+ <div class="panel-label text-center mb-3">OSCILLATOR</div>
216
+ <div class="grid grid-cols-3 gap-4">
217
+ <div class="knob-container flex flex-col items-center">
218
+ <div class="knob relative" id="osc1-knob">
219
+ <div class="knob-shadow"></div>
220
+ </div>
221
+ <div class="knob-label">WAVE</div>
222
+ </div>
223
+ <div class="knob-container flex flex-col items-center">
224
+ <div class="knob relative" id="osc2-knob">
225
+ <div class="knob-shadow"></div>
226
+ </div>
227
+ <div class="knob-label">OCTAVE</div>
228
+ </div>
229
+ <div class="knob-container flex flex-col items-center">
230
+ <div class="knob relative" id="detune-knob">
231
+ <div class="knob-shadow"></div>
232
+ </div>
233
+ <div class="knob-label">DETUNE</div>
234
+ </div>
235
+ </div>
236
+ <div class="flex justify-around mt-4">
237
+ <div class="flex flex-col items-center">
238
+ <div class="led" id="sine-led"></div>
239
+ <span class="text-xs mt-1 text-synth-label">SINE</span>
240
+ </div>
241
+ <div class="flex flex-col items-center">
242
+ <div class="led active" id="saw-led"></div>
243
+ <span class="text-xs mt-1 text-synth-label">SAW</span>
244
+ </div>
245
+ <div class="flex flex-col items-center">
246
+ <div class="led" id="square-led"></div>
247
+ <span class="text-xs mt-1 text-synth-label">SQUARE</span>
248
+ </div>
249
+ <div class="flex flex-col items-center">
250
+ <div class="led" id="triangle-led"></div>
251
+ <span class="text-xs mt-1 text-synth-label">TRI</span>
252
+ </div>
253
+ </div>
254
+ </div>
255
+
256
+ <!-- Filter Section -->
257
+ <div class="bg-synth-dark rounded-lg p-4 wood-panel">
258
+ <div class="panel-label text-center mb-3">FILTER</div>
259
+ <div class="grid grid-cols-3 gap-4">
260
+ <div class="knob-container flex flex-col items-center">
261
+ <div class="knob relative" id="cutoff-knob">
262
+ <div class="knob-shadow"></div>
263
+ </div>
264
+ <div class="knob-label">CUTOFF</div>
265
+ </div>
266
+ <div class="knob-container flex flex-col items-center">
267
+ <div class="knob relative" id="resonance-knob">
268
+ <div class="knob-shadow"></div>
269
+ </div>
270
+ <div class="knob-label">RESONANCE</div>
271
+ </div>
272
+ <div class="knob-container flex flex-col items-center">
273
+ <div class="knob relative" id="env-knob">
274
+ <div class="knob-shadow"></div>
275
+ </div>
276
+ <div class="knob-label">ENV AMT</div>
277
+ </div>
278
+ </div>
279
+ <div class="flex justify-around mt-4">
280
+ <div class="flex flex-col items-center">
281
+ <div class="led active" id="lowpass-led"></div>
282
+ <span class="text-xs mt-1 text-synth-label">LOW</span>
283
+ </div>
284
+ <div class="flex flex-col items-center">
285
+ <div class="led" id="highpass-led"></div>
286
+ <span class="text-xs mt-1 text-synth-label">HIGH</span>
287
+ </div>
288
+ <div class="flex flex-col items-center">
289
+ <div class="led" id="bandpass-led"></div>
290
+ <span class="text-xs mt-1 text-synth-label">BAND</span>
291
+ </div>
292
+ </div>
293
+ </div>
294
+
295
+ <!-- Envelope Section -->
296
+ <div class="bg-synth-dark rounded-lg p-4 wood-panel">
297
+ <div class="panel-label text-center mb-3">ENVELOPE</div>
298
+ <div class="grid grid-cols-4 gap-3">
299
+ <div class="knob-container flex flex-col items-center">
300
+ <div class="knob relative" id="attack-knob">
301
+ <div class="knob-shadow"></div>
302
+ </div>
303
+ <div class="knob-label">ATTACK</div>
304
+ </div>
305
+ <div class="knob-container flex flex-col items-center">
306
+ <div class="knob relative" id="decay-knob">
307
+ <div class="knob-shadow"></div>
308
+ </div>
309
+ <div class="knob-label">DECAY</div>
310
+ </div>
311
+ <div class="knob-container flex flex-col items-center">
312
+ <div class="knob relative" id="sustain-knob">
313
+ <div class="knob-shadow"></div>
314
+ </div>
315
+ <div class="knob-label">SUSTAIN</div>
316
+ </div>
317
+ <div class="knob-container flex flex-col items-center">
318
+ <div class="knob relative" id="release-knob">
319
+ <div class="knob-shadow"></div>
320
+ </div>
321
+ <div class="knob-label">RELEASE</div>
322
+ </div>
323
+ </div>
324
+ <div class="mt-4 flex justify-center">
325
+ <div class="bg-synth-panel rounded p-2 w-full">
326
+ <div class="h-2 bg-gray-800 rounded-full overflow-hidden">
327
+ <div class="h-full bg-synth-highlight rounded-full w-3/4"></div>
328
+ </div>
329
+ </div>
330
+ </div>
331
+ </div>
332
+ </div>
333
+
334
+ <!-- Modulation and Effects -->
335
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
336
+ <div class="bg-synth-dark rounded-lg p-4 wood-panel">
337
+ <div class="panel-label text-center mb-3">MODULATION</div>
338
+ <div class="grid grid-cols-3 gap-4">
339
+ <div class="knob-container flex flex-col items-center">
340
+ <div class="knob relative" id="lfo-rate-knob">
341
+ <div class="knob-shadow"></div>
342
+ </div>
343
+ <div class="knob-label">RATE</div>
344
+ </div>
345
+ <div class="knob-container flex flex-col items-center">
346
+ <div class="knob relative" id="lfo-amount-knob">
347
+ <div class="knob-shadow"></div>
348
+ </div>
349
+ <div class="knob-label">AMOUNT</div>
350
+ </div>
351
+ <div class="knob-container flex flex-col items-center">
352
+ <div class="knob relative" id="lfo-target-knob">
353
+ <div class="knob-shadow"></div>
354
+ </div>
355
+ <div class="knob-label">TARGET</div>
356
+ </div>
357
+ </div>
358
+ </div>
359
+
360
+ <div class="bg-synth-dark rounded-lg p-4 wood-panel">
361
+ <div class="panel-label text-center mb-3">EFFECTS</div>
362
+ <div class="grid grid-cols-3 gap-4">
363
+ <div class="knob-container flex flex-col items-center">
364
+ <div class="knob relative" id="delay-knob">
365
+ <div class="knob-shadow"></div>
366
+ </div>
367
+ <div class="knob-label">DELAY</div>
368
+ </div>
369
+ <div class="knob-container flex flex-col items-center">
370
+ <div class="knob relative" id="reverb-knob">
371
+ <div class="knob-shadow"></div>
372
+ </div>
373
+ <div class="knob-label">REVERB</div>
374
+ </div>
375
+ <div class="knob-container flex flex-col items-center">
376
+ <div class="knob relative" id="distortion-knob">
377
+ <div class="knob-shadow"></div>
378
+ </div>
379
+ <div class="knob-label">DRIVE</div>
380
+ </div>
381
+ </div>
382
+ </div>
383
+ </div>
384
+
385
+ <!-- Keyboard -->
386
+ <div class="bg-synth-dark rounded-lg p-4 wood-panel mb-6">
387
+ <div class="panel-label text-center mb-3">KEYBOARD</div>
388
+ <div class="relative h-48 flex justify-center">
389
+ <!-- White Keys -->
390
+ <div class="flex relative">
391
+ <div class="key-white" data-note="C" data-frequency="261.63"></div>
392
+ <div class="key-white" data-note="D" data-frequency="293.66"></div>
393
+ <div class="key-white" data-note="E" data-frequency="329.63"></div>
394
+ <div class="key-white" data-note="F" data-frequency="349.23"></div>
395
+ <div class="key-white" data-note="G" data-frequency="392.00"></div>
396
+ <div class="key-white" data-note="A" data-frequency="440.00"></div>
397
+ <div class="key-white" data-note="B" data-frequency="493.88"></div>
398
+ <div class="key-white" data-note="C2" data-frequency="523.25"></div>
399
+
400
+ <!-- Black Keys -->
401
+ <div class="key-black" style="left: 30px;" data-note="C#" data-frequency="277.18"></div>
402
+ <div class="key-black" style="left: 70px;" data-note="D#" data-frequency="311.13"></div>
403
+ <div class="key-black" style="left: 150px;" data-note="F#" data-frequency="369.99"></div>
404
+ <div class="key-black" style="left: 190px;" data-note="G#" data-frequency="415.30"></div>
405
+ <div class="key-black" style="left: 230px;" data-note="A#" data-frequency="466.16"></div>
406
+ </div>
407
+ </div>
408
+ </div>
409
+
410
+ <!-- Footer -->
411
+ <div class="flex justify-between items-center">
412
+ <div class="flex items-center">
413
+ <div class="led active mr-2"></div>
414
+ <span class="text-sm text-synth-label">POWER ON</span>
415
+ </div>
416
+ <div class="text-sm text-synth-label">
417
+ <i class="fas fa-copyright mr-1"></i> 1978 ANALOG SYSTEMS
418
+ </div>
419
+ <div class="flex">
420
+ <button class="metal-frame px-3 py-1 rounded text-synth-label mr-2 hover:bg-synth-highlight hover:text-white transition">
421
+ PRESETS
422
+ </button>
423
+ <button class="metal-frame px-3 py-1 rounded text-synth-label hover:bg-synth-highlight hover:text-white transition">
424
+ RECORD
425
+ </button>
426
+ </div>
427
+ </div>
428
+ </div>
429
+ </div>
430
+
431
+ <script>
432
+ // Initialize Web Audio API
433
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
434
+ let oscillator = null;
435
+ let gainNode = null;
436
+ let filter = null;
437
+
438
+ // Initialize synth parameters
439
+ const synthParams = {
440
+ waveform: 'sawtooth',
441
+ octave: 0,
442
+ detune: 0,
443
+ cutoff: 1000,
444
+ resonance: 1,
445
+ attack: 0.1,
446
+ decay: 0.3,
447
+ sustain: 0.7,
448
+ release: 0.5
449
+ };
450
+
451
+ // Note frequencies
452
+ const noteFrequencies = {
453
+ 'C': 261.63,
454
+ 'C#': 277.18,
455
+ 'D': 293.66,
456
+ 'D#': 311.13,
457
+ 'E': 329.63,
458
+ 'F': 349.23,
459
+ 'F#': 369.99,
460
+ 'G': 392.00,
461
+ 'G#': 415.30,
462
+ 'A': 440.00,
463
+ 'A#': 466.16,
464
+ 'B': 493.88,
465
+ 'C2': 523.25
466
+ };
467
+
468
+ // Initialize knobs
469
+ function initKnobs() {
470
+ const knobs = document.querySelectorAll('.knob');
471
+
472
+ knobs.forEach(knob => {
473
+ let isDragging = false;
474
+ let startY = 0;
475
+ let startRotation = 0;
476
+ let currentRotation = 0;
477
+
478
+ knob.addEventListener('mousedown', (e) => {
479
+ isDragging = true;
480
+ startY = e.clientY;
481
+ startRotation = currentRotation;
482
+ knob.style.cursor = 'grabbing';
483
+ });
484
+
485
+ document.addEventListener('mousemove', (e) => {
486
+ if (isDragging) {
487
+ const deltaY = startY - e.clientY;
488
+ currentRotation = Math.min(135, Math.max(-135, startRotation + deltaY));
489
+ knob.style.transform = `rotate(${currentRotation}deg)`;
490
+
491
+ // Simulate sound when adjusting knobs
492
+ playKnobSound();
493
+ }
494
+ });
495
+
496
+ document.addEventListener('mouseup', () => {
497
+ if (isDragging) {
498
+ isDragging = false;
499
+ knob.style.cursor = 'pointer';
500
+ }
501
+ });
502
+
503
+ knob.addEventListener('touchstart', (e) => {
504
+ isDragging = true;
505
+ startY = e.touches[0].clientY;
506
+ startRotation = currentRotation;
507
+ });
508
+
509
+ document.addEventListener('touchmove', (e) => {
510
+ if (isDragging) {
511
+ const deltaY = startY - e.touches[0].clientY;
512
+ currentRotation = Math.min(135, Math.max(-135, startRotation + deltaY));
513
+ knob.style.transform = `rotate(${currentRotation}deg)`;
514
+ playKnobSound();
515
+ }
516
+ });
517
+
518
+ document.addEventListener('touchend', () => {
519
+ isDragging = false;
520
+ });
521
+ });
522
+ }
523
+
524
+ // Play a sound when turning knobs
525
+ function playKnobSound() {
526
+ if (oscillator) {
527
+ oscillator.stop();
528
+ }
529
+
530
+ oscillator = audioContext.createOscillator();
531
+ gainNode = audioContext.createGain();
532
+
533
+ oscillator.type = 'sine';
534
+ oscillator.frequency.value = 200;
535
+
536
+ gainNode.gain.value = 0.1;
537
+ gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.1);
538
+
539
+ oscillator.connect(gainNode);
540
+ gainNode.connect(audioContext.destination);
541
+
542
+ oscillator.start();
543
+ oscillator.stop(audioContext.currentTime + 0.1);
544
+ }
545
+
546
+ // Play a note
547
+ function playNote(noteElement) {
548
+ if (oscillator) {
549
+ oscillator.stop();
550
+ }
551
+
552
+ oscillator = audioContext.createOscillator();
553
+ gainNode = audioContext.createGain();
554
+ filter = audioContext.createBiquadFilter();
555
+
556
+ // Get frequency from data attribute
557
+ const frequency = parseFloat(noteElement.dataset.frequency) * Math.pow(2, synthParams.octave);
558
+
559
+ // Set oscillator parameters
560
+ oscillator.type = synthParams.waveform;
561
+ oscillator.frequency.value = frequency;
562
+ oscillator.detune.value = synthParams.detune;
563
+
564
+ // Set filter parameters
565
+ filter.type = 'lowpass';
566
+ filter.frequency.value = synthParams.cutoff;
567
+ filter.Q.value = synthParams.resonance;
568
+
569
+ // Set envelope parameters
570
+ const now = audioContext.currentTime;
571
+ gainNode.gain.setValueAtTime(0, now);
572
+ gainNode.gain.linearRampToValueAtTime(1, now + synthParams.attack);
573
+ gainNode.gain.linearRampToValueAtTime(synthParams.sustain, now + synthParams.attack + synthParams.decay);
574
+
575
+ // Connect nodes
576
+ oscillator.connect(filter);
577
+ filter.connect(gainNode);
578
+ gainNode.connect(audioContext.destination);
579
+
580
+ oscillator.start();
581
+ }
582
+
583
+ // Release note
584
+ function releaseNote() {
585
+ const now = audioContext.currentTime;
586
+ gainNode.gain.cancelScheduledValues(now);
587
+ gainNode.gain.setValueAtTime(gainNode.gain.value, now);
588
+ gainNode.gain.linearRampToValueAtTime(0, now + synthParams.release);
589
+
590
+ setTimeout(() => {
591
+ if (oscillator) {
592
+ oscillator.stop();
593
+ }
594
+ }, synthParams.release * 1000);
595
+ }
596
+
597
+ // Initialize keyboard
598
+ function initKeyboard() {
599
+ const keys = document.querySelectorAll('.key-white, .key-black');
600
+
601
+ keys.forEach(key => {
602
+ key.addEventListener('mousedown', () => {
603
+ key.classList.add('active');
604
+ playNote(key);
605
+ });
606
+
607
+ key.addEventListener('mouseup', () => {
608
+ key.classList.remove('active');
609
+ releaseNote();
610
+ });
611
+
612
+ key.addEventListener('mouseleave', () => {
613
+ if (key.classList.contains('active')) {
614
+ key.classList.remove('active');
615
+ releaseNote();
616
+ }
617
+ });
618
+
619
+ // Touch events for mobile
620
+ key.addEventListener('touchstart', (e) => {
621
+ e.preventDefault();
622
+ key.classList.add('active');
623
+ playNote(key);
624
+ });
625
+
626
+ key.addEventListener('touchend', () => {
627
+ key.classList.remove('active');
628
+ releaseNote();
629
+ });
630
+ });
631
+ }
632
+
633
+ // Initialize waveform LEDs
634
+ function initWaveformLEDs() {
635
+ const leds = document.querySelectorAll('#sine-led, #saw-led, #square-led, #triangle-led');
636
+
637
+ leds.forEach(led => {
638
+ led.addEventListener('click', () => {
639
+ // Remove active class from all
640
+ leds.forEach(l => l.classList.remove('active'));
641
+
642
+ // Add active to clicked
643
+ led.classList.add('active');
644
+
645
+ // Update waveform
646
+ if (led.id === 'sine-led') synthParams.waveform = 'sine';
647
+ if (led.id === 'saw-led') synthParams.waveform = 'sawtooth';
648
+ if (led.id === 'square-led') synthParams.waveform = 'square';
649
+ if (led.id === 'triangle-led') synthParams.waveform = 'triangle';
650
+
651
+ playKnobSound();
652
+ });
653
+ });
654
+ }
655
+
656
+ // Initialize when page loads
657
+ document.addEventListener('DOMContentLoaded', () => {
658
+ initKnobs();
659
+ initKeyboard();
660
+ initWaveformLEDs();
661
+
662
+ // Add slight rotation to knobs for visual effect
663
+ const knobs = document.querySelectorAll('.knob');
664
+ knobs.forEach((knob, i) => {
665
+ const rotation = -90 + (i * 20);
666
+ knob.style.transform = `rotate(${rotation}deg)`;
667
+ });
668
+ });
669
+ </script>
670
+ <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=Tingchenliang/analog-synth" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
671
+ </html>