lonestar108 commited on
Commit
d839c0b
·
verified ·
1 Parent(s): ea26ebf

A responsive and dynamic musical surface

Browse files
Files changed (2) hide show
  1. README.md +7 -4
  2. index.html +365 -18
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
  title: Sonic Canvas
3
- emoji: 🏃
4
- colorFrom: pink
5
- colorTo: red
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: Sonic Canvas
3
+ colorFrom: red
4
+ colorTo: yellow
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://deepsite.hf.co).
index.html CHANGED
@@ -1,19 +1,366 @@
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>Sonic Canvas</title>
7
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://unpkg.com/feather-icons"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.waves.min.js"></script>
11
+ <style>
12
+ .note-cell {
13
+ transition: all 0.2s ease;
14
+ cursor: pointer;
15
+ }
16
+ .note-cell:hover {
17
+ transform: scale(1.1);
18
+ z-index: 10;
19
+ }
20
+ .active-note {
21
+ animation: pulse 0.5s ease-in-out;
22
+ }
23
+ @keyframes pulse {
24
+ 0% { transform: scale(1); }
25
+ 50% { transform: scale(1.3); }
26
+ 100% { transform: scale(1); }
27
+ }
28
+ .keyboard-key {
29
+ transition: all 0.1s ease;
30
+ }
31
+ .keyboard-key.active {
32
+ transform: translateY(5px);
33
+ box-shadow: inset 0 4px 8px rgba(0,0,0,0.2);
34
+ }
35
+ </style>
36
+ </head>
37
+ <body class="bg-gray-900 text-white overflow-hidden">
38
+ <!-- Header -->
39
+ <header class="absolute top-0 left-0 right-0 z-50 p-4 flex justify-between items-center">
40
+ <div class="flex items-center space-x-2">
41
+ <i data-feather="music" class="text-indigo-400"></i>
42
+ <h1 class="text-2xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-purple-400 to-indigo-300">Sonic Canvas</h1>
43
+ </div>
44
+ <div class="flex space-x-4">
45
+ <button id="playBtn" class="px-4 py-2 bg-indigo-600 rounded-lg hover:bg-indigo-700 transition flex items-center">
46
+ <i data-feather="play" class="mr-2"></i> Play
47
+ </button>
48
+ <button id="clearBtn" class="px-4 py-2 bg-purple-600 rounded-lg hover:bg-purple-700 transition flex items-center">
49
+ <i data-feather="trash-2" class="mr-2"></i> Clear
50
+ </button>
51
+ </div>
52
+ </header>
53
+
54
+ <!-- Main Grid -->
55
+ <div id="grid-container" class="grid grid-cols-16 grid-rows-12 h-screen w-screen p-8 pt-20">
56
+ <!-- Grid will be generated by JS -->
57
+ </div>
58
+
59
+ <!-- Controls -->
60
+ <div class="absolute bottom-0 left-0 right-0 bg-gray-800 bg-opacity-80 p-4 backdrop-blur-sm">
61
+ <div class="max-w-6xl mx-auto">
62
+ <div class="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
63
+ <div class="flex items-center space-x-4">
64
+ <label class="flex items-center">
65
+ <span class="mr-2">Tempo:</span>
66
+ <input type="range" id="tempo" min="60" max="240" value="120" class="w-32">
67
+ <span id="tempo-value" class="ml-2">120 BPM</span>
68
+ </label>
69
+ </div>
70
+
71
+ <div class="flex space-x-2">
72
+ <button id="octave-down" class="p-2 bg-gray-700 rounded hover:bg-gray-600">
73
+ <i data-feather="chevrons-down"></i>
74
+ </button>
75
+ <span id="octave-display" class="px-3 py-2 bg-gray-700 rounded">Octave 4</span>
76
+ <button id="octave-up" class="p-2 bg-gray-700 rounded hover:bg-gray-600">
77
+ <i data-feather="chevrons-up"></i>
78
+ </button>
79
+ </div>
80
+
81
+ <div class="flex items-center space-x-2">
82
+ <span>Scale:</span>
83
+ <select id="scale-select" class="bg-gray-700 rounded px-3 py-2">
84
+ <option value="major">Major</option>
85
+ <option value="minor">Minor</option>
86
+ <option value="pentatonic">Pentatonic</option>
87
+ <option value="blues">Blues</option>
88
+ </select>
89
+ </div>
90
+ </div>
91
+
92
+ <!-- Virtual Keyboard -->
93
+ <div id="keyboard" class="mt-4 flex justify-center">
94
+ <!-- Keyboard will be generated by JS -->
95
+ </div>
96
+ </div>
97
+ </div>
98
+
99
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
100
+ <script>
101
+ // Initialize Feather Icons
102
+ feather.replace();
103
+
104
+ // Initialize Vanta.js background
105
+ VANTA.WAVES({
106
+ el: "#grid-container",
107
+ mouseControls: true,
108
+ touchControls: true,
109
+ gyroControls: false,
110
+ minHeight: 200.00,
111
+ minWidth: 200.00,
112
+ scale: 1.00,
113
+ scaleMobile: 1.00,
114
+ color: 0x1a1a2e,
115
+ shininess: 15.00,
116
+ waveHeight: 10.00,
117
+ waveSpeed: 0.50
118
+ });
119
+
120
+ // Audio context
121
+ const AudioContext = window.AudioContext || window.webkitAudioContext;
122
+ const audioCtx = new AudioContext();
123
+
124
+ // Grid configuration
125
+ const rows = 12;
126
+ const cols = 16;
127
+ const gridContainer = document.getElementById('grid-container');
128
+ let grid = Array(rows).fill().map(() => Array(cols).fill(0));
129
+ let isPlaying = false;
130
+ let tempo = 120;
131
+ let currentStep = 0;
132
+ let octave = 4;
133
+ let scale = 'major';
134
+
135
+ // Note frequencies (C4 = 261.63 Hz)
136
+ const noteFrequencies = {
137
+ 'C': 261.63,
138
+ 'C#': 277.18,
139
+ 'D': 293.66,
140
+ 'D#': 311.13,
141
+ 'E': 329.63,
142
+ 'F': 349.23,
143
+ 'F#': 369.99,
144
+ 'G': 392.00,
145
+ 'G#': 415.30,
146
+ 'A': 440.00,
147
+ 'A#': 466.16,
148
+ 'B': 493.88
149
+ };
150
+
151
+ // Scales
152
+ const scales = {
153
+ major: [0, 2, 4, 5, 7, 9, 11],
154
+ minor: [0, 2, 3, 5, 7, 8, 10],
155
+ pentatonic: [0, 2, 4, 7, 9],
156
+ blues: [0, 3, 5, 6, 7, 10]
157
+ };
158
+
159
+ // Create grid
160
+ function createGrid() {
161
+ gridContainer.innerHTML = '';
162
+ for (let row = 0; row < rows; row++) {
163
+ for (let col = 0; col < cols; col++) {
164
+ const cell = document.createElement('div');
165
+ cell.className = `note-cell border border-gray-700 rounded relative`;
166
+ cell.dataset.row = row;
167
+ cell.dataset.col = col;
168
+
169
+ // Add note label
170
+ const noteLabel = document.createElement('span');
171
+ noteLabel.className = 'absolute top-1 left-1 text-xs opacity-50';
172
+ noteLabel.textContent = getNoteName(row);
173
+ cell.appendChild(noteLabel);
174
+
175
+ // Add step indicator
176
+ if (row === 0) {
177
+ const stepLabel = document.createElement('span');
178
+ stepLabel.className = 'absolute bottom-1 right-1 text-xs opacity-50';
179
+ stepLabel.textContent = col + 1;
180
+ cell.appendChild(stepLabel);
181
+ }
182
+
183
+ cell.addEventListener('click', () => toggleNote(row, col));
184
+ gridContainer.appendChild(cell);
185
+ }
186
+ }
187
+ updateGridDisplay();
188
+ }
189
+
190
+ // Get note name based on row and scale
191
+ function getNoteName(row) {
192
+ const scaleNotes = scales[scale];
193
+ const noteIndex = scaleNotes[11 - row] || 0;
194
+ const noteNames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
195
+ return noteNames[noteIndex];
196
+ }
197
+
198
+ // Toggle note on/off
199
+ function toggleNote(row, col) {
200
+ grid[row][col] = grid[row][col] ? 0 : 1;
201
+ updateGridDisplay();
202
+ }
203
+
204
+ // Update grid display
205
+ function updateGridDisplay() {
206
+ const cells = document.querySelectorAll('.note-cell');
207
+ cells.forEach(cell => {
208
+ const row = parseInt(cell.dataset.row);
209
+ const col = parseInt(cell.dataset.col);
210
+ if (grid[row][col]) {
211
+ cell.classList.add('bg-indigo-500');
212
+ cell.classList.remove('bg-gray-800');
213
+ } else {
214
+ cell.classList.remove('bg-indigo-500');
215
+ cell.classList.add('bg-gray-800');
216
+ }
217
+ });
218
+ }
219
+
220
+ // Play a note
221
+ function playNote(frequency, duration = 0.5) {
222
+ const oscillator = audioCtx.createOscillator();
223
+ const gainNode = audioCtx.createGain();
224
+
225
+ oscillator.connect(gainNode);
226
+ gainNode.connect(audioCtx.destination);
227
+
228
+ oscillator.type = 'sine';
229
+ oscillator.frequency.value = frequency;
230
+
231
+ gainNode.gain.setValueAtTime(0.3, audioCtx.currentTime);
232
+ gainNode.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
233
+
234
+ oscillator.start();
235
+ oscillator.stop(audioCtx.currentTime + duration);
236
+ }
237
+
238
+ // Play sequence
239
+ function playSequence() {
240
+ if (!isPlaying) return;
241
+
242
+ // Highlight current column
243
+ const cells = document.querySelectorAll('.note-cell');
244
+ cells.forEach(cell => {
245
+ const col = parseInt(cell.dataset.col);
246
+ if (col === currentStep) {
247
+ cell.classList.add('bg-purple-500');
248
+ } else {
249
+ cell.classList.remove('bg-purple-500');
250
+ }
251
+ });
252
+
253
+ // Play notes in current column
254
+ for (let row = 0; row < rows; row++) {
255
+ if (grid[row][currentStep]) {
256
+ const scaleNotes = scales[scale];
257
+ const noteIndex = scaleNotes[11 - row] || 0;
258
+ const frequency = noteFrequencies[getNoteName(row)] * Math.pow(2, octave - 4);
259
+ playNote(frequency, 60 / tempo);
260
+
261
+ // Add visual feedback
262
+ const cell = document.querySelector(`.note-cell[data-row="${row}"][data-col="${currentStep}"]`);
263
+ cell.classList.add('active-note');
264
+ setTimeout(() => cell.classList.remove('active-note'), 300);
265
+ }
266
+ }
267
+
268
+ currentStep = (currentStep + 1) % cols;
269
+ setTimeout(playSequence, (60 / tempo) * 1000);
270
+ }
271
+
272
+ // Create virtual keyboard
273
+ function createKeyboard() {
274
+ const keyboard = document.getElementById('keyboard');
275
+ keyboard.innerHTML = '';
276
+
277
+ const whiteKeys = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
278
+ const blackKeys = ['C#', 'D#', null, 'F#', 'G#', 'A#', null];
279
+
280
+ // Create white keys
281
+ whiteKeys.forEach((note, i) => {
282
+ const key = document.createElement('div');
283
+ key.className = 'keyboard-key w-12 h-32 bg-white border border-gray-300 rounded-b-md flex items-end justify-center pb-2 relative';
284
+ key.dataset.note = note;
285
+ key.textContent = note;
286
+ key.addEventListener('mousedown', () => playKeyNote(note));
287
+ keyboard.appendChild(key);
288
+
289
+ // Add black key if needed
290
+ if (blackKeys[i]) {
291
+ const blackKey = document.createElement('div');
292
+ blackKey.className = 'keyboard-key absolute w-8 h-20 bg-gray-900 rounded-b-md flex items-end justify-center pb-2 text-white -ml-6';
293
+ blackKey.dataset.note = blackKeys[i];
294
+ blackKey.textContent = blackKeys[i];
295
+ blackKey.style.left = `${i * 48 + 36}px`;
296
+ blackKey.addEventListener('mousedown', () => playKeyNote(blackKeys[i]));
297
+ keyboard.appendChild(blackKey);
298
+ }
299
+ });
300
+ }
301
+
302
+ // Play note from keyboard
303
+ function playKeyNote(note) {
304
+ const frequency = noteFrequencies[note] * Math.pow(2, octave - 4);
305
+ playNote(frequency, 0.5);
306
+
307
+ // Visual feedback
308
+ const key = document.querySelector(`.keyboard-key[data-note="${note}"]`);
309
+ if (key) {
310
+ key.classList.add('active');
311
+ setTimeout(() => key.classList.remove('active'), 200);
312
+ }
313
+ }
314
+
315
+ // Event listeners
316
+ document.getElementById('playBtn').addEventListener('click', () => {
317
+ isPlaying = !isPlaying;
318
+ document.getElementById('playBtn').innerHTML = isPlaying ?
319
+ '<i data-feather="pause" class="mr-2"></i> Pause' :
320
+ '<i data-feather="play" class="mr-2"></i> Play';
321
+ feather.replace();
322
+
323
+ if (isPlaying) {
324
+ currentStep = 0;
325
+ playSequence();
326
+ }
327
+ });
328
+
329
+ document.getElementById('clearBtn').addEventListener('click', () => {
330
+ grid = Array(rows).fill().map(() => Array(cols).fill(0));
331
+ updateGridDisplay();
332
+ });
333
+
334
+ document.getElementById('tempo').addEventListener('input', (e) => {
335
+ tempo = e.target.value;
336
+ document.getElementById('tempo-value').textContent = `${tempo} BPM`;
337
+ });
338
+
339
+ document.getElementById('octave-down').addEventListener('click', () => {
340
+ if (octave > 2) {
341
+ octave--;
342
+ document.getElementById('octave-display').textContent = `Octave ${octave}`;
343
+ createKeyboard();
344
+ }
345
+ });
346
+
347
+ document.getElementById('octave-up').addEventListener('click', () => {
348
+ if (octave < 6) {
349
+ octave++;
350
+ document.getElementById('octave-display').textContent = `Octave ${octave}`;
351
+ createKeyboard();
352
+ }
353
+ });
354
+
355
+ document.getElementById('scale-select').addEventListener('change', (e) => {
356
+ scale = e.target.value;
357
+ createGrid();
358
+ createKeyboard();
359
+ });
360
+
361
+ // Initialize
362
+ createGrid();
363
+ createKeyboard();
364
+ </script>
365
+ </body>
366
  </html>