x3HeadedMonkey commited on
Commit
fb74fc7
·
verified ·
1 Parent(s): 6ddda98

Add 1 files

Browse files
Files changed (1) hide show
  1. index.html +478 -217
index.html CHANGED
@@ -3,270 +3,531 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Fart Piano</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
- .piano-key {
11
- transition: all 0.1s ease;
12
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
13
  }
14
-
15
- .white-key {
 
 
 
16
  position: relative;
17
- z-index: 1;
 
 
 
18
  }
19
-
20
- .black-key {
21
  position: absolute;
22
- z-index: 2;
 
 
 
23
  transform: translateX(-50%);
24
  }
25
-
26
- .piano-key:active, .piano-key.active {
27
- transform: translateY(4px);
28
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
 
 
 
 
29
  }
30
-
31
- .white-key:active, .white-key.active {
32
- background-color: #e5e7eb !important;
33
  }
34
-
35
- .black-key:active, .black-key.active {
36
- background-color: #111827 !important;
37
  }
38
-
39
- .fart-cloud {
40
  position: absolute;
41
- width: 60px;
42
- height: 40px;
43
- background-color: rgba(0, 0, 0, 0.1);
44
- border-radius: 50%;
45
- animation: float 2s ease-out forwards;
46
- opacity: 0;
47
  }
48
-
49
- @keyframes float {
50
- 0% {
51
- transform: translateY(0) scale(0.5);
52
- opacity: 0.8;
53
- }
54
- 100% {
55
- transform: translateY(-100px) scale(1.5);
56
- opacity: 0;
57
- }
 
 
 
 
 
 
 
 
 
58
  }
59
  </style>
60
  </head>
61
- <body class="bg-green-100 min-h-screen flex flex-col items-center justify-center p-4">
62
- <div class="text-center mb-8">
63
- <h1 class="text-4xl font-bold text-green-800 mb-2">Fart Piano</h1>
64
- <p class="text-lg text-green-700">Press keys or click/tap to play hilarious fart sounds!</p>
65
- <div class="mt-4 flex justify-center space-x-4">
66
- <button id="random-btn" class="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded-full transition">
67
- <i class="fas fa-random mr-2"></i>Random Fart
68
- </button>
69
- <button id="combo-btn" class="bg-purple-500 hover:bg-purple-600 text-white font-bold py-2 px-4 rounded-full transition">
70
- <i class="fas fa-bolt mr-2"></i>Fart Combo
71
- </button>
 
 
72
  </div>
73
- </div>
74
-
75
- <div class="relative w-full max-w-3xl h-64 mb-12">
76
- <!-- Piano container -->
77
- <div class="relative h-full flex">
78
- <!-- White keys -->
79
- <div class="flex flex-1 relative">
80
- <!-- C -->
81
- <div data-note="C" class="white-key piano-key bg-white border border-gray-300 rounded-b-lg flex-1 flex flex-col justify-end items-center pb-2 cursor-pointer" style="z-index: 1;">
82
- <span class="text-gray-600 font-bold">C</span>
83
- </div>
84
- <!-- D -->
85
- <div data-note="D" class="white-key piano-key bg-white border border-gray-300 rounded-b-lg flex-1 flex flex-col justify-end items-center pb-2 cursor-pointer" style="z-index: 1;">
86
- <span class="text-gray-600 font-bold">D</span>
87
- </div>
88
- <!-- E -->
89
- <div data-note="E" class="white-key piano-key bg-white border border-gray-300 rounded-b-lg flex-1 flex flex-col justify-end items-center pb-2 cursor-pointer" style="z-index: 1;">
90
- <span class="text-gray-600 font-bold">E</span>
91
- </div>
92
- <!-- F -->
93
- <div data-note="F" class="white-key piano-key bg-white border border-gray-300 rounded-b-lg flex-1 flex flex-col justify-end items-center pb-2 cursor-pointer" style="z-index: 1;">
94
- <span class="text-gray-600 font-bold">F</span>
95
- </div>
96
- <!-- G -->
97
- <div data-note="G" class="white-key piano-key bg-white border border-gray-300 rounded-b-lg flex-1 flex flex-col justify-end items-center pb-2 cursor-pointer" style="z-index: 1;">
98
- <span class="text-gray-600 font-bold">G</span>
99
- </div>
100
- <!-- A -->
101
- <div data-note="A" class="white-key piano-key bg-white border border-gray-300 rounded-b-lg flex-1 flex flex-col justify-end items-center pb-2 cursor-pointer" style="z-index: 1;">
102
- <span class="text-gray-600 font-bold">A</span>
103
- </div>
104
- <!-- B -->
105
- <div data-note="B" class="white-key piano-key bg-white border border-gray-300 rounded-b-lg flex-1 flex flex-col justify-end items-center pb-2 cursor-pointer" style="z-index: 1;">
106
- <span class="text-gray-600 font-bold">B</span>
107
  </div>
