IgorSlinko commited on
Commit
b6d4ada
·
verified ·
1 Parent(s): cfa3b79

nice. but the grid became not clickable. return the grid to the state when I can click on the boxes - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +818 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Drum Machine
3
- emoji: 🐠
4
- colorFrom: purple
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: drum-machine
3
+ emoji: 🐳
4
+ colorFrom: red
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,818 @@
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>Drum Machine - Кахон</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 playHead {
11
+ from { left: 0; }
12
+ to { left: 100%; }
13
+ }
14
+ .play-head {
15
+ animation: playHead linear infinite;
16
+ animation-duration: var(--bpm);
17
+ }
18
+ .note {
19
+ transition: all 0.1s ease;
20
+ }
21
+ .note.active {
22
+ transform: scale(1.05);
23
+ box-shadow: 0 0 10px rgba(255, 255, 255, 0.8);
24
+ }
25
+ .pulse {
26
+ animation: pulse 0.5s;
27
+ }
28
+ @keyframes pulse {
29
+ 0% { transform: scale(1); }
30
+ 50% { transform: scale(1.1); }
31
+ 100% { transform: scale(1); }
32
+ }
33
+ .staff-container {
34
+ position: relative;
35
+ height: 120px;
36
+ margin-bottom: 20px;
37
+ overflow-x: auto;
38
+ overflow-y: hidden;
39
+ white-space: nowrap;
40
+ }
41
+
42
+ .staff {
43
+ position: relative;
44
+ display: inline-block;
45
+ height: 100px;
46
+ width: 800px;
47
+ background-color: rgba(255, 255, 255, 0.05);
48
+ border-radius: 4px;
49
+ }
50
+
51
+ .staff-line {
52
+ position: absolute;
53
+ width: 100%;
54
+ height: 1px;
55
+ background-color: white;
56
+ }
57
+
58
+ .staff-line:nth-child(1) { top: 10%; }
59
+ .staff-line:nth-child(2) { top: 30%; }
60
+ .staff-line:nth-child(3) { top: 50%; }
61
+ .staff-line:nth-child(4) { top: 70%; }
62
+ .staff-line:nth-child(5) { top: 90%; }
63
+
64
+ .neutral-clef {
65
+ position: absolute;
66
+ left: 10px;
67
+ top: 50%;
68
+ transform: translateY(-50%);
69
+ font-size: 40px;
70
+ font-weight: bold;
71
+ color: white;
72
+ }
73
+
74
+ .bar-line {
75
+ position: absolute;
76
+ top: 0;
77
+ height: 100%;
78
+ width: 2px;
79
+ background-color: white;
80
+ }
81
+
82
+ .drum-note {
83
+ position: absolute;
84
+ width: 12px;
85
+ height: 12px;
86
+ border-radius: 50%;
87
+ background-color: white;
88
+ transform: translateX(-50%);
89
+ }
90
+
91
+ .drum-note.bass { top: 90%; }
92
+ .drum-note.snare { top: 50%; }
93
+ .drum-note.hihat { top: 30%; }
94
+ .drum-note.rim { top: 70%; }
95
+
96
+ .note-stem {
97
+ position: absolute;
98
+ width: 2px;
99
+ background-color: white;
100
+ transform-origin: top center;
101
+ }
102
+ </style>
103
+ </head>
104
+ <body class="bg-gray-900 text-white min-h-screen flex flex-col">
105
+ <header class="py-6 bg-gray-800 shadow-lg">
106
+ <div class="container mx-auto px-4">
107
+ <h1 class="text-3xl font-bold text-center text-yellow-400">
108
+ <i class="fas fa-drum mr-2"></i> Drum Machine - Кахон
109
+ </h1>
110
+ </div>
111
+ </header>
112
+
113
+ <main class="flex-grow container mx-auto px-4 py-8">
114
+ <div class="flex flex-col lg:flex-row gap-8">
115
+ <!-- Controls -->
116
+ <div class="lg:w-1/4 bg-gray-800 p-6 rounded-lg shadow-lg">
117
+ <div class="mb-6">
118
+ <h2 class="text-xl font-semibold mb-4 text-yellow-400">Controls</h2>
119
+ <div class="flex items-center justify-between mb-4">
120
+ <button id="playBtn" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg flex items-center">
121
+ <i class="fas fa-play mr-2"></i> Play
122
+ </button>
123
+ <button id="stopBtn" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg flex items-center">
124
+ <i class="fas fa-stop mr-2"></i> Stop
125
+ </button>
126
+ </div>
127
+ <div class="mb-4">
128
+ <label for="bpm" class="block mb-2">BPM: <span id="bpmValue">120</span></label>
129
+ <input type="range" id="bpm" min="60" max="240" value="120" class="w-full accent-yellow-500">
130
+ </div>
131
+ <div class="mb-4">
132
+ <label for="volume" class="block mb-2">Volume: <span id="volumeValue">80</span>%</label>
133
+ <input type="range" id="volume" min="0" max="100" value="80" class="w-full accent-yellow-500">
134
+ </div>
135
+ <div class="mb-4">
136
+ <label for="steps" class="block mb-2">Steps: <span id="stepsValue">16</span></label>
137
+ <input type="range" id="steps" min="4" max="32" step="4" value="16" class="w-full accent-yellow-500">
138
+ </div>
139
+ <button id="clearBtn" class="w-full bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg flex items-center justify-center">
140
+ <i class="fas fa-trash-alt mr-2"></i> Clear All
141
+ </button>
142
+ </div>
143
+
144
+ <div class="mb-6">
145
+ <h2 class="text-xl font-semibold mb-4 text-yellow-400">Sounds</h2>
146
+ <div class="grid grid-cols-2 gap-2">
147
+ <button data-sound="bass" class="sound-btn bg-blue-600 hover:bg-blue-700 text-white p-2 rounded flex items-center justify-center">
148
+ <i class="fas fa-drum mr-2"></i> Bass
149
+ </button>
150
+ <button data-sound="snare" class="sound-btn bg-purple-600 hover:bg-purple-700 text-white p-2 rounded flex items-center justify-center">
151
+ <i class="fas fa-drum mr-2"></i> Snare
152
+ </button>
153
+ <button data-sound="hihat" class="sound-btn bg-green-600 hover:bg-green-700 text-white p-2 rounded flex items-center justify-center">
154
+ <i class="fas fa-drum mr-2"></i> Hi-Hat
155
+ </button>
156
+ <button data-sound="rim" class="sound-btn bg-yellow-600 hover:bg-yellow-700 text-white p-2 rounded flex items-center justify-center">
157
+ <i class="fas fa-drum mr-2"></i> Rim
158
+ </button>
159
+ </div>
160
+ </div>
161
+
162
+ <div>
163
+ <h2 class="text-xl font-semibold mb-4 text-yellow-400">Pattern</h2>
164
+ <select id="patternSelect" class="w-full bg-gray-700 text-white p-2 rounded mb-2">
165
+ <option value="empty">Empty</option>
166
+ <option value="basic">Basic Pattern</option>
167
+ <option value="complex">Complex Pattern</option>
168
+ <option value="random">Random</option>
169
+ </select>
170
+ <div class="flex gap-2">
171
+ <button id="savePatternBtn" class="flex-1 bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg">
172
+ Save
173
+ </button>
174
+ <button id="loadPatternBtn" class="flex-1 bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg">
175
+ Load
176
+ </button>
177
+ </div>
178
+ </div>
179
+ </div>
180
+
181
+ <!-- Drum Machine Grid -->
182
+ <div class="lg:w-3/4">
183
+ <div class="bg-gray-800 p-6 rounded-lg shadow-lg">
184
+ <h2 class="text-xl font-semibold mb-4 text-yellow-400">Drum Grid</h2>
185
+
186
+ <!-- Playhead -->
187
+ <div class="relative h-2 mb-4 bg-gray-700 rounded overflow-hidden">
188
+ <div id="playHead" class="absolute top-0 left-0 w-1 h-2 bg-yellow-400 z-10" style="display: none;"></div>
189
+ </div>
190
+
191
+ <!-- Note Visualization -->
192
+ <div id="noteVisualization" class="mb-6 p-4 bg-gray-700 rounded-lg">
193
+ <div class="text-center text-gray-400">Notes will appear here when playing</div>
194
+ </div>
195
+
196
+ <!-- Staff Visualization -->
197
+ <div id="staffVisualization" class="mb-6 p-4 bg-gray-700 rounded-lg">
198
+ <h3 class="text-lg font-semibold mb-2">Drum Notation</h3>
199
+ <div class="staff-container">
200
+ <div class="staff" id="drumStaff">
201
+ <!-- Staff lines -->
202
+ <div class="staff-line"></div>
203
+ <div class="staff-line"></div>
204
+ <div class="staff-line"></div>
205
+ <div class="staff-line"></div>
206
+ <div class="staff-line"></div>
207
+
208
+ <!-- Neutral clef -->
209
+ <div class="neutral-clef">𝄽</div>
210
+
211
+ <!-- Bar lines will be added here -->
212
+ <!-- Notes will be added here -->
213
+ </div>
214
+ </div>
215
+ </div>
216
+
217
+ <!-- Drum Grid -->
218
+ <div class="overflow-x-auto">
219
+ <table id="drumGrid" class="w-full">
220
+ <thead>
221
+ <tr>
222
+ <th class="w-16 h-8 bg-gray-700"></th>
223
+ <!-- Step numbers -->
224
+ </tr>
225
+ </thead>
226
+ <tbody>
227
+ <!-- Rows will be added by JavaScript -->
228
+ </tbody>
229
+ </table>
230
+ </div>
231
+ </div>
232
+ </div>
233
+ </div>
234
+ </main>
235
+
236
+ <footer class="py-4 bg-gray-800 text-center text-gray-400">
237
+ <div class="container mx-auto px-4">
238
+ <p>Drum Machine - Кахон | Created with HTML, CSS & JavaScript</p>
239
+ </div>
240
+ </footer>
241
+
242
+ <script>
243
+ document.addEventListener('DOMContentLoaded', function() {
244
+ // Audio context
245
+ const AudioContext = window.AudioContext || window.webkitAudioContext;
246
+ const audioContext = new AudioContext();
247
+ const masterGain = audioContext.createGain();
248
+ masterGain.gain.value = 0.8;
249
+ masterGain.connect(audioContext.destination);
250
+
251
+ // Sound samples (base64 encoded short samples for demo purposes)
252
+ const sounds = {
253
+ bass: {
254
+ name: "Bass",
255
+ color: "bg-blue-600",
256
+ sample: generateKick()
257
+ },
258
+ snare: {
259
+ name: "Snare",
260
+ color: "bg-purple-600",
261
+ sample: generateSnare()
262
+ },
263
+ hihat: {
264
+ name: "Hi-Hat",
265
+ color: "bg-green-600",
266
+ sample: generateHiHat()
267
+ },
268
+ rim: {
269
+ name: "Rim",
270
+ color: "bg-yellow-600",
271
+ sample: generateRim()
272
+ }
273
+ };
274
+
275
+ // Generate simple drum sounds (for demo)
276
+ function generateKick() {
277
+ const sampleRate = audioContext.sampleRate;
278
+ const duration = 0.5;
279
+ const numFrames = duration * sampleRate;
280
+ const buffer = audioContext.createBuffer(1, numFrames, sampleRate);
281
+ const data = buffer.getChannelData(0);
282
+
283
+ for (let i = 0; i < numFrames; i++) {
284
+ const n = -0.5 * Math.cos(i / (sampleRate * 0.002)) + 0.5;
285
+ data[i] = n * Math.exp(-i / (sampleRate * 0.1));
286
+ }
287
+
288
+ return buffer;
289
+ }
290
+
291
+ function generateSnare() {
292
+ const sampleRate = audioContext.sampleRate;
293
+ const duration = 0.3;
294
+ const numFrames = duration * sampleRate;
295
+ const buffer = audioContext.createBuffer(1, numFrames, sampleRate);
296
+ const data = buffer.getChannelData(0);
297
+
298
+ for (let i = 0; i < numFrames; i++) {
299
+ const noise = Math.random() * 2 - 1;
300
+ const env = Math.exp(-i / (sampleRate * 0.1));
301
+ data[i] = noise * env;
302
+ }
303
+
304
+ return buffer;
305
+ }
306
+
307
+ function generateHiHat() {
308
+ const sampleRate = audioContext.sampleRate;
309
+ const duration = 0.1;
310
+ const numFrames = duration * sampleRate;
311
+ const buffer = audioContext.createBuffer(1, numFrames, sampleRate);
312
+ const data = buffer.getChannelData(0);
313
+
314
+ for (let i = 0; i < numFrames; i++) {
315
+ const noise = Math.random() * 2 - 1;
316
+ const env = 1 - (i / numFrames);
317
+ data[i] = noise * env;
318
+ }
319
+
320
+ return buffer;
321
+ }
322
+
323
+ function generateRim() {
324
+ const sampleRate = audioContext.sampleRate;
325
+ const duration = 0.2;
326
+ const numFrames = duration * sampleRate;
327
+ const buffer = audioContext.createBuffer(1, numFrames, sampleRate);
328
+ const data = buffer.getChannelData(0);
329
+
330
+ for (let i = 0; i < numFrames; i++) {
331
+ const n = Math.sin(i / (sampleRate * 0.0005));
332
+ data[i] = n * Math.exp(-i / (sampleRate * 0.05));
333
+ }
334
+
335
+ return buffer;
336
+ }
337
+
338
+ // Play a sound
339
+ function playSound(sound) {
340
+ const source = audioContext.createBufferSource();
341
+ source.buffer = sound.sample;
342
+ source.connect(masterGain);
343
+ source.start();
344
+ }
345
+
346
+ // State variables
347
+ let isPlaying = false;
348
+ let currentStep = 0;
349
+ let timerId = null;
350
+ let bpm = 120;
351
+ let steps = 16;
352
+ let selectedSound = 'bass';
353
+ let pattern = {};
354
+
355
+ // Initialize pattern
356
+ function initPattern() {
357
+ pattern = {};
358
+ Object.keys(sounds).forEach(sound => {
359
+ pattern[sound] = Array(steps).fill(false);
360
+ });
361
+ }
362
+
363
+ initPattern();
364
+
365
+ // DOM elements
366
+ const drumGrid = document.getElementById('drumGrid');
367
+ const playBtn = document.getElementById('playBtn');
368
+ const stopBtn = document.getElementById('stopBtn');
369
+ const bpmSlider = document.getElementById('bpm');
370
+ const bpmValue = document.getElementById('bpmValue');
371
+ const volumeSlider = document.getElementById('volume');
372
+ const volumeValue = document.getElementById('volumeValue');
373
+ const stepsSlider = document.getElementById('steps');
374
+ const stepsValue = document.getElementById('stepsValue');
375
+ const clearBtn = document.getElementById('clearBtn');
376
+ const soundBtns = document.querySelectorAll('.sound-btn');
377
+ const playHead = document.getElementById('playHead');
378
+ const noteVisualization = document.getElementById('noteVisualization');
379
+ const patternSelect = document.getElementById('patternSelect');
380
+ const savePatternBtn = document.getElementById('savePatternBtn');
381
+ const loadPatternBtn = document.getElementById('loadPatternBtn');
382
+
383
+ // Create drum grid
384
+ function createDrumGrid() {
385
+ // Clear existing grid
386
+ drumGrid.querySelector('tbody').innerHTML = '';
387
+
388
+ // Create header with step numbers
389
+ const headerRow = drumGrid.querySelector('thead tr');
390
+ headerRow.innerHTML = '<th class="w-16 h-8 bg-gray-700"></th>';
391
+
392
+ for (let i = 0; i < steps; i++) {
393
+ const th = document.createElement('th');
394
+ th.className = 'w-8 h-8 bg-gray-700 text-xs text-center';
395
+ th.textContent = i + 1;
396
+ headerRow.appendChild(th);
397
+ }
398
+
399
+ // Create rows for each sound
400
+ Object.keys(sounds).forEach(sound => {
401
+ const tr = document.createElement('tr');
402
+
403
+ // Sound name cell
404
+ const th = document.createElement('th');
405
+ th.className = 'w-16 h-8 bg-gray-700 text-xs text-center';
406
+ th.textContent = sounds[sound].name;
407
+ tr.appendChild(th);
408
+
409
+ // Create cells for each step
410
+ for (let i = 0; i < steps; i++) {
411
+ const td = document.createElement('td');
412
+ td.className = 'w-8 h-8 p-0 border border-gray-700';
413
+
414
+ const cell = document.createElement('div');
415
+ cell.className = 'w-full h-full cursor-pointer note hover:bg-gray-600';
416
+ cell.dataset.sound = sound;
417
+ cell.dataset.step = i;
418
+
419
+ if (pattern[sound][i]) {
420
+ cell.classList.add(sounds[sound].color);
421
+ } else {
422
+ cell.classList.add('bg-gray-800');
423
+ }
424
+
425
+ cell.addEventListener('click', function() {
426
+ toggleStep(sound, i);
427
+ });
428
+
429
+ td.appendChild(cell);
430
+ tr.appendChild(td);
431
+ }
432
+
433
+ drumGrid.querySelector('tbody').appendChild(tr);
434
+ });
435
+ }
436
+
437
+ // Toggle step on/off
438
+ function toggleStep(sound, step) {
439
+ pattern[sound][step] = !pattern[sound][step];
440
+ updateGrid();
441
+
442
+ // Play the sound when clicking
443
+ if (pattern[sound][step]) {
444
+ playSound(sounds[sound]);
445
+ }
446
+ }
447
+
448
+ // Update grid display
449
+ function updateGrid() {
450
+ const cells = document.querySelectorAll('#drumGrid .note');
451
+
452
+ cells.forEach(cell => {
453
+ const sound = cell.dataset.sound;
454
+ const step = parseInt(cell.dataset.step);
455
+
456
+ if (pattern[sound][step]) {
457
+ cell.classList.remove('bg-gray-800');
458
+ cell.classList.add(sounds[sound].color);
459
+ } else {
460
+ cell.classList.remove(sounds[sound].color);
461
+ cell.classList.add('bg-gray-800');
462
+ }
463
+ });
464
+
465
+ updateDrumNotation();
466
+ }
467
+
468
+ // Update drum notation display
469
+ function updateDrumNotation() {
470
+ const staff = document.getElementById('drumStaff');
471
+ const cells = document.querySelectorAll('#drumGrid .note');
472
+
473
+ // Clear existing notes and bar lines
474
+ document.querySelectorAll('.drum-note, .note-stem, .bar-line').forEach(el => el.remove());
475
+
476
+ // Add bar lines for each measure (assuming 4/4 time)
477
+ const measures = Math.ceil(steps / 16);
478
+ for (let i = 0; i <= measures; i++) {
479
+ const barLine = document.createElement('div');
480
+ barLine.className = 'bar-line';
481
+ barLine.style.left = `${(i * 100) / measures}%`;
482
+ staff.appendChild(barLine);
483
+ }
484
+
485
+ cells.forEach(cell => {
486
+ const sound = cell.dataset.sound;
487
+ const step = parseInt(cell.dataset.step);
488
+
489
+ if (pattern[sound][step]) {
490
+ cell.classList.remove('bg-gray-800');
491
+ cell.classList.add(sounds[sound].color);
492
+
493
+ // Add drum notation
494
+ const note = document.createElement('div');
495
+ note.className = `drum-note ${sound}`;
496
+ note.style.left = `${(step / steps) * 100}%`;
497
+ staff.appendChild(note);
498
+
499
+ // Add stem based on note duration
500
+ const stem = document.createElement('div');
501
+ stem.className = 'note-stem';
502
+ stem.style.left = `${(step / steps) * 100}%`;
503
+ stem.style.height = '20px';
504
+ stem.style.top = sound === 'hihat' ? '30%' : sound === 'snare' ? '50%' : sound === 'rim' ? '70%' : '90%';
505
+ staff.appendChild(stem);
506
+ } else {
507
+ cell.classList.remove(sounds[sound].color);
508
+ cell.classList.add('bg-gray-800');
509
+ }
510
+ });
511
+ }
512
+
513
+ // Play the sequence
514
+ function playSequence() {
515
+ if (!isPlaying) return;
516
+
517
+ // Highlight current step
518
+ const cells = document.querySelectorAll(`#drumGrid .note[data-step="${currentStep}"]`);
519
+ cells.forEach(cell => {
520
+ cell.classList.add('active');
521
+
522
+ const sound = cell.dataset.sound;
523
+ if (pattern[sound][currentStep]) {
524
+ cell.classList.add('pulse');
525
+ playSound(sounds[sound]);
526
+
527
+ // Add note to visualization
528
+ addNoteToVisualization(sounds[sound].name, currentStep);
529
+ }
530
+
531
+ setTimeout(() => {
532
+ cell.classList.remove('active');
533
+ cell.classList.remove('pulse');
534
+ }, 100);
535
+ });
536
+
537
+ // Move playhead
538
+ const progress = (currentStep / steps) * 100;
539
+ playHead.style.left = `${progress}%`;
540
+
541
+ // Increment step
542
+ currentStep = (currentStep + 1) % steps;
543
+
544
+ // Schedule next step
545
+ const stepDuration = (60 / bpm) * (4 / (steps / 4)) * 1000;
546
+ timerId = setTimeout(playSequence, stepDuration);
547
+ }
548
+
549
+ // Add note to visualization
550
+ function addNoteToVisualization(noteName, step) {
551
+ const noteElement = document.createElement('div');
552
+ noteElement.className = 'inline-block mx-1 text-xs bg-opacity-70 rounded px-2 py-1 mb-1';
553
+
554
+ switch(noteName) {
555
+ case 'Bass':
556
+ noteElement.classList.add('bg-blue-600');
557
+ noteElement.textContent = 'B';
558
+ break;
559
+ case 'Snare':
560
+ noteElement.classList.add('bg-purple-600');
561
+ noteElement.textContent = 'S';
562
+ break;
563
+ case 'Hi-Hat':
564
+ noteElement.classList.add('bg-green-600');
565
+ noteElement.textContent = 'H';
566
+ break;
567
+ case 'Rim':
568
+ noteElement.classList.add('bg-yellow-600');
569
+ noteElement.textContent = 'R';
570
+ break;
571
+ }
572
+
573
+ noteVisualization.appendChild(noteElement);
574
+
575
+ // Limit number of notes shown
576
+ if (noteVisualization.children.length > 20) {
577
+ noteVisualization.removeChild(noteVisualization.children[0]);
578
+ }
579
+ }
580
+
581
+ // Start playback
582
+ function startPlayback() {
583
+ if (isPlaying) return;
584
+
585
+ isPlaying = true;
586
+ currentStep = 0;
587
+ playBtn.disabled = true;
588
+ stopBtn.disabled = false;
589
+ playHead.style.display = 'block';
590
+ noteVisualization.innerHTML = '';
591
+
592
+ // Calculate animation duration based on BPM
593
+ const loopDuration = (60 / bpm) * 4 * 1000; // 4 beats per measure
594
+ playHead.style.setProperty('--bpm', `${loopDuration}ms`);
595
+ playHead.style.animationDuration = `${loopDuration}ms`;
596
+
597
+ // Start audio context if suspended
598
+ if (audioContext.state === 'suspended') {
599
+ audioContext.resume();
600
+ }
601
+
602
+ playSequence();
603
+ }
604
+
605
+ // Stop playback
606
+ function stopPlayback() {
607
+ isPlaying = false;
608
+ clearTimeout(timerId);
609
+ playBtn.disabled = false;
610
+ stopBtn.disabled = true;
611
+ playHead.style.display = 'none';
612
+
613
+ // Remove active classes
614
+ document.querySelectorAll('#drumGrid .note').forEach(cell => {
615
+ cell.classList.remove('active');
616
+ cell.classList.remove('pulse');
617
+ });
618
+ }
619
+
620
+ // Clear all steps
621
+ function clearAll() {
622
+ if (confirm('Are you sure you want to clear all steps?')) {
623
+ initPattern();
624
+ updateGrid();
625
+ }
626
+ }
627
+
628
+ // Load predefined pattern
629
+ function loadPattern(patternName) {
630
+ initPattern();
631
+
632
+ switch(patternName) {
633
+ case 'basic':
634
+ // Basic rock pattern
635
+ for (let i = 0; i < steps; i++) {
636
+ // Hi-hat on every step
637
+ pattern.hihat[i] = true;
638
+
639
+ // Bass drum on 1 and 3
640
+ if (i % (steps / 4) === 0) {
641
+ pattern.bass[i] = true;
642
+ }
643
+
644
+ // Snare on 2 and 4
645
+ if (i % (steps / 2) === (steps / 4)) {
646
+ pattern.snare[i] = true;
647
+ }
648
+ }
649
+ break;
650
+
651
+ case 'complex':
652
+ // More complex pattern
653
+ for (let i = 0; i < steps; i++) {
654
+ // Hi-hat
655
+ pattern.hihat[i] = true;
656
+
657
+ // Bass drum
658
+ if (i % (steps / 4) === 0 || (i % (steps / 8) === (steps / 16))) {
659
+ pattern.bass[i] = true;
660
+ }
661
+
662
+ // Snare
663
+ if (i % (steps / 2) === (steps / 4) || (i % (steps / 4) === (steps / 8))) {
664
+ pattern.snare[i] = true;
665
+ }
666
+
667
+ // Rim
668
+ if (i % (steps / 8) === (steps / 16) && !pattern.snare[i]) {
669
+ pattern.rim[i] = true;
670
+ }
671
+ }
672
+ break;
673
+
674
+ case 'random':
675
+ // Random pattern
676
+ for (let sound in pattern) {
677
+ for (let i = 0; i < steps; i++) {
678
+ pattern[sound][i] = Math.random() > 0.7;
679
+ }
680
+ }
681
+ break;
682
+ }
683
+
684
+ updateGrid();
685
+ }
686
+
687
+ // Save current pattern
688
+ function savePattern() {
689
+ const patternName = prompt('Enter a name for this pattern:');
690
+ if (patternName) {
691
+ localStorage.setItem(`drumMachinePattern_${patternName}`, JSON.stringify(pattern));
692
+ alert(`Pattern "${patternName}" saved!`);
693
+ }
694
+ }
695
+
696
+ // Load saved pattern
697
+ function loadSavedPattern() {
698
+ const patternName = prompt('Enter the name of the pattern to load:');
699
+ if (patternName) {
700
+ const savedPattern = localStorage.getItem(`drumMachinePattern_${patternName}`);
701
+ if (savedPattern) {
702
+ pattern = JSON.parse(savedPattern);
703
+ steps = pattern.bass.length;
704
+ stepsSlider.value = steps;
705
+ stepsValue.textContent = steps;
706
+ createDrumGrid();
707
+ updateGrid();
708
+ alert(`Pattern "${patternName}" loaded!`);
709
+ } else {
710
+ alert(`Pattern "${patternName}" not found!`);
711
+ }
712
+ }
713
+ }
714
+
715
+ // Event listeners
716
+ playBtn.addEventListener('click', startPlayback);
717
+ stopBtn.addEventListener('click', stopPlayback);
718
+
719
+ bpmSlider.addEventListener('input', function() {
720
+ bpm = parseInt(this.value);
721
+ bpmValue.textContent = bpm;
722
+
723
+ if (isPlaying) {
724
+ stopPlayback();
725
+ startPlayback();
726
+ }
727
+ });
728
+
729
+ volumeSlider.addEventListener('input', function() {
730
+ const volume = parseInt(this.value) / 100;
731
+ volumeValue.textContent = this.value;
732
+ masterGain.gain.value = volume;
733
+ });
734
+
735
+ stepsSlider.addEventListener('input', function() {
736
+ steps = parseInt(this.value);
737
+ stepsValue.textContent = steps;
738
+ initPattern();
739
+ createDrumGrid();
740
+ });
741
+
742
+ clearBtn.addEventListener('click', clearAll);
743
+
744
+ soundBtns.forEach(btn => {
745
+ btn.addEventListener('click', function() {
746
+ selectedSound = this.dataset.sound;
747
+ soundBtns.forEach(b => b.classList.remove('ring-2', 'ring-yellow-400'));
748
+ this.classList.add('ring-2', 'ring-yellow-400');
749
+ playSound(sounds[selectedSound]);
750
+ });
751
+ });
752
+
753
+ // Select first sound by default
754
+ soundBtns[0].classList.add('ring-2', 'ring-yellow-400');
755
+
756
+ patternSelect.addEventListener('change', function() {
757
+ if (this.value !== 'empty') {
758
+ loadPattern(this.value);
759
+ }
760
+ });
761
+
762
+ savePatternBtn.addEventListener('click', savePattern);
763
+ loadPatternBtn.addEventListener('click', loadSavedPattern);
764
+
765
+ // Set note duration style
766
+ function setNoteDuration(noteElement, duration) {
767
+ switch(duration) {
768
+ case 'quarter':
769
+ // Single note head with stem
770
+ break;
771
+ case 'eighth':
772
+ // Add flag
773
+ noteElement.style.borderRight = '6px solid transparent';
774
+ noteElement.style.borderLeft = '6px solid transparent';
775
+ noteElement.style.borderBottom = '6px solid white';
776
+ noteElement.style.borderRadius = '0';
777
+ break;
778
+ case 'sixteenth':
779
+ // Double flag
780
+ noteElement.style.borderRight = '6px solid transparent';
781
+ noteElement.style.borderLeft = '6px solid transparent';
782
+ noteElement.style.borderBottom = '6px solid white';
783
+ noteElement.style.borderRadius = '0';
784
+ noteElement.style.boxShadow = '10px 0 0 0 white';
785
+ break;
786
+ }
787
+ }
788
+
789
+ // Initialize
790
+ createDrumGrid();
791
+ updateGrid();
792
+
793
+ // Keyboard shortcuts
794
+ document.addEventListener('keydown', function(e) {
795
+ if (e.code === 'Space') {
796
+ e.preventDefault();
797
+ if (isPlaying) {
798
+ stopPlayback();
799
+ } else {
800
+ startPlayback();
801
+ }
802
+ }
803
+
804
+ // Number keys for sounds
805
+ if (e.code === 'Digit1') {
806
+ document.querySelector('[data-sound="bass"]').click();
807
+ } else if (e.code === 'Digit2') {
808
+ document.querySelector('[data-sound="snare"]').click();
809
+ } else if (e.code === 'Digit3') {
810
+ document.querySelector('[data-sound="hihat"]').click();
811
+ } else if (e.code === 'Digit4') {
812
+ document.querySelector('[data-sound="rim"]').click();
813
+ }
814
+ });
815
+ });
816
+ </script>
817
+ <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=IgorSlinko/drum-machine" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
818
+ </html>