sirsan69 commited on
Commit
b6c8506
·
verified ·
1 Parent(s): cfc1f66

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1101 -19
index.html CHANGED
@@ -1,19 +1,1101 @@
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>PitchLab - Advanced Audio Pitch Editor</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ :root {
15
+ --bg-primary: #1a1a1a;
16
+ --bg-secondary: #242424;
17
+ --bg-tertiary: #2d2d2d;
18
+ --text-primary: #ffffff;
19
+ --text-secondary: #b0b0b0;
20
+ --accent-blue: #4a9eff;
21
+ --accent-purple: #8b5cf6;
22
+ --accent-green: #10b981;
23
+ --accent-orange: #f59e0b;
24
+ --accent-red: #ef4444;
25
+ --border-color: #3a3a3a;
26
+ --hover-bg: #353535;
27
+ }
28
+
29
+ body {
30
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
31
+ background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
32
+ color: var(--text-primary);
33
+ height: 100vh;
34
+ overflow: hidden;
35
+ display: flex;
36
+ flex-direction: column;
37
+ }
38
+
39
+ /* Header */
40
+ .header {
41
+ background: var(--bg-secondary);
42
+ border-bottom: 1px solid var(--border-color);
43
+ padding: 12px 20px;
44
+ display: flex;
45
+ justify-content: space-between;
46
+ align-items: center;
47
+ flex-shrink: 0;
48
+ }
49
+
50
+ .logo {
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 12px;
54
+ font-size: 20px;
55
+ font-weight: 600;
56
+ }
57
+
58
+ .logo-icon {
59
+ width: 32px;
60
+ height: 32px;
61
+ background: linear-gradient(135deg, var(--accent-purple), var(--accent-blue));
62
+ border-radius: 8px;
63
+ display: flex;
64
+ align-items: center;
65
+ justify-content: center;
66
+ }
67
+
68
+ .logo-icon::before {
69
+ content: "♪";
70
+ font-size: 18px;
71
+ }
72
+
73
+ .header-controls {
74
+ display: flex;
75
+ gap: 12px;
76
+ align-items: center;
77
+ }
78
+
79
+ /* Toolbar */
80
+ .toolbar {
81
+ background: var(--bg-tertiary);
82
+ border-bottom: 1px solid var(--border-color);
83
+ padding: 8px 20px;
84
+ display: flex;
85
+ gap: 16px;
86
+ align-items: center;
87
+ flex-wrap: wrap;
88
+ }
89
+
90
+ .tool-group {
91
+ display: flex;
92
+ gap: 8px;
93
+ padding: 0 12px;
94
+ border-right: 1px solid var(--border-color);
95
+ }
96
+
97
+ .tool-group:last-child {
98
+ border-right: none;
99
+ }
100
+
101
+ .tool-btn {
102
+ background: var(--bg-secondary);
103
+ border: 1px solid var(--border-color);
104
+ color: var(--text-secondary);
105
+ padding: 8px 12px;
106
+ border-radius: 6px;
107
+ cursor: pointer;
108
+ transition: all 0.2s;
109
+ display: flex;
110
+ align-items: center;
111
+ gap: 6px;
112
+ font-size: 13px;
113
+ }
114
+
115
+ .tool-btn:hover {
116
+ background: var(--hover-bg);
117
+ color: var(--text-primary);
118
+ transform: translateY(-1px);
119
+ }
120
+
121
+ .tool-btn.active {
122
+ background: var(--accent-blue);
123
+ color: white;
124
+ border-color: var(--accent-blue);
125
+ }
126
+
127
+ /* Main Content */
128
+ .main-content {
129
+ flex: 1;
130
+ display: flex;
131
+ flex-direction: column;
132
+ overflow: hidden;
133
+ }
134
+
135
+ /* Timeline and Canvas Container */
136
+ .editor-container {
137
+ flex: 1;
138
+ position: relative;
139
+ background: var(--bg-primary);
140
+ overflow: hidden;
141
+ }
142
+
143
+ .timeline {
144
+ position: absolute;
145
+ top: 0;
146
+ left: 0;
147
+ right: 0;
148
+ height: 40px;
149
+ background: var(--bg-tertiary);
150
+ border-bottom: 1px solid var(--border-color);
151
+ display: flex;
152
+ align-items: center;
153
+ padding: 0 10px;
154
+ z-index: 10;
155
+ }
156
+
157
+ .time-ruler {
158
+ width: 100%;
159
+ height: 20px;
160
+ position: relative;
161
+ }
162
+
163
+ .time-mark {
164
+ position: absolute;
165
+ top: 0;
166
+ transform: translateX(-50%);
167
+ font-size: 11px;
168
+ color: var(--text-secondary);
169
+ }
170
+
171
+ .time-mark::before {
172
+ content: '';
173
+ position: absolute;
174
+ top: 16px;
175
+ left: 50%;
176
+ width: 1px;
177
+ height: 4px;
178
+ background: var(--text-secondary);
179
+ }
180
+
181
+ /* Pitch Editor Canvas */
182
+ #pitchCanvas {
183
+ position: absolute;
184
+ top: 40px;
185
+ left: 0;
186
+ cursor: crosshair;
187
+ }
188
+
189
+ /* Playback Controls */
190
+ .playback-controls {
191
+ background: var(--bg-secondary);
192
+ border-top: 1px solid var(--border-color);
193
+ padding: 16px 20px;
194
+ display: flex;
195
+ align-items: center;
196
+ gap: 20px;
197
+ }
198
+
199
+ .transport {
200
+ display: flex;
201
+ gap: 8px;
202
+ }
203
+
204
+ .transport-btn {
205
+ width: 40px;
206
+ height: 40px;
207
+ border-radius: 50%;
208
+ background: var(--bg-tertiary);
209
+ border: 1px solid var(--border-color);
210
+ color: var(--text-primary);
211
+ cursor: pointer;
212
+ display: flex;
213
+ align-items: center;
214
+ justify-content: center;
215
+ transition: all 0.2s;
216
+ }
217
+
218
+ .transport-btn:hover {
219
+ background: var(--hover-bg);
220
+ transform: scale(1.05);
221
+ }
222
+
223
+ .transport-btn.play {
224
+ width: 48px;
225
+ height: 48px;
226
+ background: var(--accent-green);
227
+ border-color: var(--accent-green);
228
+ }
229
+
230
+ .transport-btn.play:hover {
231
+ background: #059669;
232
+ }
233
+
234
+ /* Time Display */
235
+ .time-display {
236
+ display: flex;
237
+ align-items: center;
238
+ gap: 8px;
239
+ padding: 8px 16px;
240
+ background: var(--bg-tertiary);
241
+ border-radius: 8px;
242
+ font-size: 14px;
243
+ font-variant-numeric: tabular-nums;
244
+ }
245
+
246
+ .time-separator {
247
+ color: var(--text-secondary);
248
+ }
249
+
250
+ /* Pitch Display */
251
+ .pitch-display {
252
+ display: flex;
253
+ align-items: center;
254
+ gap: 12px;
255
+ margin-left: auto;
256
+ }
257
+
258
+ .pitch-info {
259
+ padding: 8px 16px;
260
+ background: var(--bg-tertiary);
261
+ border-radius: 8px;
262
+ font-size: 14px;
263
+ }
264
+
265
+ .pitch-label {
266
+ color: var(--text-secondary);
267
+ margin-right: 8px;
268
+ }
269
+
270
+ .pitch-value {
271
+ color: var(--accent-blue);
272
+ font-weight: 600;
273
+ }
274
+
275
+ /* File Upload */
276
+ .file-input-wrapper {
277
+ position: relative;
278
+ }
279
+
280
+ .file-input {
281
+ display: none;
282
+ }
283
+
284
+ /* Side Panel */
285
+ .side-panel {
286
+ position: absolute;
287
+ right: 0;
288
+ top: 40px;
289
+ width: 280px;
290
+ height: calc(100% - 40px);
291
+ background: var(--bg-secondary);
292
+ border-left: 1px solid var(--border-color);
293
+ padding: 20px;
294
+ transform: translateX(100%);
295
+ transition: transform 0.3s;
296
+ z-index: 20;
297
+ overflow-y: auto;
298
+ }
299
+
300
+ .side-panel.open {
301
+ transform: translateX(0);
302
+ }
303
+
304
+ .panel-section {
305
+ margin-bottom: 24px;
306
+ }
307
+
308
+ .panel-title {
309
+ font-size: 14px;
310
+ font-weight: 600;
311
+ margin-bottom: 12px;
312
+ color: var(--text-primary);
313
+ }
314
+
315
+ .slider-control {
316
+ margin-bottom: 16px;
317
+ }
318
+
319
+ .slider-label {
320
+ display: flex;
321
+ justify-content: space-between;
322
+ margin-bottom: 8px;
323
+ font-size: 13px;
324
+ color: var(--text-secondary);
325
+ }
326
+
327
+ .slider {
328
+ width: 100%;
329
+ height: 4px;
330
+ background: var(--bg-tertiary);
331
+ border-radius: 2px;
332
+ outline: none;
333
+ -webkit-appearance: none;
334
+ }
335
+
336
+ .slider::-webkit-slider-thumb {
337
+ -webkit-appearance: none;
338
+ appearance: none;
339
+ width: 16px;
340
+ height: 16px;
341
+ background: var(--accent-blue);
342
+ border-radius: 50%;
343
+ cursor: pointer;
344
+ }
345
+
346
+ .slider::-moz-range-thumb {
347
+ width: 16px;
348
+ height: 16px;
349
+ background: var(--accent-blue);
350
+ border-radius: 50%;
351
+ cursor: pointer;
352
+ border: none;
353
+ }
354
+
355
+ /* Loading Overlay */
356
+ .loading-overlay {
357
+ position: fixed;
358
+ top: 0;
359
+ left: 0;
360
+ right: 0;
361
+ bottom: 0;
362
+ background: rgba(0, 0, 0, 0.8);
363
+ display: none;
364
+ align-items: center;
365
+ justify-content: center;
366
+ z-index: 1000;
367
+ }
368
+
369
+ .loading-overlay.active {
370
+ display: flex;
371
+ }
372
+
373
+ .loading-spinner {
374
+ width: 60px;
375
+ height: 60px;
376
+ border: 3px solid var(--border-color);
377
+ border-top-color: var(--accent-blue);
378
+ border-radius: 50%;
379
+ animation: spin 1s linear infinite;
380
+ }
381
+
382
+ @keyframes spin {
383
+ to { transform: rotate(360deg); }
384
+ }
385
+
386
+ /* Tooltip */
387
+ .tooltip {
388
+ position: absolute;
389
+ background: var(--bg-tertiary);
390
+ border: 1px solid var(--border-color);
391
+ padding: 8px 12px;
392
+ border-radius: 6px;
393
+ font-size: 12px;
394
+ pointer-events: none;
395
+ z-index: 100;
396
+ opacity: 0;
397
+ transition: opacity 0.2s;
398
+ }
399
+
400
+ .tooltip.visible {
401
+ opacity: 1;
402
+ }
403
+
404
+ /* Responsive */
405
+ @media (max-width: 768px) {
406
+ .toolbar {
407
+ padding: 8px 12px;
408
+ }
409
+
410
+ .tool-group {
411
+ padding: 0 8px;
412
+ }
413
+
414
+ .tool-btn {
415
+ padding: 6px 8px;
416
+ font-size: 12px;
417
+ }
418
+
419
+ .side-panel {
420
+ width: 100%;
421
+ }
422
+
423
+ .header-controls {
424
+ gap: 8px;
425
+ }
426
+ }
427
+
428
+ /* Custom Scrollbar */
429
+ ::-webkit-scrollbar {
430
+ width: 8px;
431
+ height: 8px;
432
+ }
433
+
434
+ ::-webkit-scrollbar-track {
435
+ background: var(--bg-primary);
436
+ }
437
+
438
+ ::-webkit-scrollbar-thumb {
439
+ background: var(--bg-tertiary);
440
+ border-radius: 4px;
441
+ }
442
+
443
+ ::-webkit-scrollbar-thumb:hover {
444
+ background: var(--hover-bg);
445
+ }
446
+ </style>
447
+ </head>
448
+ <body>
449
+ <!-- Header -->
450
+ <header class="header">
451
+ <div class="logo">
452
+ <div class="logo-icon"></div>
453
+ <span>PitchLab</span>
454
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="color: var(--text-secondary); font-size: 12px; margin-left: 8px;">Built with anycoder</a>
455
+ </div>
456
+ <div class="header-controls">
457
+ <button class="tool-btn" onclick="toggleSidePanel()">
458
+ <span>⚙️</span> Settings
459
+ </button>
460
+ <button class="tool-btn" onclick="exportAudio()">
461
+ <span>💾</span> Export
462
+ </button>
463
+ </div>
464
+ </header>
465
+
466
+ <!-- Toolbar -->
467
+ <div class="toolbar">
468
+ <div class="tool-group">
469
+ <button class="tool-btn active" data-tool="select" onclick="selectTool('select')">
470
+ <span>↖️</span> Select
471
+ </button>
472
+ <button class="tool-btn" data-tool="pitch" onclick="selectTool('pitch')">
473
+ <span>🎵</span> Pitch
474
+ </button>
475
+ <button class="tool-btn" data-tool="time" onclick="selectTool('time')">
476
+ <span>⏱️</span> Time
477
+ </button>
478
+ </div>
479
+ <div class="tool-group">
480
+ <button class="tool-btn" onclick="resetView()">
481
+ <span>🔄</span> Reset View
482
+ </button>
483
+ <button class="tool-btn" onclick="zoomIn()">
484
+ <span>🔍+</span> Zoom In
485
+ </button>
486
+ <button class="tool-btn" onclick="zoomOut()">
487
+ <span>🔍-</span> Zoom Out
488
+ </button>
489
+ </div>
490
+ <div class="tool-group">
491
+ <div class="file-input-wrapper">
492
+ <button class="tool-btn" onclick="document.getElementById('audioFile').click()">
493
+ <span>📁</span> Load Audio
494
+ </button>
495
+ <input type="file" id="audioFile" class="file-input" accept="audio/*" onchange="loadAudioFile(event)">
496
+ </div>
497
+ <button class="tool-btn" onclick="clearAll()">
498
+ <span>🗑️</span> Clear
499
+ </button>
500
+ </div>
501
+ </div>
502
+
503
+ <!-- Main Editor -->
504
+ <div class="main-content">
505
+ <div class="editor-container">
506
+ <!-- Timeline -->
507
+ <div class="timeline">
508
+ <div class="time-ruler" id="timeRuler"></div>
509
+ </div>
510
+
511
+ <!-- Pitch Canvas -->
512
+ <canvas id="pitchCanvas"></canvas>
513
+
514
+ <!-- Side Panel -->
515
+ <div class="side-panel" id="sidePanel">
516
+ <div class="panel-section">
517
+ <h3 class="panel-title">Pitch Correction</h3>
518
+ <div class="slider-control">
519
+ <div class="slider-label">
520
+ <span>Snap Strength</span>
521
+ <span id="snapValue">50%</span>
522
+ </div>
523
+ <input type="range" class="slider" min="0" max="100" value="50" oninput="updateSlider('snap', this.value)">
524
+ </div>
525
+ <div class="slider-control">
526
+ <div class="slider-label">
527
+ <span>Pitch Range</span>
528
+ <span id="rangeValue">±12</span>
529
+ </div>
530
+ <input type="range" class="slider" min="1" max="24" value="12" oninput="updateSlider('range', this.value)">
531
+ </div>
532
+ </div>
533
+
534
+ <div class="panel-section">
535
+ <h3 class="panel-title">Note Detection</h3>
536
+ <div class="slider-control">
537
+ <div class="slider-label">
538
+ <span>Sensitivity</span>
539
+ <span id="sensValue">75%</span>
540
+ </div>
541
+ <input type="range" class="slider" min="0" max="100" value="75" oninput="updateSlider('sens', this.value)">
542
+ </div>
543
+ <div class="slider-control">
544
+ <div class="slider-label">
545
+ <span>Min Note Length</span>
546
+ <span id="minLengthValue">100ms</span>
547
+ </div>
548
+ <input type="range" class="slider" min="50" max="500" value="100" step="50" oninput="updateSlider('minLength', this.value)">
549
+ </div>
550
+ </div>
551
+
552
+ <div class="panel-section">
553
+ <h3 class="panel-title">Display</h3>
554
+ <div class="slider-control">
555
+ <div class="slider-label">
556
+ <span>Vertical Zoom</span>
557
+ <span id="vZoomValue">100%</span>
558
+ </div>
559
+ <input type="range" class="slider" min="50" max="200" value="100" oninput="updateSlider('vZoom', this.value)">
560
+ </div>
561
+ </div>
562
+ </div>
563
+ </div>
564
+ </div>
565
+
566
+ <!-- Playback Controls -->
567
+ <div class="playback-controls">
568
+ <div class="transport">
569
+ <button class="transport-btn" onclick="skipBackward()">⏮️</button>
570
+ <button class="transport-btn" onclick="stepBackward()">⏪</button>
571
+ <button class="transport-btn play" onclick="togglePlayback()">▶️</button>
572
+ <button class="transport-btn" onclick="stepForward()">⏩</button>
573
+ <button class="transport-btn" onclick="skipForward()">⏭️</button>
574
+ </div>
575
+
576
+ <div class="time-display">
577
+ <span id="currentTime">00:00.000</span>
578
+ <span class="time-separator">/</span>
579
+ <span id="totalTime">00:00.000</span>
580
+ </div>
581
+
582
+ <div class="pitch-display">
583
+ <div class="pitch-info">
584
+ <span class="pitch-label">Current Note:</span>
585
+ <span class="pitch-value" id="currentNote">--</span>
586
+ </div>
587
+ <div class="pitch-info">
588
+ <span class="pitch-label">Frequency:</span>
589
+ <span class="pitch-value" id="currentFreq">-- Hz</span>
590
+ </div>
591
+ </div>
592
+ </div>
593
+
594
+ <!-- Loading Overlay -->
595
+ <div class="loading-overlay" id="loadingOverlay">
596
+ <div class="loading-spinner"></div>
597
+ </div>
598
+
599
+ <!-- Tooltip -->
600
+ <div class="tooltip" id="tooltip"></div>
601
+
602
+ <script>
603
+ // Global variables
604
+ let canvas, ctx;
605
+ let audioContext;
606
+ let audioBuffer;
607
+ let source;
608
+ let isPlaying = false;
609
+ let startTime = 0;
610
+ let pauseTime = 0;
611
+ let currentTool = 'select';
612
+ let zoomLevel = 1;
613
+ let verticalZoom = 1;
614
+ let notes = [];
615
+ let selectedNote = null;
616
+ let isDragging = false;
617
+ let dragStartX = 0;
618
+ let dragStartY = 0;
619
+ let viewOffset = 0;
620
+ let playbackPosition = 0;
621
+
622
+ // Note frequencies for C4 to B5
623
+ const noteFrequencies = {
624
+ 'C4': 261.63, 'C#4': 277.18, 'D4': 293.66, 'D#4': 311.13,
625
+ 'E4': 329.63, 'F4': 349.23, 'F#4': 369.99, 'G4': 392.00,
626
+ 'G#4': 415.30, 'A4': 440.00, 'A#4': 466.16, 'B4': 493.88,
627
+ 'C5': 523.25, 'C#5': 554.37, 'D5': 587.33, 'D#5': 622.25,
628
+ 'E5': 659.25, 'F5': 698.46, 'F#5': 739.99, 'G5': 783.99,
629
+ 'G#5': 830.61, 'A5': 880.00, 'A#5': 932.33, 'B5': 987.77
630
+ };
631
+
632
+ // Initialize
633
+ document.addEventListener('DOMContentLoaded', function() {
634
+ initCanvas();
635
+ initAudioContext();
636
+ generateDemoNotes();
637
+ drawInterface();
638
+ updateTimeRuler();
639
+ startAnimationLoop();
640
+ });
641
+
642
+ function initCanvas() {
643
+ canvas = document.getElementById('pitchCanvas');
644
+ ctx = canvas.getContext('2d');
645
+ resizeCanvas();
646
+ window.addEventListener('resize', resizeCanvas);
647
+
648
+ canvas.addEventListener('mousedown', handleMouseDown);
649
+ canvas.addEventListener('mousemove', handleMouseMove);
650
+ canvas.addEventListener('mouseup', handleMouseUp);
651
+ canvas.addEventListener('wheel', handleWheel);
652
+ }
653
+
654
+ function resizeCanvas() {
655
+ const container = document.querySelector('.editor-container');
656
+ canvas.width = container.clientWidth;
657
+ canvas.height = container.clientHeight - 40;
658
+ drawInterface();
659
+ }
660
+
661
+ function initAudioContext() {
662
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
663
+ }
664
+
665
+ function generateDemoNotes() {
666
+ // Generate some demo notes for visualization
667
+ const demoNotes = [
668
+ { note: 'C4', start: 0.5, duration: 0.5, pitch: 261.63 },
669
+ { note: 'E4', start: 1.0, duration: 0.5, pitch: 329.63 },
670
+ { note: 'G4', start: 1.5, duration: 0.5, pitch: 392.00 },
671
+ { note: 'C5', start: 2.0, duration: 0.5, pitch: 523.25 },
672
+ { note: 'G4', start: 2.5, duration: 0.5, pitch: 392.00 },
673
+ { note: 'E4', start: 3.0, duration: 0.5, pitch: 329.63 },
674
+ { note: 'C4', start: 3.5, duration: 1.0, pitch: 261.63 },
675
+ { note: 'D4', start: 4.5, duration: 0.5, pitch: 293.66 },
676
+ { note: 'F4', start: 5.0, duration: 0.5, pitch: 349.23 },
677
+ { note: 'A4', start: 5.5, duration: 0.5, pitch: 440.00 },
678
+ { note: 'D5', start: 6.0, duration: 0.5, pitch: 587.33 },
679
+ { note: 'A4', start: 6.5, duration: 0.5, pitch: 440.00 },
680
+ { note: 'F4', start: 7.0, duration: 0.5, pitch: 349.23 },
681
+ { note: 'D4', start: 7.5, duration: 1.0, pitch: 293.66 },
682
+ ];
683
+
684
+ notes = demoNotes.map(n => ({
685
+ ...n,
686
+ id: Math.random().toString(36).substr(2, 9),
687
+ color: getNoteColor(n.note)
688
+ }));
689
+ }
690
+
691
+ function getNoteColor(note) {
692
+ const colors = {
693
+ 'C': '#ef4444', 'C#': '#f97316', 'D': '#f59e0b', 'D#': '#eab308',
694
+ 'E': '#84cc16', 'F': '#22c55e', 'F#': '#10b981', 'G': '#14b8a6',
695
+ 'G#': '#06b6d4', 'A': '#0ea5e9', 'A#': '#3b82f6', 'B': '#6366f1'
696
+ };
697
+ return colors[note.replace(/[0-9]/, '')] || '#8b5cf6';
698
+ }
699
+
700
+ function drawInterface() {
701
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
702
+
703
+ // Draw background gradient
704
+ const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
705
+ gradient.addColorStop(0, '#1a1a1a');
706
+ gradient.addColorStop(1, '#242424');
707
+ ctx.fillStyle = gradient;
708
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
709
+
710
+ // Draw grid
711
+ drawGrid();
712
+
713
+ // Draw notes
714
+ drawNotes();
715
+
716
+ // Draw playback line
717
+ drawPlaybackLine();
718
+ }
719
+
720
+ function drawGrid() {
721
+ ctx.strokeStyle = '#3a3a3a';
722
+ ctx.lineWidth = 1;
723
+
724
+ // Vertical lines (time)
725
+ const pixelsPerSecond = 100 * zoomLevel;
726
+ const seconds = Math.ceil(canvas.width / pixelsPerSecond);
727
+
728
+ for (let i = 0; i <= seconds; i++) {
729
+ const x = i * pixelsPerSecond - viewOffset;
730
+ if (x >= 0 && x <= canvas.width) {
731
+ ctx.beginPath();
732
+ ctx.moveTo(x, 0);
733
+ ctx.lineTo(x, canvas.height);
734
+ ctx.stroke();
735
+ }
736
+ }
737
+
738
+ // Horizontal lines (pitch)
739
+ const noteHeight = 30 * verticalZoom;
740
+ const notesCount = Math.ceil(canvas.height / noteHeight);
741
+
742
+ for (let i = 0; i <= notesCount; i++) {
743
+ const y = i * noteHeight;
744
+ ctx.beginPath();
745
+ ctx.moveTo(0, y);
746
+ ctx.lineTo(canvas.width, y);
747
+ ctx.stroke();
748
+ }
749
+ }
750
+
751
+ function drawNotes() {
752
+ const pixelsPerSecond = 100 * zoomLevel;
753
+ const noteHeight = 30 * verticalZoom;
754
+
755
+ notes.forEach(note => {
756
+ const x = (note.start * pixelsPerSecond) - viewOffset;
757
+ const width = note.duration * pixelsPerSecond;
758
+ const y = canvas.height - (getNotePosition(note.note) * noteHeight) - noteHeight;
759
+
760
+ // Draw note rectangle
761
+ ctx.fillStyle = note.color + '88';
762
+ ctx.fillRect(x, y, width, noteHeight - 2);
763
+
764
+ // Draw note border
765
+ ctx.strokeStyle = note.color;
766
+ ctx.lineWidth = 2;
767
+ ctx.strokeRect(x, y, width, noteHeight - 2);
768
+
769
+ // Draw note label
770
+ ctx.fillStyle = '#ffffff';
771
+ ctx.font = '12px sans-serif';
772
+ ctx.fillText(note.note, x + 5, y + noteHeight / 2 + 4);
773
+
774
+ // Highlight selected note
775
+ if (selectedNote && selectedNote.id === note.id) {
776
+ ctx.strokeStyle = '#ffffff';
777
+ ctx.lineWidth = 3;
778
+ ctx.strokeRect(x - 2, y - 2, width + 4, noteHeight + 2);
779
+ }
780
+ });
781
+ }
782
+
783
+ function getNotePosition(noteName) {
784
+ const noteOrder = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
785
+ const note = noteName.replace(/[0-9]/, '');
786
+ const octave = parseInt(noteName.replace(/[A-G#]/, ''));
787
+ return (octave - 3) * 12 + noteOrder.indexOf(note);
788
+ }
789
+
790
+ function drawPlaybackLine() {
791
+ if (isPlaying || playbackPosition > 0) {
792
+ const pixelsPerSecond = 100 * zoomLevel;
793
+ const x = (playbackPosition * pixelsPerSecond) - viewOffset;
794
+
795
+ if (x >= 0 && x <= canvas.width) {
796
+ ctx.strokeStyle = '#10b981';
797
+ ctx.lineWidth = 2;
798
+ ctx.beginPath();
799
+ ctx.moveTo(x, 0);
800
+ ctx.lineTo(x, canvas.height);
801
+ ctx.stroke();
802
+ }
803
+ }
804
+ }
805
+
806
+ function handleMouseDown(e) {
807
+ const rect = canvas.getBoundingClientRect();
808
+ const x = e.clientX - rect.left;
809
+ const y = e.clientY - rect.top;
810
+
811
+ const pixelsPerSecond = 100 * zoomLevel;
812
+ const time = (x + viewOffset) / pixelsPerSecond;
813
+
814
+ // Check if clicking on a note
815
+ selectedNote = null;
816
+ notes.forEach(note => {
817
+ const noteX = (note.start * pixelsPerSecond) - viewOffset;
818
+ const noteY = canvas.height - (getNotePosition(note.note) * 30 * verticalZoom) - (30 * verticalZoom);
819
+ const noteWidth = note.duration * pixelsPerSecond;
820
+ const noteHeight = 30 * verticalZoom;
821
+
822
+ if (x >= noteX && x <= noteX + noteWidth && y >= noteY && y <= noteY + noteHeight) {
823
+ selectedNote = note;
824
+ isDragging = true;
825
+ dragStartX = x - noteX;
826
+ dragStartY = y - noteY;
827
+ }
828
+ });
829
+
830
+ drawInterface();
831
+ }
832
+
833
+ function handleMouseMove(e) {
834
+ const rect = canvas.getBoundingClientRect();
835
+ const x = e.clientX - rect.left;
836
+ const y = e.clientY - rect.top;
837
+
838
+ if (isDragging && selectedNote) {
839
+ const pixelsPerSecond = 100 * zoomLevel;
840
+ const newStart = (x + viewOffset - dragStartX) / pixelsPerSecond;
841
+ selectedNote.start = Math.max(0, newStart);
842
+ drawInterface();
843
+ }
844
+
845
+ // Update cursor
846
+ canvas.style.cursor = isDragging ? 'grabbing' : 'crosshair';
847
+ }
848
+
849
+ function handleMouseUp(e) {
850
+ isDragging = false;
851
+ }
852
+
853
+ function handleWheel(e) {
854
+ e.preventDefault();
855
+ if (e.deltaY < 0) {
856
+ zoomIn();
857
+ } else {
858
+ zoomOut();
859
+ }
860
+ }
861
+
862
+ function selectTool(tool) {
863
+ currentTool = tool;
864
+ document.querySelectorAll('.tool-btn[data-tool]').forEach(btn => {
865
+ btn.classList.remove('active');
866
+ });
867
+ document.querySelector(`[data-tool="${tool}"]`).classList.add('active');
868
+ }
869
+
870
+ function toggleSidePanel() {
871
+ document.getElementById('sidePanel').classList.toggle('open');
872
+ }
873
+
874
+ function updateSlider(type, value) {
875
+ switch(type) {
876
+ case 'snap':
877
+ document.getElementById('snapValue').textContent = value + '%';
878
+ break;
879
+ case 'range':
880
+ document.getElementById('rangeValue').textContent = '±' + value;
881
+ break;
882
+ case 'sens':
883
+ document.getElementById('sensValue').textContent = value + '%';
884
+ break;
885
+ case 'minLength':
886
+ document.getElementById('minLengthValue').textContent = value + 'ms';
887
+ break;
888
+ case 'vZoom':
889
+ verticalZoom = value / 100;
890
+ document.getElementById('vZoomValue').textContent = value + '%';
891
+ drawInterface();
892
+ break;
893
+ }
894
+ }
895
+
896
+ function zoomIn() {
897
+ zoomLevel = Math.min(zoomLevel * 1.2, 5);
898
+ drawInterface();
899
+ updateTimeRuler();
900
+ }
901
+
902
+ function zoomOut() {
903
+ zoomLevel = Math.max(zoomLevel / 1.2, 0.5);
904
+ drawInterface();
905
+ updateTimeRuler();
906
+ }
907
+
908
+ function resetView() {
909
+ zoomLevel = 1;
910
+ viewOffset = 0;
911
+ drawInterface();
912
+ updateTimeRuler();
913
+ }
914
+
915
+ function updateTimeRuler() {
916
+ const ruler = document.getElementById('timeRuler');
917
+ ruler.innerHTML = '';
918
+
919
+ const pixelsPerSecond = 100 * zoomLevel;
920
+ const seconds = Math.ceil(canvas.width / pixelsPerSecond);
921
+
922
+ for (let i = 0; i <= seconds; i++) {
923
+ const mark = document.createElement('div');
924
+ mark.className = 'time-mark';
925
+ mark.style.left = (i * pixelsPerSecond) + 'px';
926
+ mark.textContent = formatTime(i);
927
+ ruler.appendChild(mark);
928
+ }
929
+ }
930
+
931
+ function formatTime(seconds) {
932
+ const mins = Math.floor(seconds / 60);
933
+ const secs = Math.floor(seconds % 60);
934
+ return `${mins}:${secs.toString().padStart(2, '0')}`;
935
+ }
936
+
937
+ function loadAudioFile(event) {
938
+ const file = event.target.files[0];
939
+ if (!file) return;
940
+
941
+ showLoading(true);
942
+
943
+ const reader = new FileReader();
944
+ reader.onload = function(e) {
945
+ audioContext.decodeAudioData(e.target.result, function(buffer) {
946
+ audioBuffer = buffer;
947
+ document.getElementById('totalTime').textContent = formatAudioTime(buffer.duration);
948
+ showLoading(false);
949
+
950
+ // Simulate note detection
951
+ detectNotes();
952
+ });
953
+ };
954
+ reader.readAsArrayBuffer(file);
955
+ }
956
+
957
+ function detectNotes() {
958
+ // Simulate note detection with random notes
959
+ const detectedNotes = [];
960
+ const noteNames = Object.keys(noteFrequencies);
961
+
962
+ for (let i = 0; i < 20; i++) {
963
+ const noteName = noteNames[Math.floor(Math.random() * noteNames.length)];
964
+ detectedNotes.push({
965
+ id: Math.random().toString(36).substr(2, 9),
966
+ note: noteName,
967
+ start: i * 0.5,
968
+ duration: 0.3 + Math.random() * 0.4,
969
+ pitch: noteFrequencies[noteName],
970
+ color: getNoteColor(noteName)
971
+ });
972
+ }
973
+
974
+ notes = detectedNotes;
975
+ drawInterface();
976
+ }
977
+
978
+ function togglePlayback() {
979
+ if (!audioBuffer) {
980
+ // Simulate playback with demo
981
+ simulatePlayback();
982
+ return;
983
+ }
984
+
985
+ if (isPlaying) {
986
+ pauseAudio();
987
+ } else {
988
+ playAudio();
989
+ }
990
+ }
991
+
992
+ function simulatePlayback() {
993
+ isPlaying = !isPlaying;
994
+ const playBtn = document.querySelector('.transport-btn.play');
995
+ playBtn.innerHTML = isPlaying ? '⏸️' : '▶️';
996
+
997
+ if (isPlaying) {
998
+ startTime = Date.now() - (playbackPosition * 1000);
999
+ updatePlayback();
1000
+ }
1001
+ }
1002
+
1003
+ function updatePlayback() {
1004
+ if (!isPlaying) return;
1005
+
1006
+ const elapsed = (Date.now() - startTime) / 1000;
1007
+ playbackPosition = elapsed % 10; // Loop every 10 seconds
1008
+
1009
+ document.getElementById('currentTime').textContent = formatAudioTime(playbackPosition);
1010
+
1011
+ // Update current note display
1012
+ const currentNoteObj = notes.find(n =>
1013
+ playbackPosition >= n.start && playbackPosition <= n.start + n.duration
1014
+ );
1015
+
1016
+ if (currentNoteObj) {
1017
+ document.getElementById('currentNote').textContent = currentNoteObj.note;
1018
+ document.getElementById('currentFreq').textContent = Math.round(currentNoteObj.pitch) + ' Hz';
1019
+ } else {
1020
+ document.getElementById('currentNote').textContent = '--';
1021
+ document.getElementById('currentFreq').textContent = '-- Hz';
1022
+ }
1023
+
1024
+ drawInterface();
1025
+ requestAnimationFrame(updatePlayback);
1026
+ }
1027
+
1028
+ function playAudio() {
1029
+ if (!audioBuffer) return;
1030
+
1031
+ source = audioContext.createBufferSource();
1032
+ source.buffer = audioBuffer;
1033
+ source.connect(audioContext.destination);
1034
+
1035
+ source.start(0, pauseTime);
1036
+ startTime = audioContext.currentTime - pauseTime;
1037
+ isPlaying = true;
1038
+
1039
+ const playBtn = document.querySelector('.transport-btn.play');
1040
+ playBtn.innerHTML = '⏸️';
1041
+
1042
+ source.onended = function() {
1043
+ if (isPlaying) {
1044
+ stopAudio();
1045
+ }
1046
+ };
1047
+
1048
+ updatePlaybackTime();
1049
+ }
1050
+
1051
+ function pauseAudio() {
1052
+ if (source) {
1053
+ source.stop();
1054
+ source = null;
1055
+ }
1056
+ pauseTime = audioContext.currentTime - startTime;
1057
+ isPlaying = false;
1058
+
1059
+ const playBtn = document.querySelector('.transport-btn.play');
1060
+ playBtn.innerHTML = '▶️';
1061
+ }
1062
+
1063
+ function stopAudio() {
1064
+ if (source) {
1065
+ source.stop();
1066
+ source = null;
1067
+ }
1068
+ isPlaying = false;
1069
+ pauseTime = 0;
1070
+ playbackPosition = 0;
1071
+
1072
+ const playBtn = document.querySelector('.transport-btn.play');
1073
+ playBtn.innerHTML = '▶️';
1074
+
1075
+ document.getElementById('currentTime').textContent = '00:00.000';
1076
+ drawInterface();
1077
+ }
1078
+
1079
+ function updatePlaybackTime() {
1080
+ if (!isPlaying) return;
1081
+
1082
+ const currentTime = audioContext.currentTime - startTime;
1083
+ document.getElementById('currentTime').textContent = formatAudioTime(currentTime);
1084
+ playbackPosition = currentTime;
1085
+
1086
+ if (currentTime < audioBuffer.duration) {
1087
+ requestAnimationFrame(updatePlaybackTime);
1088
+ } else {
1089
+ stopAudio();
1090
+ }
1091
+ }
1092
+
1093
+ function formatAudioTime(seconds) {
1094
+ const mins = Math.floor(seconds / 60);
1095
+ const secs = Math.floor(seconds % 60);
1096
+ const ms = Math.floor((seconds % 1) * 1000);
1097
+ return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}`;
1098
+ }
1099
+
1100
+ function skipBackward() {
1101
+ playbackPosition = Math.max(0, playbackPosition - 5