108
- <!-- C (high) -->
109
- <div data-note="C-high" class="white-key piano-key bg-white border border-gray-300 rounded-b-lg flex-1 flex flex-col justify-end items-center pb-2 cursor-pointer" style="z-index: 1;">
110
- <span class="text-gray-600 font-bold">C</span>
111
  </div>
112
  </div>
113
 
114
- <!-- Black keys -->
115
- <div class="absolute top-0 left-0 w-full h-2/3 flex pointer-events-none" style="z-index: 2;">
116
- <!-- C# -->
117
- <div class="flex-1 relative">
118
- <div data-note="C#" class="black-key piano-key bg-gray-900 rounded-b-lg w-8 h-32 mx-auto cursor-pointer" style="left: 70%;"></div>
119
- </div>
120
- <!-- D# -->
121
- <div class="flex-1 relative">
122
- <div data-note="D#" class="black-key piano-key bg-gray-900 rounded-b-lg w-8 h-32 mx-auto cursor-pointer" style="left: 70%;"></div>
123
- </div>
124
- <!-- F# -->
125
- <div class="flex-1 relative">
126
- <div data-note="F#" class="black-key piano-key bg-gray-900 rounded-b-lg w-8 h-32 mx-auto cursor-pointer" style="left: 70%;"></div>
127
- </div>
128
- <!-- G# -->
129
- <div class="flex-1 relative">
130
- <div data-note="G#" class="black-key piano-key bg-gray-900 rounded-b-lg w-8 h-32 mx-auto cursor-pointer" style="left: 70%;"></div>
131
  </div>
132
- <!-- A# -->
133
- <div class="flex-1 relative">
134
- <div data-note="A#" class="black-key piano-key bg-gray-900 rounded-b-lg w-8 h-32 mx-auto cursor-pointer" style="left: 70%;"></div>
 
 
 
 
135
  </div>
136
- <!-- Empty space for B and high C -->
137
- <div class="flex-1"></div>
138
- <div class="flex-1"></div>
139
  </div>
 
 
140
  </div>
141
  </div>
142
-
143
- <div class="text-center text-gray-700">
144
- <p class="mb-2">Keyboard controls: A S D F G H J K (white keys) and W E T Y U (black keys)</p>
145
- <p class="text-sm text-gray-500">Warning: This piano may cause uncontrollable laughter!</p>
146
- </div>
147
-
148
- <audio id="C" src="https://www.myinstants.com/media/sounds/fart.mp3"></audio>
149
- <audio id="C#" src="https://www.myinstants.com/media/sounds/fart-01.mp3"></audio>
150
- <audio id="D" src="https://www.myinstants.com/media/sounds/fart-02.mp3"></audio>
151
- <audio id="D#" src="https://www.myinstants.com/media/sounds/fart-03.mp3"></audio>
152
- <audio id="E" src="https://www.myinstants.com/media/sounds/fart-04.mp3"></audio>
153
- <audio id="F" src="https://www.myinstants.com/media/sounds/fart-05.mp3"></audio>
154
- <audio id="F#" src="https://www.myinstants.com/media/sounds/fart-06.mp3"></audio>
155
- <audio id="G" src="https://www.myinstants.com/media/sounds/fart-07.mp3"></audio>
156
- <audio id="G#" src="https://www.myinstants.com/media/sounds/fart-08.mp3"></audio>
157
- <audio id="A" src="https://www.myinstants.com/media/sounds/fart-09.mp3"></audio>
158
- <audio id="A#" src="https://www.myinstants.com/media/sounds/fart-10.mp3"></audio>
159
- <audio id="B" src="https://www.myinstants.com/media/sounds/fart-11.mp3"></audio>
160
- <audio id="C-high" src="https://www.myinstants.com/media/sounds/fart-12.mp3"></audio>
161
-
162
  <script>
