C50BARZ commited on
Commit
4d108ab
·
verified ·
1 Parent(s): 530bdb3

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +732 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Turntable Dj Scratch App
3
- emoji: 📚
4
- colorFrom: yellow
5
- colorTo: green
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: turntable-dj-scratch-app
3
+ emoji: 🐳
4
+ colorFrom: purple
5
+ colorTo: red
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,732 @@
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>Turntable DJ Scratch App</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
+ @keyframes spin {
11
+ 0% { transform: rotate(0deg); }
12
+ 100% { transform: rotate(360deg); }
13
+ }
14
+
15
+ .record-spin {
16
+ animation: spin 2s linear infinite;
17
+ }
18
+
19
+ .record-spin.slow {
20
+ animation-duration: 4s;
21
+ }
22
+
23
+ .record-spin.fast {
24
+ animation-duration: 0.5s;
25
+ }
26
+
27
+ .crossfader {
28
+ -webkit-appearance: none;
29
+ width: 100%;
30
+ height: 8px;
31
+ background: #ddd;
32
+ outline: none;
33
+ border-radius: 4px;
34
+ }
35
+
36
+ .crossfader::-webkit-slider-thumb {
37
+ -webkit-appearance: none;
38
+ appearance: none;
39
+ width: 24px;
40
+ height: 24px;
41
+ border-radius: 50%;
42
+ background: #4f46e5;
43
+ cursor: pointer;
44
+ }
45
+
46
+ .scratch-area {
47
+ touch-action: none;
48
+ }
49
+
50
+ .pitch-slider {
51
+ -webkit-appearance: none;
52
+ height: 8px;
53
+ background: linear-gradient(to right, #ef4444, #f59e0b, #10b981);
54
+ border-radius: 4px;
55
+ outline: none;
56
+ }
57
+
58
+ .pitch-slider::-webkit-slider-thumb {
59
+ -webkit-appearance: none;
60
+ appearance: none;
61
+ width: 20px;
62
+ height: 20px;
63
+ border-radius: 50%;
64
+ background: white;
65
+ border: 2px solid #4f46e5;
66
+ cursor: pointer;
67
+ }
68
+ </style>
69
+ </head>
70
+ <body class="bg-gray-900 text-white min-h-screen">
71
+ <div class="container mx-auto px-4 py-8">
72
+ <header class="text-center mb-8">
73
+ <h1 class="text-4xl font-bold bg-gradient-to-r from-purple-500 to-blue-500 bg-clip-text text-transparent">Turntable DJ Scratch App</h1>
74
+ <p class="text-gray-400 mt-2">Load your MP3 and scratch like a pro!</p>
75
+ </header>
76
+
77
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
78
+ <!-- Left Turntable -->
79
+ <div class="bg-gray-800 rounded-xl p-6 shadow-lg">
80
+ <div class="flex justify-between items-center mb-4">
81
+ <h2 class="text-xl font-semibold text-purple-400">Deck A</h2>
82
+ <div class="flex space-x-2">
83
+ <button id="playA" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg">
84
+ <i class="fas fa-play"></i>
85
+ </button>
86
+ <button id="stopA" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg">
87
+ <i class="fas fa-stop"></i>
88
+ </button>
89
+ <button id="cueA" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">
90
+ <i class="fas fa-undo"></i>
91
+ </button>
92
+ </div>
93
+ </div>
94
+
95
+ <div class="relative h-64 w-64 mx-auto mb-6">
96
+ <div class="absolute inset-0 bg-gray-900 rounded-full flex items-center justify-center">
97
+ <div id="recordA" class="relative h-56 w-56 rounded-full border-4 border-gray-700 bg-gradient-to-br from-gray-800 to-gray-900 flex items-center justify-center">
98
+ <div class="absolute h-6 w-6 rounded-full bg-gray-700 z-10"></div>
99
+ <div class="absolute h-4 w-4 rounded-full bg-gray-600 z-10"></div>
100
+ <div class="absolute h-12 w-12 rounded-full bg-gray-900 z-0"></div>
101
+ </div>
102
+ </div>
103
+ <div class="absolute -bottom-2 left-1/2 transform -translate-x-1/2 w-8 h-16 bg-gray-700 rounded-b-lg"></div>
104
+ </div>
105
+
106
+ <div class="mb-4">
107
+ <label class="block text-sm font-medium text-gray-400 mb-2">Pitch</label>
108
+ <input type="range" id="pitchA" class="pitch-slider w-full" min="-50" max="50" value="0">
109
+ </div>
110
+
111
+ <div class="mb-4">
112
+ <label class="block text-sm font-medium text-gray-400 mb-2">Volume</label>
113
+ <input type="range" id="volumeA" class="w-full" min="0" max="1" step="0.01" value="0.8">
114
+ </div>
115
+
116
+ <div class="mb-4">
117
+ <label class="block text-sm font-medium text-gray-400 mb-2">Load MP3</label>
118
+ <input type="file" id="fileA" accept="audio/mp3" class="block w-full text-sm text-gray-400
119
+ file:mr-4 file:py-2 file:px-4
120
+ file:rounded-md file:border-0
121
+ file:text-sm file:font-semibold
122
+ file:bg-purple-600 file:text-white
123
+ hover:file:bg-purple-700">
124
+ </div>
125
+
126
+ <div id="scratchA" class="scratch-area h-16 bg-gray-700 rounded-lg flex items-center justify-center cursor-grab active:cursor-grabbing">
127
+ <span class="text-gray-400">Scratch Area (Drag with mouse)</span>
128
+ </div>
129
+ </div>
130
+
131
+ <!-- Right Turntable -->
132
+ <div class="bg-gray-800 rounded-xl p-6 shadow-lg">
133
+ <div class="flex justify-between items-center mb-4">
134
+ <h2 class="text-xl font-semibold text-blue-400">Deck B</h2>
135
+ <div class="flex space-x-2">
136
+ <button id="playB" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg">
137
+ <i class="fas fa-play"></i>
138
+ </button>
139
+ <button id="stopB" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg">
140
+ <i class="fas fa-stop"></i>
141
+ </button>
142
+ <button id="cueB" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">
143
+ <i class="fas fa-undo"></i>
144
+ </button>
145
+ </div>
146
+ </div>
147
+
148
+ <div class="relative h-64 w-64 mx-auto mb-6">
149
+ <div class="absolute inset-0 bg-gray-900 rounded-full flex items-center justify-center">
150
+ <div id="recordB" class="relative h-56 w-56 rounded-full border-4 border-gray-700 bg-gradient-to-br from-gray-800 to-gray-900 flex items-center justify-center">
151
+ <div class="absolute h-6 w-6 rounded-full bg-gray-700 z-10"></div>
152
+ <div class="absolute h-4 w-4 rounded-full bg-gray-600 z-10"></div>
153
+ <div class="absolute h-12 w-12 rounded-full bg-gray-900 z-0"></div>
154
+ </div>
155
+ </div>
156
+ <div class="absolute -bottom-2 left-1/2 transform -translate-x-1/2 w-8 h-16 bg-gray-700 rounded-b-lg"></div>
157
+ </div>
158
+
159
+ <div class="mb-4">
160
+ <label class="block text-sm font-medium text-gray-400 mb-2">Pitch</label>
161
+ <input type="range" id="pitchB" class="pitch-slider w-full" min="-50" max="50" value="0">
162
+ </div>
163
+
164
+ <div class="mb-4">
165
+ <label class="block text-sm font-medium text-gray-400 mb-2">Volume</label>
166
+ <input type="range" id="volumeB" class="w-full" min="0" max="1" step="0.01" value="0.8">
167
+ </div>
168
+
169
+ <div class="mb-4">
170
+ <label class="block text-sm font-medium text-gray-400 mb-2">Load MP3</label>
171
+ <input type="file" id="fileB" accept="audio/mp3" class="block w-full text-sm text-gray-400
172
+ file:mr-4 file:py-2 file:px-4
173
+ file:rounded-md file:border-0
174
+ file:text-sm file:font-semibold
175
+ file:bg-blue-600 file:text-white
176
+ hover:file:bg-blue-700">
177
+ </div>
178
+
179
+ <div id="scratchB" class="scratch-area h-16 bg-gray-700 rounded-lg flex items-center justify-center cursor-grab active:cursor-grabbing">
180
+ <span class="text-gray-400">Scratch Area (Drag with mouse)</span>
181
+ </div>
182
+ </div>
183
+ </div>
184
+
185
+ <!-- Mixer Section -->
186
+ <div class="mt-8 bg-gray-800 rounded-xl p-6 shadow-lg">
187
+ <h2 class="text-xl font-semibold text-center mb-6 text-indigo-400">Mixer Controls</h2>
188
+
189
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
190
+ <div class="flex flex-col items-center">
191
+ <label class="block text-sm font-medium text-gray-400 mb-2">Deck A Volume</label>
192
+ <input type="range" id="masterVolumeA" class="w-full h-32 -rotate-90" min="0" max="1" step="0.01" value="0.8">
193
+ </div>
194
+
195
+ <div class="flex flex-col items-center justify-center">
196
+ <label class="block text-sm font-medium text-gray-400 mb-2">Crossfader</label>
197
+ <input type="range" id="crossfader" class="crossfader w-full" min="0" max="1" step="0.01" value="0.5">
198
+ <div class="flex justify-between w-full mt-2 text-xs text-gray-400">
199
+ <span>A</span>
200
+ <span>B</span>
201
+ </div>
202
+ </div>
203
+
204
+ <div class="flex flex-col items-center">
205
+ <label class="block text-sm font-medium text-gray-400 mb-2">Deck B Volume</label>
206
+ <input type="range" id="masterVolumeB" class="w-full h-32 rotate-90" min="0" max="1" step="0.01" value="0.8">
207
+ </div>
208
+ </div>
209
+
210
+ <div class="mt-6 flex justify-center">
211
+ <button id="masterPlay" class="bg-green-600 hover:bg-green-700 text-white px-6 py-3 rounded-lg text-lg font-semibold flex items-center">
212
+ <i class="fas fa-play mr-2"></i> Master Play
213
+ </button>
214
+ </div>
215
+ </div>
216
+
217
+ <!-- Visualizer -->
218
+ <div class="mt-8 bg-gray-800 rounded-xl p-6 shadow-lg">
219
+ <h2 class="text-xl font-semibold text-center mb-4 text-pink-400">Audio Visualizer</h2>
220
+ <canvas id="visualizer" class="w-full h-32 bg-gray-900 rounded-lg"></canvas>
221
+ </div>
222
+ </div>
223
+
224
+ <script>
225
+ document.addEventListener('DOMContentLoaded', function() {
226
+ // Audio context setup
227
+ const AudioContext = window.AudioContext || window.webkitAudioContext;
228
+ const audioContext = new AudioContext();
229
+
230
+ // Master gain node
231
+ const masterGain = audioContext.createGain();
232
+ masterGain.gain.value = 0.8;
233
+ masterGain.connect(audioContext.destination);
234
+
235
+ // Deck A elements
236
+ const fileInputA = document.getElementById('fileA');
237
+ const playButtonA = document.getElementById('playA');
238
+ const stopButtonA = document.getElementById('stopA');
239
+ const cueButtonA = document.getElementById('cueA');
240
+ const pitchSliderA = document.getElementById('pitchA');
241
+ const volumeSliderA = document.getElementById('volumeA');
242
+ const masterVolumeA = document.getElementById('masterVolumeA');
243
+ const recordA = document.getElementById('recordA');
244
+ const scratchAreaA = document.getElementById('scratchA');
245
+
246
+ // Deck B elements
247
+ const fileInputB = document.getElementById('fileB');
248
+ const playButtonB = document.getElementById('playB');
249
+ const stopButtonB = document.getElementById('stopB');
250
+ const cueButtonB = document.getElementById('cueB');
251
+ const pitchSliderB = document.getElementById('pitchB');
252
+ const volumeSliderB = document.getElementById('volumeB');
253
+ const masterVolumeB = document.getElementById('masterVolumeB');
254
+ const recordB = document.getElementById('recordB');
255
+ const scratchAreaB = document.getElementById('scratchB');
256
+
257
+ // Mixer elements
258
+ const crossfader = document.getElementById('crossfader');
259
+ const masterPlay = document.getElementById('masterPlay');
260
+
261
+ // Visualizer
262
+ const visualizer = document.getElementById('visualizer');
263
+ const canvasCtx = visualizer.getContext('2d');
264
+
265
+ // Audio buffers and nodes
266
+ let audioBufferA = null;
267
+ let audioBufferB = null;
268
+ let sourceNodeA = null;
269
+ let sourceNodeB = null;
270
+ let gainNodeA = null;
271
+ let gainNodeB = null;
272
+ let pannerNodeA = null;
273
+ let pannerNodeB = null;
274
+ let playbackRateA = 1.0;
275
+ let playbackRateB = 1.0;
276
+ let isPlayingA = false;
277
+ let isPlayingB = false;
278
+
279
+ // Initialize audio nodes for Deck A
280
+ function initDeckA() {
281
+ if (audioBufferA) {
282
+ sourceNodeA = audioContext.createBufferSource();
283
+ sourceNodeA.buffer = audioBufferA;
284
+
285
+ gainNodeA = audioContext.createGain();
286
+ gainNodeA.gain.value = volumeSliderA.value;
287
+
288
+ pannerNodeA = audioContext.createStereoPanner();
289
+ pannerNodeA.pan.value = -0.5;
290
+
291
+ sourceNodeA.connect(gainNodeA);
292
+ gainNodeA.connect(pannerNodeA);
293
+ pannerNodeA.connect(masterGain);
294
+
295
+ sourceNodeA.playbackRate.value = playbackRateA;
296
+ sourceNodeA.loop = true;
297
+
298
+ sourceNodeA.onended = function() {
299
+ isPlayingA = false;
300
+ recordA.classList.remove('record-spin');
301
+ };
302
+ }
303
+ }
304
+
305
+ // Initialize audio nodes for Deck B
306
+ function initDeckB() {
307
+ if (audioBufferB) {
308
+ sourceNodeB = audioContext.createBufferSource();
309
+ sourceNodeB.buffer = audioBufferB;
310
+
311
+ gainNodeB = audioContext.createGain();
312
+ gainNodeB.gain.value = volumeSliderB.value;
313
+
314
+ pannerNodeB = audioContext.createStereoPanner();
315
+ pannerNodeB.pan.value = 0.5;
316
+
317
+ sourceNodeB.connect(gainNodeB);
318
+ gainNodeB.connect(pannerNodeB);
319
+ pannerNodeB.connect(masterGain);
320
+
321
+ sourceNodeB.playbackRate.value = playbackRateB;
322
+ sourceNodeB.loop = true;
323
+
324
+ sourceNodeB.onended = function() {
325
+ isPlayingB = false;
326
+ recordB.classList.remove('record-spin');
327
+ };
328
+ }
329
+ }
330
+
331
+ // Load audio file for Deck A
332
+ fileInputA.addEventListener('change', function(e) {
333
+ const file = e.target.files[0];
334
+ if (file) {
335
+ const reader = new FileReader();
336
+ reader.onload = function(e) {
337
+ audioContext.decodeAudioData(e.target.result)
338
+ .then(buffer => {
339
+ audioBufferA = buffer;
340
+ initDeckA();
341
+ recordA.classList.add('record-spin');
342
+ recordA.classList.add('slow');
343
+ })
344
+ .catch(err => {
345
+ console.error("Error decoding audio data", err);
346
+ });
347
+ };
348
+ reader.readAsArrayBuffer(file);
349
+ }
350
+ });
351
+
352
+ // Load audio file for Deck B
353
+ fileInputB.addEventListener('change', function(e) {
354
+ const file = e.target.files[0];
355
+ if (file) {
356
+ const reader = new FileReader();
357
+ reader.onload = function(e) {
358
+ audioContext.decodeAudioData(e.target.result)
359
+ .then(buffer => {
360
+ audioBufferB = buffer;
361
+ initDeckB();
362
+ recordB.classList.add('record-spin');
363
+ recordB.classList.add('slow');
364
+ })
365
+ .catch(err => {
366
+ console.error("Error decoding audio data", err);
367
+ });
368
+ };
369
+ reader.readAsArrayBuffer(file);
370
+ }
371
+ });
372
+
373
+ // Play Deck A
374
+ playButtonA.addEventListener('click', function() {
375
+ if (audioBufferA && !isPlayingA) {
376
+ initDeckA();
377
+ sourceNodeA.start(0);
378
+ isPlayingA = true;
379
+ recordA.classList.add('record-spin');
380
+
381
+ // Adjust speed based on pitch
382
+ if (pitchSliderA.value > 0) {
383
+ recordA.classList.remove('slow');
384
+ recordA.classList.add('fast');
385
+ } else if (pitchSliderA.value < 0) {
386
+ recordA.classList.remove('fast');
387
+ recordA.classList.add('slow');
388
+ } else {
389
+ recordA.classList.remove('fast', 'slow');
390
+ }
391
+ }
392
+ });
393
+
394
+ // Play Deck B
395
+ playButtonB.addEventListener('click', function() {
396
+ if (audioBufferB && !isPlayingB) {
397
+ initDeckB();
398
+ sourceNodeB.start(0);
399
+ isPlayingB = true;
400
+ recordB.classList.add('record-spin');
401
+
402
+ // Adjust speed based on pitch
403
+ if (pitchSliderB.value > 0) {
404
+ recordB.classList.remove('slow');
405
+ recordB.classList.add('fast');
406
+ } else if (pitchSliderB.value < 0) {
407
+ recordB.classList.remove('fast');
408
+ recordB.classList.add('slow');
409
+ } else {
410
+ recordB.classList.remove('fast', 'slow');
411
+ }
412
+ }
413
+ });
414
+
415
+ // Stop Deck A
416
+ stopButtonA.addEventListener('click', function() {
417
+ if (isPlayingA) {
418
+ sourceNodeA.stop();
419
+ isPlayingA = false;
420
+ recordA.classList.remove('record-spin', 'fast', 'slow');
421
+ }
422
+ });
423
+
424
+ // Stop Deck B
425
+ stopButtonB.addEventListener('click', function() {
426
+ if (isPlayingB) {
427
+ sourceNodeB.stop();
428
+ isPlayingB = false;
429
+ recordB.classList.remove('record-spin', 'fast', 'slow');
430
+ }
431
+ });
432
+
433
+ // Cue Deck A
434
+ cueButtonA.addEventListener('click', function() {
435
+ if (audioBufferA) {
436
+ if (isPlayingA) {
437
+ sourceNodeA.stop();
438
+ isPlayingA = false;
439
+ recordA.classList.remove('record-spin', 'fast', 'slow');
440
+ }
441
+
442
+ initDeckA();
443
+ sourceNodeA.start(0);
444
+ setTimeout(() => {
445
+ sourceNodeA.stop();
446
+ }, 500);
447
+
448
+ // Briefly show spinning record
449
+ recordA.classList.add('record-spin', 'fast');
450
+ setTimeout(() => {
451
+ recordA.classList.remove('record-spin', 'fast');
452
+ }, 500);
453
+ }
454
+ });
455
+
456
+ // Cue Deck B
457
+ cueButtonB.addEventListener('click', function() {
458
+ if (audioBufferB) {
459
+ if (isPlayingB) {
460
+ sourceNodeB.stop();
461
+ isPlayingB = false;
462
+ recordB.classList.remove('record-spin', 'fast', 'slow');
463
+ }
464
+
465
+ initDeckB();
466
+ sourceNodeB.start(0);
467
+ setTimeout(() => {
468
+ sourceNodeB.stop();
469
+ }, 500);
470
+
471
+ // Briefly show spinning record
472
+ recordB.classList.add('record-spin', 'fast');
473
+ setTimeout(() => {
474
+ recordB.classList.remove('record-spin', 'fast');
475
+ }, 500);
476
+ }
477
+ });
478
+
479
+ // Pitch control for Deck A
480
+ pitchSliderA.addEventListener('input', function() {
481
+ playbackRateA = 1.0 + (this.value / 100);
482
+ if (isPlayingA) {
483
+ sourceNodeA.playbackRate.value = playbackRateA;
484
+
485
+ // Adjust visual spin speed
486
+ if (this.value > 0) {
487
+ recordA.classList.remove('slow');
488
+ recordA.classList.add('fast');
489
+ } else if (this.value < 0) {
490
+ recordA.classList.remove('fast');
491
+ recordA.classList.add('slow');
492
+ } else {
493
+ recordA.classList.remove('fast', 'slow');
494
+ }
495
+ }
496
+ });
497
+
498
+ // Pitch control for Deck B
499
+ pitchSliderB.addEventListener('input', function() {
500
+ playbackRateB = 1.0 + (this.value / 100);
501
+ if (isPlayingB) {
502
+ sourceNodeB.playbackRate.value = playbackRateB;
503
+
504
+ // Adjust visual spin speed
505
+ if (this.value > 0) {
506
+ recordB.classList.remove('slow');
507
+ recordB.classList.add('fast');
508
+ } else if (this.value < 0) {
509
+ recordB.classList.remove('fast');
510
+ recordB.classList.add('slow');
511
+ } else {
512
+ recordB.classList.remove('fast', 'slow');
513
+ }
514
+ }
515
+ });
516
+
517
+ // Volume control for Deck A
518
+ volumeSliderA.addEventListener('input', function() {
519
+ if (gainNodeA) {
520
+ gainNodeA.gain.value = this.value;
521
+ }
522
+ });
523
+
524
+ // Volume control for Deck B
525
+ volumeSliderB.addEventListener('input', function() {
526
+ if (gainNodeB) {
527
+ gainNodeB.gain.value = this.value;
528
+ }
529
+ });
530
+
531
+ // Master volume for Deck A
532
+ masterVolumeA.addEventListener('input', function() {
533
+ if (pannerNodeA) {
534
+ pannerNodeA.pan.value = -0.5 + (this.value * 0.5);
535
+ }
536
+ });
537
+
538
+ // Master volume for Deck B
539
+ masterVolumeB.addEventListener('input', function() {
540
+ if (pannerNodeB) {
541
+ pannerNodeB.pan.value = 0.5 - (this.value * 0.5);
542
+ }
543
+ });
544
+
545
+ // Crossfader control
546
+ crossfader.addEventListener('input', function() {
547
+ const value = parseFloat(this.value);
548
+ if (pannerNodeA && pannerNodeB) {
549
+ pannerNodeA.pan.value = -1 + value;
550
+ pannerNodeB.pan.value = 1 - value;
551
+ }
552
+ });
553
+
554
+ // Master play button
555
+ masterPlay.addEventListener('click', function() {
556
+ if (audioBufferA && !isPlayingA) {
557
+ playButtonA.click();
558
+ }
559
+ if (audioBufferB && !isPlayingB) {
560
+ playButtonB.click();
561
+ }
562
+ });
563
+
564
+ // Scratch functionality for Deck A
565
+ let isScratchingA = false;
566
+ let lastX = 0;
567
+ let scratchBufferA = null;
568
+
569
+ scratchAreaA.addEventListener('mousedown', function(e) {
570
+ if (audioBufferA) {
571
+ isScratchingA = true;
572
+ lastX = e.clientX;
573
+
574
+ // Create a new buffer source for scratching
575
+ if (isPlayingA) {
576
+ sourceNodeA.stop();
577
+ isPlayingA = false;
578
+ }
579
+
580
+ scratchBufferA = audioContext.createBufferSource();
581
+ scratchBufferA.buffer = audioBufferA;
582
+ scratchBufferA.connect(gainNodeA);
583
+ scratchBufferA.loop = true;
584
+ scratchBufferA.start(0);
585
+
586
+ recordA.classList.add('record-spin');
587
+ recordA.classList.remove('fast', 'slow');
588
+ }
589
+ });
590
+
591
+ document.addEventListener('mousemove', function(e) {
592
+ if (isScratchingA && scratchBufferA) {
593
+ const deltaX = e.clientX - lastX;
594
+ lastX = e.clientX;
595
+
596
+ // Adjust playback rate based on mouse movement
597
+ const rate = 1.0 + (deltaX * 0.01);
598
+ scratchBufferA.playbackRate.value = rate;
599
+
600
+ // Adjust visual spin speed
601
+ if (deltaX > 0) {
602
+ recordA.classList.remove('slow');
603
+ recordA.classList.add('fast');
604
+ } else if (deltaX < 0) {
605
+ recordA.classList.remove('fast');
606
+ recordA.classList.add('slow');
607
+ }
608
+ }
609
+ });
610
+
611
+ document.addEventListener('mouseup', function() {
612
+ if (isScratchingA) {
613
+ isScratchingA = false;
614
+
615
+ if (scratchBufferA) {
616
+ scratchBufferA.stop();
617
+ scratchBufferA = null;
618
+ }
619
+
620
+ recordA.classList.remove('record-spin', 'fast', 'slow');
621
+ }
622
+ });
623
+
624
+ // Scratch functionality for Deck B
625
+ let isScratchingB = false;
626
+ let scratchBufferB = null;
627
+
628
+ scratchAreaB.addEventListener('mousedown', function(e) {
629
+ if (audioBufferB) {
630
+ isScratchingB = true;
631
+ lastX = e.clientX;
632
+
633
+ // Create a new buffer source for scratching
634
+ if (isPlayingB) {
635
+ sourceNodeB.stop();
636
+ isPlayingB = false;
637
+ }
638
+
639
+ scratchBufferB = audioContext.createBufferSource();
640
+ scratchBufferB.buffer = audioBufferB;
641
+ scratchBufferB.connect(gainNodeB);
642
+ scratchBufferB.loop = true;
643
+ scratchBufferB.start(0);
644
+
645
+ recordB.classList.add('record-spin');
646
+ recordB.classList.remove('fast', 'slow');
647
+ }
648
+ });
649
+
650
+ document.addEventListener('mousemove', function(e) {
651
+ if (isScratchingB && scratchBufferB) {
652
+ const deltaX = e.clientX - lastX;
653
+ lastX = e.clientX;
654
+
655
+ // Adjust playback rate based on mouse movement
656
+ const rate = 1.0 + (deltaX * 0.01);
657
+ scratchBufferB.playbackRate.value = rate;
658
+
659
+ // Adjust visual spin speed
660
+ if (deltaX > 0) {
661
+ recordB.classList.remove('slow');
662
+ recordB.classList.add('fast');
663
+ } else if (deltaX < 0) {
664
+ recordB.classList.remove('fast');
665
+ recordB.classList.add('slow');
666
+ }
667
+ }
668
+ });
669
+
670
+ document.addEventListener('mouseup', function() {
671
+ if (isScratchingB) {
672
+ isScratchingB = false;
673
+
674
+ if (scratchBufferB) {
675
+ scratchBufferB.stop();
676
+ scratchBufferB = null;
677
+ }
678
+
679
+ recordB.classList.remove('record-spin', 'fast', 'slow');
680
+ }
681
+ });
682
+
683
+ // Visualizer setup
684
+ let analyser = audioContext.createAnalyser();
685
+ analyser.fftSize = 256;
686
+ masterGain.connect(analyser);
687
+
688
+ const bufferLength = analyser.frequencyBinCount;
689
+ const dataArray = new Uint8Array(bufferLength);
690
+
691
+ visualizer.width = visualizer.offsetWidth;
692
+ visualizer.height = visualizer.offsetHeight;
693
+
694
+ function drawVisualizer() {
695
+ requestAnimationFrame(drawVisualizer);
696
+
697
+ analyser.getByteFrequencyData(dataArray);
698
+
699
+ canvasCtx.fillStyle = 'rgb(17, 24, 39)';
700
+ canvasCtx.fillRect(0, 0, visualizer.width, visualizer.height);
701
+
702
+ const barWidth = (visualizer.width / bufferLength) * 2.5;
703
+ let x = 0;
704
+
705
+ for (let i = 0; i < bufferLength; i++) {
706
+ const barHeight = (dataArray[i] / 255) * visualizer.height;
707
+
708
+ // Create gradient
709
+ const gradient = canvasCtx.createLinearGradient(0, 0, 0, visualizer.height);
710
+ gradient.addColorStop(0, '#4f46e5');
711
+ gradient.addColorStop(0.5, '#8b5cf6');
712
+ gradient.addColorStop(1, '#ec4899');
713
+
714
+ canvasCtx.fillStyle = gradient;
715
+ canvasCtx.fillRect(x, visualizer.height - barHeight, barWidth, barHeight);
716
+
717
+ x += barWidth + 1;
718
+ }
719
+ }
720
+
721
+ drawVisualizer();
722
+
723
+ // Handle tab visibility change to resume audio context
724
+ document.addEventListener('visibilitychange', function() {
725
+ if (document.visibilityState === 'visible') {
726
+ audioContext.resume();
727
+ }
728
+ });
729
+ });
730
+ </script>
731
+ <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=C50BARZ/turntable-dj-scratch-app" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
732
+ </html>