drxzh commited on
Commit
b523dc2
·
verified ·
1 Parent(s): 3045294

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +640 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Drums
3
- emoji: 🚀
4
- colorFrom: pink
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: drums
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: blue
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,640 @@
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>CRYSTAL DRUMS | MIDI Pattern Generator</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 src="https://cdn.jsdelivr.net/npm/tone@14.7.77/build/Tone.min.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/@magenta/music@1.23.1/es6/core.js"></script>
11
+ <style>
12
+ @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&family=Syncopate:wght@400;700&display=swap');
13
+
14
+ body {
15
+ font-family: 'Montserrat', sans-serif;
16
+ background-color: #0f0f12;
17
+ color: #e0e0e0;
18
+ min-height: 100vh;
19
+ }
20
+
21
+ .title-font {
22
+ font-family: 'Syncopate', sans-serif;
23
+ text-transform: uppercase;
24
+ }
25
+
26
+ .glitch-box {
27
+ position: relative;
28
+ overflow: hidden;
29
+ }
30
+
31
+ .glitch-box::before {
32
+ content: "";
33
+ position: absolute;
34
+ top: 0;
35
+ left: 0;
36
+ width: 100%;
37
+ height: 100%;
38
+ background: linear-gradient(
39
+ 90deg,
40
+ transparent 0%,
41
+ rgba(255, 255, 255, 0.03) 20%,
42
+ rgba(255, 255, 255, 0.05) 50%,
43
+ rgba(255, 255, 255, 0.03) 80%,
44
+ transparent 100%
45
+ );
46
+ z-index: 1;
47
+ pointer-events: none;
48
+ animation: glitch 5s infinite alternate;
49
+ }
50
+
51
+ @keyframes glitch {
52
+ 0% { transform: translateX(-100%); }
53
+ 15% { transform: translateX(0); opacity: 1; }
54
+ 30% { opacity: 0.3; }
55
+ 45% { opacity: 1; }
56
+ 100% { transform: translateX(100%); }
57
+ }
58
+
59
+ .pixel-grid {
60
+ display: grid;
61
+ gap: 2px;
62
+ }
63
+
64
+ .pixel {
65
+ width: 100%;
66
+ aspect-ratio: 1;
67
+ background-color: #1e1e25;
68
+ transition: all 0.1s ease;
69
+ position: relative;
70
+ overflow: hidden;
71
+ }
72
+
73
+ .pixel::before {
74
+ content: "";
75
+ position: absolute;
76
+ top: 0;
77
+ left: 0;
78
+ width: 100%;
79
+ height: 100%;
80
+ background: radial-gradient(circle, transparent 0%, rgba(0,0,0,0.3) 100%);
81
+ }
82
+
83
+ .pixel.active {
84
+ background-color: #7b2dff;
85
+ box-shadow: 0 0 10px #7b2dffaa;
86
+ }
87
+
88
+ .pixel.active.kick {
89
+ background-color: #ff2d6d;
90
+ box-shadow: 0 0 10px #ff2d6daa;
91
+ }
92
+
93
+ .pixel.active.snare {
94
+ background-color: #2dfff3;
95
+ box-shadow: 0 0 10px #2dfff3aa;
96
+ }
97
+
98
+ .pixel.active.hihat {
99
+ background-color: #f3ff2d;
100
+ box-shadow: 0 0 10px #f3ff2daa;
101
+ }
102
+
103
+ .pixel.active.perc {
104
+ background-color: #2dff88;
105
+ box-shadow: 0 0 10px #2dff88aa;
106
+ }
107
+
108
+ .vhs-effect {
109
+ position: relative;
110
+ overflow: hidden;
111
+ }
112
+
113
+ .vhs-effect::after {
114
+ content: "";
115
+ position: absolute;
116
+ top: 0;
117
+ left: 0;
118
+ width: 100%;
119
+ height: 100%;
120
+ background: linear-gradient(0deg, transparent 0%, rgba(255,255,255,0.01) 1%, transparent 2%, transparent 100%);
121
+ pointer-events: none;
122
+ animation: vhsScan 0.1s infinite;
123
+ }
124
+
125
+ @keyframes vhsScan {
126
+ 0% { transform: translateY(-100%); }
127
+ 100% { transform: translateY(100%); }
128
+ }
129
+
130
+ .grid-line {
131
+ position: absolute;
132
+ top: 0;
133
+ height: 100%;
134
+ width: 0.5px;
135
+ background-color: rgba(255,255,255,0.05);
136
+ pointer-events: none;
137
+ }
138
+ </style>
139
+ </head>
140
+ <body class="bg-gray-900">
141
+ <!-- VHS overlay -->
142
+ <div class="fixed inset-0 vhs-effect pointer-events-none z-50 opacity-20"></div>
143
+
144
+ <header class="bg-black bg-opacity-80 border-b border-gray-800 backdrop-blur-md sticky top-0 z-40">
145
+ <div class="container mx-auto px-4 py-4">
146
+ <div class="flex justify-between items-center">
147
+ <div class="flex items-center space-x-3">
148
+ <i class="fas fa-drum text-2xl text-purple-500"></i>
149
+ <h1 class="text-xl md:text-2xl font-bold title-font text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-500 tracking-wider">
150
+ CRYSTAL DRUMS
151
+ </h1>
152
+ </div>
153
+ <div class="flex space-x-3">
154
+ <button id="infoBtn" class="bg-gray-800 hover:bg-gray-700 text-gray-300 px-3 py-1 rounded-md text-sm font-medium transition-colors">
155
+ <i class="fas fa-question-circle mr-1"></i> Info
156
+ </button>
157
+ <button id="randomizeBtn" class="bg-purple-900 hover:bg-purple-800 text-purple-100 px-3 py-1 rounded-md text-sm font-medium transition-colors">
158
+ <i class="fas fa-random mr-1"></i> Randomize
159
+ </button>
160
+ </div>
161
+ </div>
162
+ </div>
163
+ </header>
164
+
165
+ <main class="container mx-auto px-4 py-8">
166
+ <div class="max-w-6xl mx-auto">
167
+ <div class="glitch-box bg-gray-900 border border-gray-800 rounded-lg p-6 mb-8">
168
+ <h2 class="text-lg font-semibold text-pink-400 mb-4">
169
+ <i class="fas fa-sliders-h mr-2"></i> CRYSTAL SOUNDS
170
+ </h2>
171
+
172
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
173
+ <div class="space-y-2">
174
+ <label class="block text-xs font-medium text-gray-400">CRUNCH</label>
175
+ <input type="range" min="0" max="100" value="70" class="w-full h-1 bg-gray-700 rounded-lg appearance-none cursor-pointer accent-purple-500">
176
+ </div>
177
+ <div class="space-y-2">
178
+ <label class="block text-xs font-medium text-gray-400">PITCH</label>
179
+ <input type="range" min="-12" max="12" value="-3" class="w-full h-1 bg-gray-700 rounded-lg appearance-none cursor-pointer accent-pink-500">
180
+ </div>
181
+ <div class="space-y-2">
182
+ <label class="block text-xs font-medium text-gray-400">STUTTER</label>
183
+ <input type="range" min="0" max="100" value="15" class="w-full h-1 bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-400">
184
+ </div>
185
+ <div class="space-y-2">
186
+ <label class="block text-xs font-medium text-gray-400">TAPE HISS</label>
187
+ <input type="range" min="0" max="100" value="30" class="w-full h-1 bg-gray-700 rounded-lg appearance-none cursor-pointer accent-green-400">
188
+ </div>
189
+ </div>
190
+
191
+ <div class="grid grid-cols-4 gap-4">
192
+ <div class="space-y-2">
193
+ <div class="flex justify-between items-center">
194
+ <label class="text-xs font-medium text-gray-400">BPM</label>
195
+ <span class="text-xs font-mono text-gray-300">140</span>
196
+ </div>
197
+ <input type="range" min="80" max="200" value="140" id="bpmSlider" class="w-full h-1 bg-gray-700 rounded-lg appearance-none cursor-pointer accent-yellow-400">
198
+ </div>
199
+ <div class="space-y-2">
200
+ <div class="flex justify-between items-center">
201
+ <label class="text-xs font-medium text-gray-400">STEPS</label>
202
+ <span class="text-xs font-mono text-gray-300">16</span>
203
+ </div>
204
+ <input type="range" min="8" max="32" value="16" step="8" id="stepsSlider" class="w-full h-1 bg-gray-700 rounded-lg appearance-none cursor-pointer accent-red-400">
205
+ </div>
206
+ <div class="space-y-2">
207
+ <label class="block text-xs font-medium text-gray-400">SWING</label>
208
+ <input type="range" min="0" max="50" value="5" class="w-full h-1 bg-gray-700 rounded-lg appearance-none cursor-pointer accent-teal-400">
209
+ </div>
210
+ <div class="space-y-2">
211
+ <label class="block text-xs font-medium text-gray-400">VOLUME</label>
212
+ <input type="range" min="0" max="100" value="70" class="w-full h-1 bg-gray-700 rounded-lg appearance-none cursor-pointer accent-gray-400">
213
+ </div>
214
+ </div>
215
+ </div>
216
+
217
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
218
+ <div class="glitch-box bg-gray-900 border border-gray-800 rounded-lg p-6">
219
+ <h3 class="text-lg font-semibold text-pink-400 mb-4">
220
+ <i class="fas fa-music mr-2"></i> DRUM GRID
221
+ </h3>
222
+
223
+ <div id="drumGridContainer" class="relative">
224
+ <!-- Grid lines will be added via JS -->
225
+ <div id="drumGrid" class="pixel-grid grid grid-rows-4 grid-cols-16 gap-0.5"></div>
226
+ </div>
227
+
228
+ <div class="flex justify-between mt-4 text-xs text-gray-400">
229
+ <span>KICK</span>
230
+ <span>SNARE</span>
231
+ <span>HIHAT</span>
232
+ <span>PERC</span>
233
+ </div>
234
+ </div>
235
+
236
+ <div class="glitch-box bg-gray-900 border border-gray-800 rounded-lg p-6">
237
+ <h3 class="text-lg font-semibold text-blue-400 mb-4">
238
+ <i class="fas fa-compact-disc mr-2"></i> PATTERN PLAYER
239
+ </h3>
240
+
241
+ <div class="flex items-center space-x-4 mb-6">
242
+ <button id="playBtn" class="bg-purple-600 hover:bg-purple-500 text-white px-6 py-2 rounded-md font-medium transition-colors">
243
+ <i class="fas fa-play mr-2"></i> Play
244
+ </button>
245
+ <button id="stopBtn" class="bg-gray-700 hover:bg-gray-600 text-gray-200 px-4 py-2 rounded-md font-medium transition-colors">
246
+ <i class="fas fa-stop mr-2"></i> Stop
247
+ </button>
248
+ <button id="exportBtn" class="bg-pink-600 hover:bg-pink-500 text-white px-4 py-2 rounded-md font-medium transition-colors">
249
+ <i class="fas fa-download mr-2"></i> Export MIDI
250
+ </button>
251
+ </div>
252
+
253
+ <div class="space-y-4">
254
+ <div class="flex items-center">
255
+ <div class="w-2 h-2 rounded-full bg-purple-500 mr-2"></div>
256
+ <div class="flex-grow h-2 bg-gray-700 rounded-full">
257
+ <div id="playhead" class="h-2 bg-purple-400 rounded-full w-0"></div>
258
+ </div>
259
+ </div>
260
+ <div id="patternInfo" class="text-sm text-gray-400 font-mono">
261
+ <p>Pattern: <span class="text-gray-300">unmodified</span></p>
262
+ <p>BPM: <span class="text-gray-300">140</span></p>
263
+ <p>Steps: <span class="text-gray-300">16</span></p>
264
+ </div>
265
+ </div>
266
+ </div>
267
+ </div>
268
+
269
+ <div class="glitch-box bg-gray-900 border border-gray-800 rounded-lg p-6">
270
+ <h3 class="text-lg font-semibold text-yellow-400 mb-4">
271
+ <i class="fas fa-flask mr-2"></i> CRYSTAL LAB
272
+ </h3>
273
+
274
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
275
+ <button class="bg-gray-800 hover:bg-gray-700 text-gray-300 px-4 py-2 rounded-md flex items-center justify-center space-x-2">
276
+ <i class="fas fa-trash"></i>
277
+ <span>Clear All</span>
278
+ </button>
279
+ <button class="bg-gray-800 hover:bg-gray-700 text-gray-300 px-4 py-2 rounded-md flex items-center justify-center space-x-2">
280
+ <i class="fas fa-random"></i>
281
+ <span>Random Fill</span>
282
+ </button>
283
+ <button class="bg-gray-800 hover:bg-gray-700 text-gray-300 px-4 py-2 rounded-md flex items-center justify-center space-x-2">
284
+ <i class="fas fa-undo"></i>
285
+ <span>Undo</span>
286
+ </button>
287
+ <button class="bg-gray-800 hover:bg-gray-700 text-gray-300 px-4 py-2 rounded-md flex items-center justify-center space-x-2">
288
+ <i class="fas fa-redo"></i>
289
+ <span>Redo</span>
290
+ </button>
291
+ </div>
292
+
293
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
294
+ <button class="bg-purple-900 hover:bg-purple-800 text-purple-100 px-4 py-2 rounded-md flex items-center justify-center space-x-2">
295
+ <i class="fas fa-bolt"></i>
296
+ <span>Glitch Pattern</span>
297
+ </button>
298
+ <button class="bg-pink-900 hover:bg-pink-800 text-pink-100 px-4 py-2 rounded-md flex items-center justify-center space-x-2">
299
+ <i class="fas fa-forward"></i>
300
+ <span>Shift Right</span>
301
+ </button>
302
+ <button class="bg-blue-900 hover:bg-blue-800 text-blue-100 px-4 py-2 rounded-md flex items-center justify-center space-x-2">
303
+ <i class="fas fa-backward"></i>
304
+ <span>Shift Left</span>
305
+ </button>
306
+ <button class="bg-green-900 hover:bg-green-800 text-green-100 px-4 py-2 rounded-md flex items-center justify-center space-x-2">
307
+ <i class="fas fa-expand"></i>
308
+ <span>Double Time</span>
309
+ </button>
310
+ </div>
311
+ </div>
312
+ </div>
313
+ </main>
314
+
315
+ <footer class="bg-black bg-opacity-80 border-t border-gray-800 py-6 mt-10">
316
+ <div class="container mx-auto px-4">
317
+ <div class="flex flex-col md:flex-row justify-between items-center">
318
+ <div class="mb-4 md:mb-0">
319
+ <div class="flex items-center space-x-2">
320
+ <i class="fas fa-drum text-xl text-purple-500"></i>
321
+ <h2 class="text-xl font-bold title-font tracking-wider text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-500">
322
+ CRYSTAL DRUMS
323
+ </h2>
324
+ </div>
325
+ <p class="text-gray-500 mt-2 text-xs">Inspired by the chaotic beauty of Crystal Castles</p>
326
+ </div>
327
+ <div class="flex space-x-4">
328
+ <a href="#" class="text-gray-500 hover:text-purple-400 transition-colors">
329
+ <i class="fab fa-github"></i>
330
+ </a>
331
+ <a href="#" class="text-gray-500 hover:text-pink-400 transition-colors">
332
+ <i class="fab fa-twitter"></i>
333
+ </a>
334
+ <a href="#" class="text-gray-500 hover:text-blue-400 transition-colors">
335
+ <i class="fas fa-envelope"></i>
336
+ </a>
337
+ </div>
338
+ </div>
339
+ <div class="border-t border-gray-800 mt-6 pt-6 text-center text-gray-600 text-xs">
340
+ <p>© 2023 CRYSTAL DRUMS. Not affiliated with Crystal Castles.</p>
341
+ </div>
342
+ </div>
343
+ </footer>
344
+
345
+ <script>
346
+ // Initialize Tone.js
347
+ const sampler = new Tone.Sampler({
348
+ urls: {
349
+ A1: "kick.wav",
350
+ A2: "snare.wav",
351
+ A3: "hihat.wav",
352
+ A4: "perc.wav"
353
+ },
354
+ release: 1,
355
+ baseUrl: "https://tonejs.github.io/audio/drum-samples/",
356
+ }).toDestination();
357
+
358
+ // Constants
359
+ const DRUM_ROWS = 4;
360
+ let currentCols = 16;
361
+ let bpm = 140;
362
+ let isPlaying = false;
363
+ let playheadInterval;
364
+ let currentStep = 0;
365
+ let patternData = [];
366
+
367
+ // DOM Elements
368
+ const drumGrid = document.getElementById('drumGrid');
369
+ const drumGridContainer = document.getElementById('drumGridContainer');
370
+ const playBtn = document.getElementById('playBtn');
371
+ const stopBtn = document.getElementById('stopBtn');
372
+ const exportBtn = document.getElementById('exportBtn');
373
+ const randomizeBtn = document.getElementById('randomizeBtn');
374
+ const infoBtn = document.getElementById('infoBtn');
375
+ const playhead = document.getElementById('playhead');
376
+ const patternInfo = document.getElementById('patternInfo');
377
+ const bpmSlider = document.getElementById('bpmSlider');
378
+ const stepsSlider = document.getElementById('stepsSlider');
379
+
380
+ // Create the drum grid
381
+ function createDrumGrid(rows, cols) {
382
+ drumGrid.innerHTML = '';
383
+
384
+ // Clear existing grid lines
385
+ const oldLines = document.querySelectorAll('.grid-line');
386
+ oldLines.forEach(line => line.remove());
387
+
388
+ // Add vertical grid lines
389
+ for (let i = 0; i <= cols; i++) {
390
+ const line = document.createElement('div');
391
+ line.className = 'grid-line';
392
+ line.style.left = `${(i / cols) * 100}%`;
393
+ drumGridContainer.appendChild(line);
394
+ }
395
+
396
+ // Create grid cells (pixels)
397
+ for (let row = 0; row < rows; row++) {
398
+ for (let col = 0; col < cols; col++) {
399
+ const pixel = document.createElement('div');
400
+ pixel.className = 'pixel';
401
+
402
+ if (row === 0) pixel.classList.add('kick-cell');
403
+ if (row === 1) pixel.classList.add('snare-cell');
404
+ if (row === 2) pixel.classList.add('hihat-cell');
405
+ if (row === 3) pixel.classList.add('perc-cell');
406
+
407
+ pixel.dataset.row = row;
408
+ pixel.dataset.col = col;
409
+
410
+ pixel.addEventListener('click', togglePixel);
411
+ drumGrid.appendChild(pixel);
412
+ }
413
+ }
414
+
415
+ // Initialize empty pattern data
416
+ patternData = Array.from({ length: rows }, () =>
417
+ Array.from({ length: cols }, () => false)
418
+ );
419
+
420
+ // Update grid layout
421
+ drumGrid.style.gridTemplateRows = `repeat(${rows}, 1fr)`;
422
+ drumGrid.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
423
+ }
424
+
425
+ // Toggle pixel state
426
+ function togglePixel(e) {
427
+ const pixel = e.target;
428
+ const row = parseInt(pixel.dataset.row);
429
+ const col = parseInt(pixel.dataset.col);
430
+
431
+ // Toggle the pixel state
432
+ patternData[row][col] = !patternData[row][col];
433
+
434
+ // Update visual state
435
+ pixel.classList.toggle('active');
436
+
437
+ // Add color class based on row (drum type)
438
+ if (row === 0) pixel.classList.toggle('kick');
439
+ if (row === 1) pixel.classList.toggle('snare');
440
+ if (row === 2) pixel.classList.toggle('hihat');
441
+ if (row === 3) pixel.classList.toggle('perc');
442
+ }
443
+
444
+ // Randomize the pattern
445
+ function randomizePattern() {
446
+ patternData = patternData.map(row =>
447
+ row.map(() => Math.random() < 0.2)
448
+ );
449
+
450
+ updateGridVisuals();
451
+ }
452
+
453
+ // Update grid visuals based on pattern data
454
+ function updateGridVisuals() {
455
+ const pixels = document.querySelectorAll('.pixel');
456
+ pixels.forEach(pixel => {
457
+ const row = parseInt(pixel.dataset.row);
458
+ const col = parseInt(pixel.dataset.col);
459
+
460
+ pixel.classList.remove('active', 'kick', 'snare', 'hihat', 'perc');
461
+
462
+ if (patternData[row][col]) {
463
+ pixel.classList.add('active');
464
+ if (row === 0) pixel.classList.add('kick');
465
+ if (row === 1) pixel.classList.add('snare');
466
+ if (row === 2) pixel.classList.add('hihat');
467
+ if (row === 3) pixel.classList.add('perc');
468
+ }
469
+ });
470
+ }
471
+
472
+ // Play the pattern
473
+ function playPattern() {
474
+ if (isPlaying) return;
475
+
476
+ isPlaying = true;
477
+ Tone.start();
478
+ Tone.getDestination().volume.value = -10;
479
+
480
+ const stepInterval = 60 / bpm / 4; // quarter notes
481
+ let step = 0;
482
+
483
+ const loop = new Tone.Loop((time) => {
484
+ currentStep = step;
485
+
486
+ // Update playhead position
487
+ const playheadPos = (step / currentCols) * 100;
488
+ playhead.style.width = `${playheadPos}%`;
489
+
490
+ // Play each row that has a hit at this step
491
+ for (let row = 0; row < DRUM_ROWS; row++) {
492
+ if (patternData[row][step]) {
493
+ playDrum(row, time);
494
+ }
495
+ }
496
+
497
+ step = (step + 1) % currentCols;
498
+ }, stepInterval).start(0);
499
+
500
+ Tone.Transport.bpm.value = bpm;
501
+ Tone.Transport.start();
502
+
503
+ playBtn.innerHTML = '<i class="fas fa-pause mr-2"></i> Pause';
504
+ playBtn.classList.remove('bg-purple-600', 'hover:bg-purple-500');
505
+ playBtn.classList.add('bg-yellow-600', 'hover:bg-yellow-500');
506
+ }
507
+
508
+ // Play drum sound based on row
509
+ function playDrum(row, time) {
510
+ let note;
511
+ switch (row) {
512
+ case 0: note = "A1"; break; // Kick
513
+ case 1: note = "A2"; break; // Snare
514
+ case 2: note = "A3"; break; // Hihat
515
+ case 3: note = "A4"; break; // Perc
516
+ }
517
+
518
+ // Add some random pitch variation to humanize
519
+ const pitchVariation = (Math.random() * 0.2) - 0.1;
520
+ sampler.triggerAttackRelease(note, "8n", time, 0.3 + pitchVariation);
521
+ }
522
+
523
+ // Stop playback
524
+ function stopPlayback() {
525
+ if (!isPlaying) return;
526
+
527
+ isPlaying = false;
528
+ Tone.Transport.stop();
529
+ currentStep = 0;
530
+ playhead.style.width = '0%';
531
+
532
+ playBtn.innerHTML = '<i class="fas fa-play mr-2"></i> Play';
533
+ playBtn.classList.remove('bg-yellow-600', 'hover:bg-yellow-500');
534
+ playBtn.classList.add('bg-purple-600', 'hover:bg-purple-500');
535
+ }
536
+
537
+ // Export as MIDI
538
+ function exportMIDI() {
539
+ // Convert pattern to Magenta.js NoteSequence
540
+ const sequence = {
541
+ notes: [],
542
+ quantizationInfo: { stepsPerQuarter: 4 },
543
+ totalQuantizedSteps: currentCols,
544
+ timeSignatures: [{ time: 4, numerator: 4, denominator: 4 }],
545
+ tempos: [{ qpm: bpm }],
546
+ ticksPerQuarter: 220,
547
+ };
548
+
549
+ // Add drum hits
550
+ for (let step = 0; step < currentCols; step++) {
551
+ for (let row = 0; row < DRUM_ROWS; row++) {
552
+ if (patternData[row][step]) {
553
+ let pitch;
554
+ let instrument = 0;
555
+
556
+ switch (row) {
557
+ case 0: pitch = 36; instrument = 1; break; // Kick
558
+ case 1: pitch = 38; instrument = 2; break; // Snare
559
+ case 2: pitch = 42; instrument = 3; break; // Hihat
560
+ case 3: pitch = 45; instrument = 4; break; // Perc
561
+ }
562
+
563
+ sequence.notes.push({
564
+ pitch: pitch,
565
+ quantizedStartStep: step,
566
+ quantizedEndStep: step + 1,
567
+ instrument: instrument,
568
+ isDrum: true,
569
+ velocity: 80
570
+ });
571
+ }
572
+ }
573
+ }
574
+
575
+ // Convert to MIDI and trigger download
576
+ const midiBytes = core.sequenceProtoToMidi(sequence);
577
+ const blob = new Blob([midiBytes], { type: 'audio/midi' });
578
+ const url = URL.createObjectURL(blob);
579
+
580
+ const a = document.createElement('a');
581
+ a.href = url;
582
+ a.download = `crystal-drums-${Date.now()}.mid`;
583
+ document.body.appendChild(a);
584
+ a.click();
585
+
586
+ setTimeout(() => {
587
+ document.body.removeChild(a);
588
+ URL.revokeObjectURL(url);
589
+ }, 100);
590
+ }
591
+
592
+ // Event listeners
593
+ playBtn.addEventListener('click', () => {
594
+ if (isPlaying) {
595
+ stopPlayback();
596
+ } else {
597
+ playPattern();
598
+ }
599
+ });
600
+
601
+ stopBtn.addEventListener('click', stopPlayback);
602
+ exportBtn.addEventListener('click', exportMIDI);
603
+ randomizeBtn.addEventListener('click', randomizePattern);
604
+
605
+ infoBtn.addEventListener('click', () => {
606
+ alert("CRYSTAL DRUMS is a MIDI drum pattern generator inspired by the raw, glitchy beats of Crystal Castles.\n\nCreate patterns by clicking on the grid, then play them back with randomized pitch and timing for that authentic lo-fi sound.\n\nExport your patterns as MIDI files to use in your DAW.");
607
+ });
608
+
609
+ bpmSlider.addEventListener('input', (e) => {
610
+ bpm = parseInt(e.target.value);
611
+ updateBpmDisplay();
612
+ });
613
+
614
+ stepsSlider.addEventListener('input', (e) => {
615
+ currentCols = parseInt(e.target.value);
616
+ createDrumGrid(DRUM_ROWS, currentCols);
617
+ updateStepDisplay();
618
+ });
619
+
620
+ // Update displayed BPM
621
+ function updateBpmDisplay() {
622
+ const bpmDisplay = patternInfo.querySelector('p:nth-child(2) span');
623
+ bpmDisplay.textContent = bpm;
624
+ }
625
+
626
+ // Update displayed steps
627
+ function updateStepDisplay() {
628
+ const stepsDisplay = patternInfo.querySelector('p:nth-child(3) span');
629
+ stepsDisplay.textContent = currentCols;
630
+ }
631
+
632
+ // Initialize
633
+ document.addEventListener('DOMContentLoaded', () => {
634
+ createDrumGrid(DRUM_ROWS, currentCols);
635
+ updateBpmDisplay();
636
+ updateStepDisplay();
637
+ });
638
+ </script>
639
+ <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=drxzh/drums" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
640
+ </html>