163
- document.addEventListener('DOMContentLoaded', function() {
164
- const keys = document.querySelectorAll('.piano-key');
165
- const randomBtn = document.getElementById('random-btn');
166
- const comboBtn = document.getElementById('combo-btn');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
- // Keyboard to note mapping
169
- const keyMap = {
170
- 'a': 'C',
171
- 'w': 'C#',
172
- 's': 'D',
173
- 'e': 'D#',
174
- 'd': 'E',
175
- 'f': 'F',
176
- 't': 'F#',
177
- 'g': 'G',
178
- 'y': 'G#',
179
- 'h': 'A',
180
- 'u': 'A#',
181
- 'j': 'B',
182
- 'k': 'C-high'
183
- };
184
 
185
- // Play sound and animate key
186
- function playNote(note) {
187
- const audio = document.getElementById(note);
188
- if (audio) {
189
- audio.currentTime = 0;
190
- audio.play();
191
-
192
- // Find the key element
193
- const key = document.querySelector(`.piano-key[data-note="${note}"]`);
194
- if (key) {
195
- // Add active class
196
- key.classList.add('active');
197
-
198
- // Create fart cloud
199
- if (Math.random() > 0.3) { // 70% chance to show cloud
200
- const cloud = document.createElement('div');
201
- cloud.className = 'fart-cloud';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
- // Position cloud above the key
204
- const rect = key.getBoundingClientRect();
205
- cloud.style.left = `${rect.left + rect.width/2 - 30}px`;
206
- cloud.style.top = `${rect.top - 20}px`;
207
 
208
- document.body.appendChild(cloud);
 
 
 
 
 
 
 
 
 
 
 
209
 
210
- // Remove cloud after animation
211
- setTimeout(() => {
212
- cloud.remove();
213
- }, 2000);
214
- }
215
-
216
- // Remove active class after delay
217
- setTimeout(() => {
218
- key.classList.remove('active');
219
- }, 150);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  }
222
  }
223
 
224
- // Click/tap event for keys
225
- keys.forEach(key => {
226
- key.addEventListener('mousedown', () => {
227
- const note = key.getAttribute('data-note');
228
- playNote(note);
229
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
- key.addEventListener('touchstart', (e) => {
232
- e.preventDefault();
233
- const note = key.getAttribute('data-note');
234
- playNote(note);
 
 
 
235
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  });
237
 
238
- // Keyboard events
239
- document.addEventListener('keydown', (e) => {
240
- const note = keyMap[e.key.toLowerCase()];
241
- if (note && !e.repeat) {
242
- playNote(note);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  }
244
  });
245
 
246
- // Random fart button
247
- randomBtn.addEventListener('click', () => {
248
- const notes = Array.from(keys).map(key => key.getAttribute('data-note'));
249
- const randomNote = notes[Math.floor(Math.random() * notes.length)];
250
- playNote(randomNote);
251
  });
252
 
253
- // Fart combo button
254
- comboBtn.addEventListener('click', () => {
255
- const notes = Array.from(keys).map(key => key.getAttribute('data-note'));
256
-
257
- // Play 5-8 random notes in sequence with delays
258
- const count = 5 + Math.floor(Math.random() * 4);
259
- let delay = 0;
260
-
261
- for (let i = 0; i < count; i++) {
262
- setTimeout(() => {
263
- const randomNote = notes[Math.floor(Math.random() * notes.length)];
264
- playNote(randomNote);
265
- }, delay);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
 
267
- delay += 200 + Math.random() * 300; // Random delay between 200-500ms
 
 
 
 
 
 
 
 
 
 
268
  }
269
- });
 
 
 
 
 
 
 
 
 
270
  });
271
  </script>
272
  <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=x3HeadedMonkey/faret" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Audio Stereo Width Visualizer</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
9
  <style>
10
+ .dropzone {
11
+ transition: all 0.3s ease;
 
12
  }
13
+ .dropzone.active {
14
+ border-color: #4f46e5;
15
+ background-color: rgba(79, 70, 229, 0.05);
16
+ }
17
+ .visualizer {
18
  position: relative;
19
+ height: 100px;
20
+ background: linear-gradient(90deg, #1e293b 0%, #334155 50%, #1e293b 100%);
21
+ border-radius: 8px;
22
+ overflow: hidden;
23
  }
24
+ .center-line {
 
25
  position: absolute;
26
+ width: 2px;
27
+ height: 100%;
28
+ background-color: rgba(255, 255, 255, 0.2);
29
+ left: 50%;
30
  transform: translateX(-50%);
31
  }
32
+ .stereo-bar {
33
+ position: absolute;
34
+ bottom: 0;
35
+ height: 0;
36
+ transition: height 0.05s ease-out;
37
+ width: 100%;
38
+ background: linear-gradient(to top, #4f46e5, #818cf8);
39
+ opacity: 0.7;
40
  }
41
+ .left-channel {
42
+ clip-path: polygon(0 0, 50% 0, 50% 100%, 0% 100%);
 
43
  }
44
+ .right-channel {
45
+ clip-path: polygon(50% 0, 100% 0, 100% 100%, 50% 100%);
 
46
  }
47
+ .width-indicator {
 
48
  position: absolute;
49
+ top: 10px;
50
+ width: 100%;
51
+ text-align: center;
52
+ color: white;
53
+ font-weight: bold;
54
+ text-shadow: 0 1px 2px rgba(0,0,0,0.5);
55
  }
56
+ .playhead {
57
+ position: absolute;
58
+ width: 2px;
59
+ height: 100%;
60
+ background-color: white;
61
+ left: 0;
62
+ top: 0;
63
+ z-index: 10;
64
+ }
65
+ .waveform {
66
+ position: absolute;
67
+ width: 100%;
68
+ height: 100%;
69
+ opacity: 0.3;
70
+ }
71
+ .waveform path {
72
+ stroke: white;
73
+ stroke-width: 1;
74
+ fill: none;
75
  }
76
  </style>
77
  </head>
78
+ <body class="bg-slate-900 text-white min-h-screen flex flex-col items-center justify-center p-4">
79
+ <div class="max-w-3xl w-full space-y-6">
80
+ <h1 class="text-3xl font-bold text-center text-indigo-400 mb-2">Stereo Width Visualizer</h1>
81
+ <p class="text-center text-slate-400 mb-8">Drag and drop audio files to analyze stereo width</p>
82
+
83
+ <div
84
+ id="dropzone"
85
+ class="dropzone border-2 border-dashed border-slate-600 rounded-xl p-12 flex flex-col items-center justify-center cursor-pointer hover:border-indigo-500 transition-colors"
86
+ >
87
+ <i class="fas fa-music text-4xl text-indigo-500 mb-4"></i>
88
+ <h2 class="text-xl font-semibold mb-2">Drop audio files here</h2>
89
+ <p class="text-slate-400">or click to browse</p>
90
+ <input type="file" id="fileInput" class="hidden" accept="audio/*" multiple>
91
  </div>
92
+
93
+ <div id="playerContainer" class="hidden space-y-4">
94
+ <div class="flex items-center justify-between">
95
+ <div class="flex items-center space-x-4">
96
+ <button id="playBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white rounded-full w-12 h-12 flex items-center justify-center">
97
+ <i class="fas fa-play"></i>
98
+ </button>
99
+ <div>
100
+ <div id="trackName" class="font-medium">No track selected</div>
101
+ <div class="text-sm text-slate-400 flex space-x-4">
102
+ <span id="currentTime">0:00</span>
103
+ <span>/</span>
104
+ <span id="totalTime">0:00</span>
105
+ </div>
106
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  </div>
108
+ <div class="flex items-center space-x-2">
109
+ <span class="text-sm text-slate-400">Volume:</span>
110
+ <input id="volumeControl" type="range" min="0" max="1" step="0.01" value="0.7" class="w-24">
111
  </div>
112
  </div>
113
 
114
+ <div class="relative">
115
+ <div class="visualizer">
116
+ <div class="center-line"></div>
117
+ <div id="leftBar" class="stereo-bar left-channel"></div>
118
+ <div id="rightBar" class="stereo-bar right-channel"></div>
119
+ <div id="widthIndicator" class="width-indicator">Stereo Width: 0%</div>
120
+ <div id="waveform" class="waveform"></div>
121
+ <div id="playhead" class="playhead"></div>
 
 
 
 
 
 
 
 
 
122
  </div>
123
+ </div>
124
+
125
+ <div class="flex items-center space-x-4">
126
+ <input id="seekBar" type="range" min="0" max="100" value="0" class="flex-1">
127
+ <div id="stereoMeter" class="w-24 h-2 bg-slate-700 rounded-full overflow-hidden flex">
128
+ <div id="stereoMeterLeft" class="h-full bg-indigo-500" style="width: 50%;"></div>
129
+ <div id="stereoMeterRight" class="h-full bg-indigo-300" style="width: 50%;"></div>
130
  </div>
 
 
 
131
  </div>
132
+
133
+ <div id="trackList" class="space-y-2 max-h-60 overflow-y-auto"></div>
134
  </div>
135
  </div>
136
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  <script>
138
+ document.addEventListener('DOMContentLoaded', () => {
139
+ const dropzone = document.getElementById('dropzone');
140
+ const fileInput = document.getElementById('fileInput');
141
+ const playerContainer = document.getElementById('playerContainer');
142
+ const trackList = document.getElementById('trackList');
143
+ const playBtn = document.getElementById('playBtn');
144
+ const seekBar = document.getElementById('seekBar');
145
+ const currentTime = document.getElementById('currentTime');
146
+ const totalTime = document.getElementById('totalTime');
147
+ const volumeControl = document.getElementById('volumeControl');
148
+ const trackName = document.getElementById('trackName');
149
+ const leftBar = document.getElementById('leftBar');
150
+ const rightBar = document.getElementById('rightBar');
151
+ const widthIndicator = document.getElementById('widthIndicator');
152
+ const stereoMeterLeft = document.getElementById('stereoMeterLeft');
153
+ const stereoMeterRight = document.getElementById('stereoMeterRight');
154
+ const waveform = document.getElementById('waveform');
155
+ const playhead = document.getElementById('playhead');
156
 
157
+ let audioContext;
158
+ let analyser;
159
+ let audioSource;
160
+ let audioBuffer;
161
+ let currentTrack = null;
162
+ let tracks = [];
163
+ let isPlaying = false;
164
+ let animationFrameId;
165
+ let waveformData = [];
 
 
 
 
 
 
 
166
 
167
+ // Initialize Audio Context on user interaction
168
+ function initAudioContext() {
169
+ if (!audioContext) {
170
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
171
+ analyser = audioContext.createAnalyser();
172
+ analyser.fftSize = 256;
173
+ }
174
+ }
175
+
176
+ // Handle drag and drop
177
+ dropzone.addEventListener('click', () => fileInput.click());
178
+
179
+ fileInput.addEventListener('change', (e) => {
180
+ handleFiles(e.target.files);
181
+ fileInput.value = '';
182
+ });
183
+
184
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
185
+ dropzone.addEventListener(eventName, preventDefaults, false);
186
+ });
187
+
188
+ function preventDefaults(e) {
189
+ e.preventDefault();
190
+ e.stopPropagation();
191
+ }
192
+
193
+ ['dragenter', 'dragover'].forEach(eventName => {
194
+ dropzone.addEventListener(eventName, highlight, false);
195
+ });
196
+
197
+ ['dragleave', 'drop'].forEach(eventName => {
198
+ dropzone.addEventListener(eventName, unhighlight, false);
199
+ });
200
+
201
+ function highlight() {
202
+ dropzone.classList.add('active');
203
+ }
204
+
205
+ function unhighlight() {
206
+ dropzone.classList.remove('active');
207
+ }
208
+
209
+ dropzone.addEventListener('drop', (e) => {
210
+ const dt = e.dataTransfer;
211
+ const files = dt.files;
212
+ handleFiles(files);
213
+ });
214
+
215
+ // Handle dropped files
216
+ function handleFiles(files) {
217
+ initAudioContext();
218
+
219
+ const audioFiles = Array.from(files).filter(file => file.type.startsWith('audio/'));
220
+
221
+ if (audioFiles.length === 0) {
222
+ alert('Please drop audio files only.');
223
+ return;
224
+ }
225
+
226
+ tracks = [];
227
+ trackList.innerHTML = '';
228
+
229
+ audioFiles.forEach((file, index) => {
230
+ const reader = new FileReader();
231
+ reader.onload = (e) => {
232
+ audioContext.decodeAudioData(e.target.result).then(buffer => {
233
+ const track = {
234
+ name: file.name,
235
+ buffer: buffer,
236
+ duration: buffer.duration
237
+ };
238
 
239
+ tracks.push(track);
 
 
 
240
 
241
+ // Add to track list
242
+ const trackElement = document.createElement('div');
243
+ trackElement.className = `p-3 rounded-lg cursor-pointer ${index === 0 ? 'bg-slate-800' : 'hover:bg-slate-800'}`;
244
+ trackElement.innerHTML = `
245
+ <div class="flex items-center justify-between">
246
+ <div>
247
+ <div class="font-medium truncate">${track.name}</div>
248
+ <div class="text-sm text-slate-400">${formatTime(track.duration)}</div>
249
+ </div>
250
+ <i class="fas fa-play text-indigo-500"></i>
251
+ </div>
252
+ `;
253
 
254
+ trackElement.addEventListener('click', () => loadTrack(track));
255
+
256
+ trackList.appendChild(trackElement);
257
+
258
+ // Load first track automatically
259
+ if (index === 0) {
260
+ loadTrack(track);
261
+ }
262
+ });
263
+ };
264
+ reader.readAsArrayBuffer(file);
265
+ });
266
+
267
+ playerContainer.classList.remove('hidden');
268
+ }
269
+
270
+ // Load a track
271
+ function loadTrack(track) {
272
+ if (audioSource) {
273
+ audioSource.stop();
274
+ }
275
+
276
+ currentTrack = track;
277
+ audioBuffer = track.buffer;
278
+
279
+ // Update UI
280
+ trackName.textContent = track.name;
281
+ totalTime.textContent = formatTime(track.duration);
282
+
283
+ // Highlight selected track in list
284
+ Array.from(trackList.children).forEach((child, index) => {
285
+ if (tracks[index] === track) {
286
+ child.classList.add('bg-slate-800');
287
+ } else {
288
+ child.classList.remove('bg-slate-800');
289
  }
290
+ });
291
+
292
+ // Generate waveform
293
+ generateWaveform(track.buffer);
294
+
295
+ // Reset playback
296
+ seekBar.value = 0;
297
+ currentTime.textContent = '0:00';
298
+ updatePlayhead(0);
299
+
300
+ // If was playing, start new track
301
+ if (isPlaying) {
302
+ playTrack();
303
  }
304
  }
305
 
306
+ // Generate waveform visualization
307
+ function generateWaveform(buffer) {
308
+ waveformData = [];
309
+ const svgNS = "http://www.w3.org/2000/svg";
310
+ waveform.innerHTML = '';
311
+
312
+ const leftChannel = buffer.getChannelData(0);
313
+ const rightChannel = buffer.numberOfChannels > 1 ? buffer.getChannelData(1) : leftChannel;
314
+
315
+ // Downsample for display
316
+ const samplesPerPixel = Math.floor(leftChannel.length / 500);
317
+ const points = [];
318
+
319
+ for (let i = 0; i < 500; i++) {
320
+ const start = i * samplesPerPixel;
321
+ const end = start + samplesPerPixel;
322
+
323
+ let leftSum = 0;
324
+ let rightSum = 0;
325
+
326
+ for (let j = start; j < end; j++) {
327
+ leftSum += Math.abs(leftChannel[j] || 0);
328
+ if (rightChannel) {
329
+ rightSum += Math.abs(rightChannel[j] || 0);
330
+ }
331
+ }
332
+
333
+ const leftAvg = leftSum / samplesPerPixel;
334
+ const rightAvg = rightSum / samplesPerPixel;
335
+
336
+ points.push({
337
+ x: i,
338
+ left: leftAvg,
339
+ right: rightAvg
340
+ });
341
+
342
+ waveformData.push({
343
+ left: leftAvg,
344
+ right: rightAvg
345
+ });
346
+ }
347
 
348
+ // Create SVG path for left channel
349
+ const leftPath = document.createElementNS(svgNS, "path");
350
+ let leftPathData = "M 0 50 ";
351
+
352
+ points.forEach((point, i) => {
353
+ const y = 50 - (point.left * 40);
354
+ leftPathData += `L ${i} ${y} `;
355
  });
356
+
357
+ leftPath.setAttribute("d", leftPathData);
358
+ leftPath.setAttribute("stroke", "#4f46e5");
359
+ waveform.appendChild(leftPath);
360
+
361
+ // Create SVG path for right channel if stereo
362
+ if (buffer.numberOfChannels > 1) {
363
+ const rightPath = document.createElementNS(svgNS, "path");
364
+ let rightPathData = "M 0 50 ";
365
+
366
+ points.forEach((point, i) => {
367
+ const y = 50 + (point.right * 40);
368
+ rightPathData += `L ${i} ${y} `;
369
+ });
370
+
371
+ rightPath.setAttribute("d", rightPathData);
372
+ rightPath.setAttribute("stroke", "#818cf8");
373
+ waveform.appendChild(rightPath);
374
+ }
375
+ }
376
+
377
+ // Play/pause track
378
+ playBtn.addEventListener('click', () => {
379
+ if (!currentTrack) return;
380
+
381
+ if (isPlaying) {
382
+ pauseTrack();
383
+ } else {
384
+ playTrack();
385
+ }
386
  });
387
 
388
+ function playTrack() {
389
+ if (!audioBuffer) return;
390
+
391
+ audioSource = audioContext.createBufferSource();
392
+ audioSource.buffer = audioBuffer;
393
+
394
+ // Create analyser
395
+ analyser = audioContext.createAnalyser();
396
+ analyser.fftSize = 256;
397
+
398
+ // Connect nodes
399
+ audioSource.connect(analyser);
400
+ analyser.connect(audioContext.destination);
401
+
402
+ // Set volume
403
+ const gainNode = audioContext.createGain();
404
+ gainNode.gain.value = volumeControl.value;
405
+ audioSource.connect(gainNode);
406
+ gainNode.connect(audioContext.destination);
407
+
408
+ // Start playback
409
+ audioSource.start(0, seekBar.value / 100 * audioBuffer.duration);
410
+
411
+ // Update UI
412
+ isPlaying = true;
413
+ playBtn.innerHTML = '<i class="fas fa-pause"></i>';
414
+
415
+ // Start visualization
416
+ startVisualization();
417
+
418
+ // Update seek bar as audio plays
419
+ audioSource.onended = () => {
420
+ pauseTrack();
421
+ seekBar.value = 0;
422
+ currentTime.textContent = '0:00';
423
+ updatePlayhead(0);
424
+ };
425
+ }
426
+
427
+ function pauseTrack() {
428
+ if (!audioSource) return;
429
+
430
+ audioSource.stop();
431
+ isPlaying = false;
432
+ playBtn.innerHTML = '<i class="fas fa-play"></i>';
433
+
434
+ cancelAnimationFrame(animationFrameId);
435
+ }
436
+
437
+ // Seek control
438
+ seekBar.addEventListener('input', () => {
439
+ if (!currentTrack) return;
440
+
441
+ const seekTime = (seekBar.value / 100) * currentTrack.duration;
442
+ currentTime.textContent = formatTime(seekTime);
443
+ updatePlayhead(seekBar.value);
444
+
445
+ if (isPlaying) {
446
+ pauseTrack();
447
+ playTrack();
448
  }
449
  });
450
 
451
+ // Volume control
452
+ volumeControl.addEventListener('input', () => {
453
+ if (audioSource && audioSource.gain) {
454
+ audioSource.gain.value = volumeControl.value;
455
+ }
456
  });
457
 
458
+ // Update playhead position
459
+ function updatePlayhead(percent) {
460
+ playhead.style.left = `${percent}%`;
461
+ }
462
+
463
+ // Visualization
464
+ function startVisualization() {
465
+ const bufferLength = analyser.frequencyBinCount;
466
+ const dataArray = new Uint8Array(bufferLength);
467
+
468
+ function visualize() {
469
+ animationFrameId = requestAnimationFrame(visualize);
470
+
471
+ analyser.getByteFrequencyData(dataArray);
472
+
473
+ // Calculate stereo width
474
+ let leftSum = 0;
475
+ let rightSum = 0;
476
+
477
+ // Assuming the first half is left, second half is right
478
+ const midPoint = Math.floor(bufferLength / 2);
479
+
480
+ for (let i = 0; i < midPoint; i++) {
481
+ leftSum += dataArray[i];
482
+ }
483
+
484
+ for (let i = midPoint; i < bufferLength; i++) {
485
+ rightSum += dataArray[i];
486
+ }
487
+
488
+ const leftAvg = leftSum / midPoint;
489
+ const rightAvg = rightSum / (bufferLength - midPoint);
490
+
491
+ // Update bars
492
+ const leftHeight = (leftAvg / 255) * 100;
493
+ const rightHeight = (rightAvg / 255) * 100;
494
+
495
+ leftBar.style.height = `${leftHeight}%`;
496
+ rightBar.style.height = `${rightHeight}%`;
497
+
498
+ // Calculate stereo width (0-100%)
499
+ const stereoWidth = Math.min(100, Math.max(0,
500
+ (Math.abs(leftAvg - rightAvg) / 255) * 100 * 2
501
+ ));
502
+
503
+ widthIndicator.textContent = `Stereo Width: ${Math.round(stereoWidth)}%`;
504
+
505
+ // Update stereo meter
506
+ stereoMeterLeft.style.width = `${50 + (leftAvg - rightAvg) / 255 * 50}%`;
507
+ stereoMeterRight.style.width = `${50 + (rightAvg - leftAvg) / 255 * 50}%`;
508
 
509
+ // Update seek bar if playing
510
+ if (isPlaying && audioSource) {
511
+ const currentTime = audioContext.currentTime - audioSource.startTime;
512
+ const progress = (currentTime / audioBuffer.duration) * 100;
513
+
514
+ if (!isNaN(progress)) {
515
+ seekBar.value = progress;
516
+ updatePlayhead(progress);
517
+ document.getElementById('currentTime').textContent = formatTime(currentTime);
518
+ }
519
+ }
520
  }
521
+
522
+ visualize();
523
+ }
524
+
525
+ // Format time (seconds to MM:SS)
526
+ function formatTime(seconds) {
527
+ const mins = Math.floor(seconds / 60);
528
+ const secs = Math.floor(seconds % 60);
529
+ return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
530
+ }
531
  });
532
  </script>
533
  <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=x3HeadedMonkey/faret" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>