LukasBe commited on
Commit
c58894c
·
verified ·
1 Parent(s): 7b3ef6f

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +1161 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Voice Command
3
- emoji: 👀
4
- colorFrom: yellow
5
- colorTo: gray
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: voice-command
3
+ emoji: 🐳
4
+ colorFrom: gray
5
+ colorTo: green
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,1161 @@
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>Neural Audio Command Recognizer</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 pulse {
11
+ 0% { transform: scale(1); }
12
+ 50% { transform: scale(1.05); }
13
+ 100% { transform: scale(1); }
14
+ }
15
+
16
+ .pulse-animation {
17
+ animation: pulse 2s infinite;
18
+ }
19
+
20
+ .gradient-bg {
21
+ background: linear-gradient(135deg, #6e8efb, #a777e3);
22
+ }
23
+
24
+ .command-card {
25
+ transition: all 0.3s ease;
26
+ transform-style: preserve-3d;
27
+ }
28
+
29
+ .command-card:hover {
30
+ transform: translateY(-5px);
31
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
32
+ }
33
+
34
+ .waveform {
35
+ height: 60px;
36
+ position: relative;
37
+ overflow: hidden;
38
+ }
39
+
40
+ .confidence-meter {
41
+ height: 6px;
42
+ background: rgba(255, 255, 255, 0.2);
43
+ border-radius: 3px;
44
+ overflow: hidden;
45
+ }
46
+
47
+ .confidence-fill {
48
+ height: 100%;
49
+ background: linear-gradient(90deg, #4ade80, #3b82f6);
50
+ transition: width 0.5s ease;
51
+ }
52
+
53
+ .glow {
54
+ box-shadow: 0 0 15px rgba(167, 119, 227, 0.5);
55
+ }
56
+
57
+ .spectrogram {
58
+ height: 120px;
59
+ background: #1f2937;
60
+ border-radius: 6px;
61
+ margin-top: 10px;
62
+ }
63
+
64
+ .progress-bar {
65
+ height: 8px;
66
+ background: rgba(255, 255, 255, 0.1);
67
+ border-radius: 4px;
68
+ overflow: hidden;
69
+ }
70
+
71
+ .progress-fill {
72
+ height: 100%;
73
+ background: linear-gradient(90deg, #a777e3, #6e8efb);
74
+ }
75
+
76
+ .neuron {
77
+ display: inline-block;
78
+ width: 20px;
79
+ height: 20px;
80
+ border-radius: 50%;
81
+ background: linear-gradient(135deg, #6e8efb, #a777e3);
82
+ margin: 0 2px;
83
+ transition: all 0.3s;
84
+ }
85
+
86
+ .neuron.active {
87
+ transform: scale(1.3);
88
+ box-shadow: 0 0 10px rgba(167, 119, 227, 0.7);
89
+ }
90
+
91
+ .network-visualization {
92
+ display: flex;
93
+ justify-content: center;
94
+ align-items: center;
95
+ height: 200px;
96
+ margin: 20px 0;
97
+ position: relative;
98
+ }
99
+
100
+ .connection {
101
+ position: absolute;
102
+ background: rgba(110, 142, 251, 0.4);
103
+ transform-origin: left center;
104
+ height: 2px;
105
+ }
106
+ </style>
107
+ </head>
108
+ <body class="bg-gray-900 text-white min-h-screen">
109
+ <div class="container mx-auto px-4 py-8">
110
+ <!-- Header -->
111
+ <header class="flex justify-between items-center mb-8">
112
+ <div class="flex items-center space-x-2">
113
+ <div class="gradient-bg rounded-full w-10 h-10 flex items-center justify-center">
114
+ <i class="fas fa-robot text-xl"></i>
115
+ </div>
116
+ <h1 class="text-2xl font-bold">Neural Audio Command Recognizer</h1>
117
+ </div>
118
+ <div class="flex space-x-4">
119
+ <button id="clearStorageBtn" class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg transition">
120
+ <i class="fas fa-trash-alt mr-2"></i>Clear Data
121
+ </button>
122
+ </div>
123
+ </header>
124
+
125
+ <!-- Main Content -->
126
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
127
+ <!-- Left Panel - Command List -->
128
+ <div class="lg:col-span-1 bg-gray-800 rounded-xl p-6">
129
+ <h2 class="text-xl font-semibold mb-4 flex items-center">
130
+ <i class="fas fa-list-ul mr-2"></i> Your Commands
131
+ </h2>
132
+ <div id="commandList" class="space-y-4">
133
+ <!-- Commands will be dynamically added here -->
134
+ </div>
135
+
136
+ <div class="mt-6">
137
+ <h3 class="font-medium mb-2">Add New Command</h3>
138
+ <div class="flex">
139
+ <input id="newCommandInput" type="text" placeholder="Command word" class="flex-1 bg-gray-700 border border-gray-600 rounded-l-lg px-4 py-2 focus:outline-none focus:border-purple-500">
140
+ <button id="addCommandBtn" class="gradient-bg hover:opacity-90 px-4 py-2 rounded-r-lg font-medium transition">
141
+ <i class="fas fa-plus"></i>
142
+ </button>
143
+ </div>
144
+ </div>
145
+
146
+ <div class="mt-6 bg-gray-700 rounded-lg p-4">
147
+ <h3 class="font-medium mb-2">Model Status</h3>
148
+ <div class="flex items-center mb-2">
149
+ <span class="text-sm">Training Progress:</span>
150
+ <span id="trainingProgressText" class="ml-auto text-sm">No data</span>
151
+ </div>
152
+ <div class="progress-bar">
153
+ <div id="trainingProgressBar" class="progress-fill" style="width: 0%"></div>
154
+ </div>
155
+ </div>
156
+ </div>
157
+
158
+ <!-- Center Panel - Training Interface -->
159
+ <div class="lg:col-span-2 space-y-6">
160
+ <div class="bg-gray-800 rounded-xl p-6">
161
+ <h2 class="text-xl font-semibold mb-4 flex items-center">
162
+ <i class="fas fa-microphone-alt mr-2"></i> Training Mode
163
+ </h2>
164
+
165
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
166
+ <div id="currentCommandDisplay" class="bg-gray-700 rounded-lg p-4">
167
+ <h3 class="font-medium mb-2">Training Command</h3>
168
+ <div id="currentCommand" class="text-2xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-500">
169
+ None selected
170
+ </div>
171
+ </div>
172
+ <div class="bg-gray-700 rounded-lg p-4">
173
+ <h3 class="font-medium mb-2">Training Samples</h3>
174
+ <div id="sampleCount" class="text-2xl font-bold">0</div>
175
+ <div class="text-sm text-gray-300">Minimum 5 samples needed</div>
176
+ </div>
177
+ </div>
178
+
179
+ <div id="audioVisualization" class="spectrogram relative">
180
+ <canvas id="waveformCanvas" class="absolute inset-0 w-full h-full"></canvas>
181
+ <canvas id="spectrogramCanvas" class="absolute inset-0 w-full h-full"></canvas>
182
+ </div>
183
+
184
+ <div class="network-visualization" id="networkVisualization">
185
+ <!-- Network visualization will be dynamically generated here -->
186
+ </div>
187
+
188
+ <div class="flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4 mt-4">
189
+ <button id="recordTrainBtn" class="gradient-bg hover:opacity-90 flex-1 py-3 rounded-lg font-medium transition flex items-center justify-center">
190
+ <i class="fas fa-microphone mr-2"></i> Record Sample
191
+ </button>
192
+ <button id="trainBtn" class="bg-gray-700 hover:bg-gray-600 flex-1 py-3 rounded-lg font-medium transition flex items-center justify-center">
193
+ <i class="fas fa-brain mr-2"></i> Train Model
194
+ </button>
195
+ <button id="testBtn" class="border border-purple-500 text-purple-400 hover:bg-purple-900 hover:bg-opacity-30 flex-1 py-3 rounded-lg font-medium transition flex items-center justify-center">
196
+ <i class="fas fa-vial mr-2"></i> Test Model
197
+ </button>
198
+ </div>
199
+ </div>
200
+
201
+ <!-- Recognition Panel -->
202
+ <div class="bg-gray-800 rounded-xl p-6">
203
+ <h2 class="text-xl font-semibold mb-4 flex items-center">
204
+ <i class="fas fa-robot mr-2"></i> Recognition Mode
205
+ </h2>
206
+
207
+ <div id="predictionResult" class="bg-gray-700 rounded-lg p-4 mb-4">
208
+ <div class="flex justify-between items-center mb-2">
209
+ <h3 class="font-medium">Predicted Command</h3>
210
+ <div id="predictionConfidence" class="text-sm font-medium">--% confidence</div>
211
+ </div>
212
+ <div id="recognizedCommand" class="text-3xl font-bold text-center py-4">
213
+ Waiting for command...
214
+ </div>
215
+ <div class="progress-bar">
216
+ <div id="confidenceBar" class="progress-fill" style="width: 0%"></div>
217
+ </div>
218
+ </div>
219
+
220
+ <div class="flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
221
+ <button id="recordPredictBtn" class="gradient-bg hover:opacity-90 flex-1 py-3 rounded-lg font-medium transition flex items-center justify-center pulse-animation">
222
+ <i class="fas fa-microphone mr-2"></i> Record Command
223
+ </button>
224
+ <button id="continuousBtn" class="bg-gray-700 hover:bg-gray-600 flex-1 py-3 rounded-lg font-medium transition flex items-center justify-center">
225
+ <i class="fas fa-circle-notch mr-2"></i> Continuous Mode
226
+ </button>
227
+ </div>
228
+ </div>
229
+ </div>
230
+ </div>
231
+ </div>
232
+
233
+ <script>
234
+ // Neural Network Implementation
235
+ class NeuralNetwork {
236
+ constructor(inputSize, hiddenSize, outputSize) {
237
+ this.inputSize = inputSize;
238
+ this.hiddenSize = hiddenSize;
239
+ this.outputSize = outputSize;
240
+
241
+ // Initialize weights and biases
242
+ const xavierInit = (size) => Math.sqrt(1.0 / size[0]);
243
+
244
+ // Input to hidden layer
245
+ this.weights1 = Array(hiddenSize).fill().map(() =>
246
+ Array(inputSize).fill().map(() => xavierInit([inputSize, hiddenSize]) * (Math.random() * 2 - 1))
247
+ );
248
+ this.bias1 = Array(hiddenSize).fill(0.1);
249
+
250
+ // Hidden to output layer
251
+ this.weights2 = Array(outputSize).fill().map(() =>
252
+ Array(hiddenSize).fill().map(() => xavierInit([hiddenSize, outputSize]) * (Math.random() * 2 - 1))
253
+ );
254
+ this.bias2 = Array(outputSize).fill(0.1);
255
+
256
+ this.learningRate = 0.01;
257
+ }
258
+
259
+ // Sigmoid activation function
260
+ sigmoid(x) {
261
+ return 1 / (1 + Math.exp(-x));
262
+ }
263
+
264
+ // Derivative of sigmoid
265
+ sigmoidDerivative(x) {
266
+ const s = this.sigmoid(x);
267
+ return s * (1 - s);
268
+ }
269
+
270
+ // Forward propagation
271
+ forward(input) {
272
+ // Input to hidden
273
+ const hiddenInput = Array(this.hiddenSize).fill(0);
274
+ for (let i = 0; i < this.hiddenSize; i++) {
275
+ for (let j = 0; j < this.inputSize; j++) {
276
+ hiddenInput[i] += this.weights1[i][j] * input[j];
277
+ }
278
+ hiddenInput[i] += this.bias1[i];
279
+ hiddenInput[i] = this.sigmoid(hiddenInput[i]);
280
+ }
281
+
282
+ // Hidden to output
283
+ const output = Array(this.outputSize).fill(0);
284
+ for (let i = 0; i < this.outputSize; i++) {
285
+ for (let j = 0; j < this.hiddenSize; j++) {
286
+ output[i] += this.weights2[i][j] * hiddenInput[j];
287
+ }
288
+ output[i] += this.bias2[i];
289
+ output[i] = this.sigmoid(output[i]);
290
+ }
291
+
292
+ return {
293
+ output,
294
+ hidden: hiddenInput
295
+ };
296
+ }
297
+
298
+ // Train the network with one sample
299
+ train(input, target) {
300
+ // Forward pass
301
+ const { output, hidden } = this.forward(input);
302
+
303
+ // Backpropagation
304
+ // Output layer error
305
+ const outputErrors = Array(this.outputSize).fill(0);
306
+ const outputDeltas = Array(this.outputSize).fill(0);
307
+ for (let i = 0; i < this.outputSize; i++) {
308
+ outputErrors[i] = target[i] - output[i];
309
+ outputDeltas[i] = outputErrors[i] * this.sigmoidDerivative(output[i]);
310
+ }
311
+
312
+ // Hidden layer error
313
+ const hiddenErrors = Array(this.hiddenSize).fill(0);
314
+ const hiddenDeltas = Array(this.hiddenSize).fill(0);
315
+ for (let i = 0; i < this.hiddenSize; i++) {
316
+ for (let j = 0; j < this.outputSize; j++) {
317
+ hiddenErrors[i] += outputDeltas[j] * this.weights2[j][i];
318
+ }
319
+ hiddenDeltas[i] = hiddenErrors[i] * this.sigmoidDerivative(hidden[i]);
320
+ }
321
+
322
+ // Update weights and biases
323
+ for (let i = 0; i < this.outputSize; i++) {
324
+ for (let j = 0; j < this.hiddenSize; j++) {
325
+ this.weights2[i][j] += this.learningRate * outputDeltas[i] * hidden[j];
326
+ }
327
+ this.bias2[i] += this.learningRate * outputDeltas[i];
328
+ }
329
+
330
+ for (let i = 0; i < this.hiddenSize; i++) {
331
+ for (let j = 0; j < this.inputSize; j++) {
332
+ this.weights1[i][j] += this.learningRate * hiddenDeltas[i] * input[j];
333
+ }
334
+ this.bias1[i] += this.learningRate * hiddenDeltas[i];
335
+ }
336
+
337
+ // Return error
338
+ return outputErrors.reduce((sum, err) => sum + Math.abs(err), 0) / outputErrors.length;
339
+ }
340
+
341
+ // Save model to JSON
342
+ toJSON() {
343
+ return {
344
+ inputSize: this.inputSize,
345
+ hiddenSize: this.hiddenSize,
346
+ outputSize: this.outputSize,
347
+ weights1: this.weights1,
348
+ weights2: this.weights2,
349
+ bias1: this.bias1,
350
+ bias2: this.bias2
351
+ };
352
+ }
353
+
354
+ // Load model from JSON
355
+ static fromJSON(json) {
356
+ const net = new NeuralNetwork(json.inputSize, json.hiddenSize, json.outputSize);
357
+ net.weights1 = json.weights1;
358
+ net.weights2 = json.weights2;
359
+ net.bias1 = json.bias1;
360
+ net.bias2 = json.bias2;
361
+ return net;
362
+ }
363
+ }
364
+
365
+ // Audio Feature Extractor
366
+ class AudioFeatureExtractor {
367
+ constructor() {
368
+ this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
369
+ this.analyser = this.audioContext.createAnalyser();
370
+ this.analyser.fftSize = 512;
371
+ this.bufferLength = this.analyser.frequencyBinCount;
372
+ this.dataArray = new Uint8Array(this.bufferLength);
373
+ this.sampleRate = this.audioContext.sampleRate;
374
+
375
+ // For spectrogram
376
+ this.spectrogramBuffer = [];
377
+ this.maxSpectrogramLength = 30; // Number of frames to keep
378
+ }
379
+
380
+ async startRecording(stream, onAudioProcess) {
381
+ this.audioSource = this.audioContext.createMediaStreamSource(stream);
382
+ this.audioSource.connect(this.analyser);
383
+
384
+ // For recording audio data
385
+ this.recorder = new MediaRecorder(stream);
386
+ this.chunks = [];
387
+ this.recorder.ondataavailable = e => this.chunks.push(e.data);
388
+ this.recorder.start();
389
+
390
+ // Process audio
391
+ const process = () => {
392
+ this.analyser.getByteFrequencyData(this.dataArray);
393
+
394
+ // Add to spectrogram buffer
395
+ this.spectrogramBuffer.push(new Uint8Array(this.dataArray));
396
+ if (this.spectrogramBuffer.length > this.maxSpectrogramLength) {
397
+ this.spectrogramBuffer.shift();
398
+ }
399
+
400
+ onAudioProcess(this.dataArray);
401
+ this.rafId = requestAnimationFrame(process);
402
+ };
403
+
404
+ process();
405
+ }
406
+
407
+ stopRecording() {
408
+ if (this.rafId) {
409
+ cancelAnimationFrame(this.rafId);
410
+ }
411
+
412
+ return new Promise((resolve) => {
413
+ if (!this.recorder) {
414
+ resolve(null);
415
+ return;
416
+ }
417
+
418
+ this.recorder.onstop = async () => {
419
+ const blob = new Blob(this.chunks, { type: 'audio/wav' });
420
+ const audioBuffer = await this.decodeAudioData(blob);
421
+ resolve(audioBuffer);
422
+ };
423
+
424
+ this.recorder.stop();
425
+ if (this.audioSource) {
426
+ this.audioSource.disconnect();
427
+ }
428
+ });
429
+ }
430
+
431
+ async decodeAudioData(blob) {
432
+ const arrayBuffer = await blob.arrayBuffer();
433
+ return new Promise((resolve, reject) => {
434
+ this.audioContext.decodeAudioData(arrayBuffer, resolve, reject);
435
+ });
436
+ }
437
+
438
+ extractMFCC(audioBuffer) {
439
+ // Simplified MFCC feature extraction
440
+ // In a real application, you'd want a full MFCC implementation
441
+
442
+ // First get FFT data
443
+ this.analyser.getByteFrequencyData(this.dataArray);
444
+
445
+ // Convert to power spectrum
446
+ const powerSpectrum = Array.from(this.dataArray).map(val => val / 255);
447
+
448
+ // Simple feature extraction - using mean of bands as approximation
449
+ const bands = 13; // Standard number of MFCC coefficients
450
+ const bandSize = Math.floor(powerSpectrum.length / bands);
451
+ const features = [];
452
+
453
+ for (let i = 0; i < bands; i++) {
454
+ const start = i * bandSize;
455
+ const end = (i + 1) * bandSize;
456
+ const band = powerSpectrum.slice(start, end);
457
+ const mean = band.reduce((sum, val) => sum + val, 0) / band.length;
458
+ features.push(mean);
459
+ }
460
+
461
+ // Add delta features (approximation)
462
+ if (features.length > 1) {
463
+ for (let i = 1; i < features.length; i++) {
464
+ features.push(features[i] - features[i-1]);
465
+ }
466
+ }
467
+
468
+ return features;
469
+ }
470
+
471
+ getSpectrogramData() {
472
+ return this.spectrogramBuffer;
473
+ }
474
+ }
475
+
476
+ // Main Application
477
+ class AudioCommandApp {
478
+ constructor() {
479
+ this.featureExtractor = new AudioFeatureExtractor();
480
+ this.model = null;
481
+ this.commands = [];
482
+ this.trainingData = {};
483
+ this.currentCommand = null;
484
+ this.isRecording = false;
485
+ this.isTraining = false;
486
+ this.isPredicting = false;
487
+ this.minSamples = 5; // Minimum samples per command needed for training
488
+ this.inputSize = 26; // Number of MFCC features (13 + 13 deltas)
489
+ this.hiddenSize = 16; // Size of hidden layer
490
+
491
+ // DOM elements
492
+ this.commandList = document.getElementById('commandList');
493
+ this.newCommandInput = document.getElementById('newCommandInput');
494
+ this.addCommandBtn = document.getElementById('addCommandBtn');
495
+ this.recordTrainBtn = document.getElementById('recordTrainBtn');
496
+ this.trainBtn = document.getElementById('trainBtn');
497
+ this.testBtn = document.getElementById('testBtn');
498
+ this.recordPredictBtn = document.getElementById('recordPredictBtn');
499
+ this.continuousBtn = document.getElementById('continuousBtn');
500
+ this.currentCommandDisplay = document.getElementById('currentCommand');
501
+ this.sampleCount = document.getElementById('sampleCount');
502
+ this.trainingProgressBar = document.getElementById('trainingProgressBar');
503
+ this.trainingProgressText = document.getElementById('trainingProgressText');
504
+ this.recognizedCommand = document.getElementById('recognizedCommand');
505
+ this.predictionConfidence = document.getElementById('predictionConfidence');
506
+ this.confidenceBar = document.getElementById('confidenceBar');
507
+ this.clearStorageBtn = document.getElementById('clearStorageBtn');
508
+
509
+ // Visualization canvases
510
+ this.waveformCanvas = document.getElementById('waveformCanvas');
511
+ this.waveformCtx = this.waveformCanvas.getContext('2d');
512
+ this.spectrogramCanvas = document.getElementById('spectrogramCanvas');
513
+ this.spectrogramCtx = this.spectrogramCanvas.getContext('2d');
514
+ this.networkVisualization = document.getElementById('networkVisualization');
515
+
516
+ // Setup UI
517
+ this.setupCanvas();
518
+ this.setupEventListeners();
519
+ this.loadFromStorage();
520
+ this.renderCommandList();
521
+ this.visualizeNetwork();
522
+ }
523
+
524
+ setupCanvas() {
525
+ const width = this.audioVisualization.clientWidth;
526
+ const height = this.audioVisualization.clientHeight;
527
+
528
+ this.waveformCanvas.width = width;
529
+ this.waveformCanvas.height = height;
530
+ this.spectrogramCanvas.width = width;
531
+ this.spectrogramCanvas.height = height;
532
+
533
+ // Initially clear canvases
534
+ this.clearVisualizations();
535
+ }
536
+
537
+ setupEventListeners() {
538
+ // Add new command
539
+ this.addCommandBtn.addEventListener('click', () => {
540
+ const command = this.newCommandInput.value.trim().toLowerCase();
541
+ if (command && !this.commands.includes(command)) {
542
+ this.commands.push(command);
543
+ this.trainingData[command] = [];
544
+ this.newCommandInput.value = '';
545
+ this.saveToStorage();
546
+ this.renderCommandList();
547
+ }
548
+ });
549
+
550
+ // Record training sample
551
+ this.recordTrainBtn.addEventListener('click', () => {
552
+ if (this.currentCommand) {
553
+ this.toggleTrainRecording();
554
+ } else {
555
+ alert('Please select a command to train first');
556
+ }
557
+ });
558
+
559
+ // Train model
560
+ this.trainBtn.addEventListener('click', () => this.trainModel());
561
+
562
+ // Test model
563
+ this.testBtn.addEventListener('click', () => this.testModel());
564
+
565
+ // Record prediction
566
+ this.recordPredictBtn.addEventListener('click', () => this.togglePredictRecording());
567
+
568
+ // Continuous recognition mode
569
+ this.continuousBtn.addEventListener('click', () => this.toggleContinuousMode());
570
+
571
+ // Clear storage
572
+ this.clearStorageBtn.addEventListener('click', () => {
573
+ if (confirm('Clear all training data and commands?')) {
574
+ localStorage.clear();
575
+ this.commands = [];
576
+ this.trainingData = {};
577
+ this.model = null;
578
+ this.currentCommand = null;
579
+ this.saveToStorage();
580
+ this.renderCommandList();
581
+ this.updateTrainingUI();
582
+ this.clearVisualizations();
583
+ this.visualizeNetwork();
584
+ }
585
+ });
586
+
587
+ // Handle window resize
588
+ window.addEventListener('resize', () => {
589
+ this.setupCanvas();
590
+ if (this.isRecording) {
591
+ this.drawVisualizations(this.featureExtractor.getSpectrogramData());
592
+ }
593
+ });
594
+ }
595
+
596
+ async toggleTrainRecording() {
597
+ try {
598
+ if (this.isRecording) {
599
+ // Stop recording
600
+ this.isRecording = false;
601
+ this.recordTrainBtn.innerHTML = '<i class="fas fa-microphone mr-2"></i> Record Sample';
602
+ this.recordTrainBtn.classList.remove('bg-red-600', 'hover:bg-red-500');
603
+ this.recordTrainBtn.classList.add('gradient-bg');
604
+
605
+ const audioBuffer = await this.featureExtractor.stopRecording();
606
+ if (audioBuffer) {
607
+ const features = this.featureExtractor.extractMFCC(audioBuffer);
608
+ this.trainingData[this.currentCommand].push(features);
609
+ this.saveToStorage();
610
+ this.updateTrainingUI();
611
+
612
+ // Show notification
613
+ const notification = document.createElement('div');
614
+ notification.className = 'fixed bottom-4 right-4 bg-green-600 text-white px-4 py-2 rounded-lg shadow-lg transition transform translate-y-10 opacity-0';
615
+ notification.innerHTML = 'Sample recorded successfully';
616
+ document.body.appendChild(notification);
617
+
618
+ setTimeout(() => {
619
+ notification.classList.add('opacity-100', 'translate-y-0');
620
+ setTimeout(() => {
621
+ notification.classList.remove('opacity-100', 'translate-y-0');
622
+ setTimeout(() => notification.remove(), 300);
623
+ }, 2000);
624
+ }, 10);
625
+ }
626
+
627
+ this.clearVisualizations();
628
+ } else {
629
+ // Start recording
630
+ this.isRecording = true;
631
+ this.recordTrainBtn.innerHTML = '<i class="fas fa-stop mr-2"></i> Stop Recording';
632
+ this.recordTrainBtn.classList.add('bg-red-600', 'hover:bg-red-500');
633
+ this.recordTrainBtn.classList.remove('gradient-bg');
634
+
635
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
636
+ this.featureExtractor.startRecording(stream, (data) => {
637
+ this.drawVisualizations(this.featureExtractor.getSpectrogramData());
638
+ });
639
+ }
640
+ } catch (error) {
641
+ console.error('Recording error:', error);
642
+ this.isRecording = false;
643
+ this.recordTrainBtn.innerHTML = '<i class="fas fa-microphone mr-2"></i> Record Sample';
644
+ this.recordTrainBtn.classList.add('gradient-bg');
645
+ this.recordTrainBtn.classList.remove('bg-red-600', 'hover:bg-red-500');
646
+ alert('Error accessing microphone: ' + error.message);
647
+ }
648
+ }
649
+
650
+ async togglePredictRecording() {
651
+ try {
652
+ if (this.isPredicting) {
653
+ // Stop recording
654
+ this.isPredicting = false;
655
+ this.recordPredictBtn.innerHTML = '<i class="fas fa-microphone mr-2"></i> Record Command';
656
+ this.recordPredictBtn.classList.remove('bg-red-600', 'hover:bg-red-500');
657
+ this.recordPredictBtn.classList.add('gradient-bg', 'pulse-animation');
658
+
659
+ await this.featureExtractor.stopRecording();
660
+ this.clearVisualizations();
661
+ } else {
662
+ // Start recording
663
+ this.isPredicting = true;
664
+ this.recordPredictBtn.innerHTML = '<i class="fas fa-stop mr-2"></i> Stop Recording';
665
+ this.recordPredictBtn.classList.add('bg-red-600', 'hover:bg-red-500');
666
+ this.recordPredictBtn.classList.remove('gradient-bg', 'pulse-animation');
667
+
668
+ this.recognizedCommand.textContent = 'Listening...';
669
+ this.predictionConfidence.textContent = '--% confidence';
670
+ this.confidenceBar.style.width = '0%';
671
+
672
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
673
+ this.featureExtractor.startRecording(stream, (data) => {
674
+ this.drawVisualizations(this.featureExtractor.getSpectrogramData());
675
+
676
+ if (this.model) {
677
+ const features = this.featureExtractor.extractMFCC();
678
+ this.predictCommand(features);
679
+ }
680
+ });
681
+ }
682
+ } catch (error) {
683
+ console.error('Prediction error:', error);
684
+ this.isPredicting = false;
685
+ this.recordPredictBtn.innerHTML = '<i class="fas fa-microphone mr-2"></i> Record Command';
686
+ this.recordPredictBtn.classList.add('gradient-bg', 'pulse-animation');
687
+ this.recordPredictBtn.classList.remove('bg-red-600', 'hover:bg-red-500');
688
+ alert('Error accessing microphone: ' + error.message);
689
+ }
690
+ }
691
+
692
+ toggleContinuousMode() {
693
+ // To be implemented
694
+ alert('Continuous mode coming soon!');
695
+ }
696
+
697
+ trainModel() {
698
+ if (this.commands.length < 1) {
699
+ alert('Please add at least one command first');
700
+ return;
701
+ }
702
+
703
+ // Check if we have enough samples for each command
704
+ const commandsWithEnoughSamples = this.commands.filter(cmd =>
705
+ this.trainingData[cmd] && this.trainingData[cmd].length >= this.minSamples
706
+ );
707
+
708
+ if (commandsWithEnoughSamples.length < 1) {
709
+ alert(`Please record at least ${this.minSamples} samples for each command you want to train`);
710
+ return;
711
+ }
712
+
713
+ this.isTraining = true;
714
+ this.trainBtn.disabled = true;
715
+ this.recordTrainBtn.disabled = true;
716
+
717
+ // Prepare training data
718
+ const trainingData = [];
719
+ const targets = [];
720
+ const commandIndex = {};
721
+ commandsWithEnoughSamples.forEach((cmd, idx) => {
722
+ commandIndex[cmd] = idx;
723
+ this.trainingData[cmd].forEach(features => {
724
+ trainingData.push(features);
725
+ // One-hot encoded target
726
+ const target = Array(commandsWithEnoughSamples.length).fill(0);
727
+ target[idx] = 1;
728
+ targets.push(target);
729
+ });
730
+ });
731
+
732
+ // Initialize or reset model
733
+ if (!this.model) {
734
+ this.model = new NeuralNetwork(this.inputSize, this.hiddenSize, commandsWithEnoughSamples.length);
735
+ }
736
+
737
+ // Train the model
738
+ const epochs = 200;
739
+ const batchSize = 16;
740
+ const progressStep = Math.ceil(epochs / 20);
741
+
742
+ const train = async (epoch = 0) => {
743
+ if (epoch >= epochs) {
744
+ // Training complete
745
+ this.isTraining = false;
746
+ this.trainBtn.disabled = false;
747
+ this.recordTrainBtn.disabled = false;
748
+
749
+ // Visualize the trained network
750
+ this.visualizeNetwork();
751
+
752
+ // Show notification
753
+ const notification = document.createElement('div');
754
+ notification.className = 'fixed bottom-4 right-4 bg-green-600 text-white px-4 py-2 rounded-lg shadow-lg transition transform translate-y-10 opacity-0';
755
+ notification.innerHTML = 'Training complete! Model is ready';
756
+ document.body.appendChild(notification);
757
+
758
+ setTimeout(() => {
759
+ notification.classList.add('opacity-100', 'translate-y-0');
760
+ setTimeout(() => {
761
+ notification.classList.remove('opacity-100', 'translate-y-0');
762
+ setTimeout(() => notification.remove(), 300);
763
+ }, 2000);
764
+ }, 10);
765
+
766
+ return;
767
+ }
768
+
769
+ // Shuffle training data
770
+ const shuffledIndices = Array.from({ length: trainingData.length }, (_, i) => i);
771
+ for (let i = shuffledIndices.length - 1; i > 0; i--) {
772
+ const j = Math.floor(Math.random() * (i + 1));
773
+ [shuffledIndices[i], shuffledIndices[j]] = [shuffledIndices[j], shuffledIndices[i]];
774
+ }
775
+
776
+ // Train in mini-batches
777
+ let totalError = 0;
778
+ for (let i = 0; i < Math.ceil(trainingData.length / batchSize); i++) {
779
+ const batchIndices = shuffledIndices.slice(i * batchSize, (i + 1) * batchSize);
780
+
781
+ for (const idx of batchIndices) {
782
+ const error = this.model.train(trainingData[idx], targets[idx]);
783
+ totalError += error;
784
+ }
785
+ }
786
+
787
+ const avgError = totalError / trainingData.length;
788
+
789
+ // Update UI
790
+ if (epoch % progressStep === 0 || epoch === epochs - 1) {
791
+ const progress = Math.floor((epoch / epochs) * 100);
792
+ this.trainingProgressBar.style.width = `${progress}%`;
793
+ this.trainingProgressText.textContent = `Epoch ${epoch + 1}/${epochs} (Error: ${avgError.toFixed(4)})`;
794
+
795
+ // Visualize network occasionally
796
+ if (epoch % (progressStep * 2) === 0) {
797
+ this.visualizeNetwork();
798
+ }
799
+ }
800
+
801
+ // Schedule next epoch
802
+ await new Promise(resolve => setTimeout(resolve, 0));
803
+ requestAnimationFrame(() => train(epoch + 1));
804
+ };
805
+
806
+ // Start training
807
+ train();
808
+ }
809
+
810
+ testModel() {
811
+ if (!this.model || this.commands.length < 1) {
812
+ alert('Please train at least one command first');
813
+ return;
814
+ }
815
+
816
+ // Simple test of the model with training data
817
+ const summary = {};
818
+ let totalCorrect = 0;
819
+ let totalSamples = 0;
820
+
821
+ this.commands.forEach(cmd => {
822
+ if (!this.trainingData[cmd] || this.trainingData[cmd].length === 0) return;
823
+
824
+ summary[cmd] = { correct: 0, total: this.trainingData[cmd].length };
825
+ totalSamples += this.trainingData[cmd].length;
826
+
827
+ this.trainingData[cmd].forEach(features => {
828
+ const prediction = this.model.forward(features).output;
829
+ const predictedIndex = prediction.indexOf(Math.max(...prediction));
830
+ const actualIndex = this.commands.indexOf(cmd);
831
+
832
+ if (predictedIndex === actualIndex) {
833
+ summary[cmd].correct++;
834
+ totalCorrect++;
835
+ }
836
+ });
837
+ });
838
+
839
+ // Display test results
840
+ let resultText = 'Model Test Results\n\n';
841
+ this.commands.forEach(cmd => {
842
+ if (!summary[cmd]) return;
843
+ const accuracy = Math.round((summary[cmd].correct / summary[cmd].total) * 100);
844
+ resultText += `${cmd}: ${summary[cmd].correct}/${summary[cmd].total} (${accuracy}%)\n`;
845
+ });
846
+
847
+ resultText += `\nOverall Accuracy: ${Math.round((totalCorrect / totalSamples) * 100)}%`;
848
+ alert(resultText);
849
+ }
850
+
851
+ predictCommand(features) {
852
+ if (!this.model || this.commands.length < 1) return;
853
+
854
+ const { output, hidden } = this.model.forward(features);
855
+ const maxConfidence = Math.max(...output);
856
+ const predictedIndex = output.indexOf(maxConfidence);
857
+ const confidence = Math.round(maxConfidence * 100);
858
+
859
+ if (confidence > 30) { // Minimum confidence threshold
860
+ const predictedCommand = this.commands[predictedIndex];
861
+ this.recognizedCommand.textContent = predictedCommand;
862
+ this.predictionConfidence.textContent = `${confidence}% confidence`;
863
+ this.confidenceBar.style.width = `${confidence}%`;
864
+
865
+ // Visualize network activation
866
+ this.visualizeNetwork(hidden, predictedIndex, confidence);
867
+ } else {
868
+ this.recognizedCommand.textContent = 'Not recognized';
869
+ this.predictionConfidence.textContent = 'Low confidence';
870
+ this.confidenceBar.style.width = '0%';
871
+ }
872
+ }
873
+
874
+ // Visualization methods
875
+ drawVisualizations(spectrogramBuffer) {
876
+ if (!spectrogramBuffer || spectrogramBuffer.length === 0) return;
877
+
878
+ const width = this.waveformCanvas.width;
879
+ const height = this.waveformCanvas.height;
880
+
881
+ // Clear canvases
882
+ this.waveformCtx.clearRect(0, 0, width, height);
883
+ this.spectrogramCtx.clearRect(0, 0, width, height);
884
+
885
+ // Draw waveform (simplified)
886
+ this.waveformCtx.beginPath();
887
+ this.waveformCtx.strokeStyle = '#a777e3';
888
+ this.waveformCtx.lineWidth = 2;
889
+
890
+ const currentData = spectrogramBuffer[spectrogramBuffer.length - 1];
891
+ const sliceWidth = width / currentData.length;
892
+
893
+ for (let i = 0; i < currentData.length; i++) {
894
+ const v = currentData[i] / 255.0;
895
+ const y = (1 - v) * height;
896
+
897
+ if (i === 0) {
898
+ this.waveformCtx.moveTo(0, y);
899
+ } else {
900
+ this.waveformCtx.lineTo(i * sliceWidth, y);
901
+ }
902
+ }
903
+
904
+ this.waveformCtx.stroke();
905
+
906
+ // Draw spectrogram
907
+ const spectrogramHeight = height;
908
+ const spectrogramWidth = width;
909
+ const binHeight = spectrogramHeight / currentData.length;
910
+
911
+ for (let i = 0; i < spectrogramBuffer.length; i++) {
912
+ const colData = spectrogramBuffer[i];
913
+ const x = spectrogramWidth - (spectrogramBuffer.length - i);
914
+
915
+ for (let j = 0; j < colData.length; j++) {
916
+ const value = colData[j] / 255;
917
+ const h = 240; // Hue (blue)
918
+ const s = 100; // Saturation
919
+ const l = value * 100; // Lightness
920
+
921
+ this.spectrogramCtx.fillStyle = `hsl(${h}, ${s}%, ${l}%)`;
922
+ this.spectrogramCtx.fillRect(x, j * binHeight, 1, binHeight);
923
+ }
924
+ }
925
+ }
926
+
927
+ clearVisualizations() {
928
+ this.waveformCtx.clearRect(0, 0, this.waveformCanvas.width, this.waveformCanvas.height);
929
+ this.spectrogramCtx.clearRect(0, 0, this.spectrogramCanvas.width, this.spectrogramCanvas.height);
930
+
931
+ // Draw empty state
932
+ this.waveformCtx.fillStyle = 'rgba(255, 255, 255, 0.05)';
933
+ this.waveformCtx.fillRect(0, 0, this.waveformCanvas.width, this.waveformCanvas.height);
934
+ this.spectrogramCtx.fillStyle = 'rgba(255, 255, 255, 0.05)';
935
+ this.spectrogramCtx.fillRect(0, 0, this.spectrogramCanvas.width, this.spectrogramCanvas.height);
936
+
937
+ this.waveformCtx.fillStyle = 'white';
938
+ this.waveformCtx.font = '14px Arial';
939
+ this.waveformCtx.textAlign = 'center';
940
+ this.waveformCtx.fillText('No audio data', this.waveformCanvas.width / 2, this.waveformCanvas.height / 2);
941
+ }
942
+
943
+ visualizeNetwork(hiddenActivations = null, outputIndex = -1, confidence = 0) {
944
+ // Clear network visualization
945
+ this.networkVisualization.innerHTML = '';
946
+
947
+ if (!this.model) {
948
+ // Show placeholder if no model exists
949
+ const placeholder = document.createElement('div');
950
+ placeholder.className = 'text-gray-400 text-center py-12';
951
+ placeholder.textContent = 'No trained model. Train with at least 5 samples per command.';
952
+ this.networkVisualization.appendChild(placeholder);
953
+ return;
954
+ }
955
+
956
+ // Create layers container
957
+ const layersContainer = document.createElement('div');
958
+ layersContainer.className = 'flex items-center justify-center h-full';
959
+ this.networkVisualization.appendChild(layersContainer);
960
+
961
+ // Input layer
962
+ const inputLayer = document.createElement('div');
963
+ inputLayer.className = 'flex flex-col items-center mx-2';
964
+ const inputLabel = document.createElement('div');
965
+ inputLabel.className = 'text-xs text-gray-400 mb-1';
966
+ inputLabel.textContent = 'Input Features';
967
+ inputLayer.appendChild(inputLabel);
968
+
969
+ const inputNeurons = document.createElement('div');
970
+ inputNeurons.className = 'flex flex-col items-center';
971
+ for (let i = 0; i < this.model.inputSize; i++) {
972
+ const neuron = document.createElement('div');
973
+ neuron.className = 'neuron';
974
+ inputNeurons.appendChild(neuron);
975
+ }
976
+ inputLayer.appendChild(inputNeurons);
977
+ layersContainer.appendChild(inputLayer);
978
+
979
+ // Connections between input and hidden
980
+ for (let i = 0; i < this.model.inputSize; i++) {
981
+ for (let j = 0; j < this.model.hiddenSize; j++) {
982
+ const connection = document.createElement('div');
983
+ connection.className = 'connection';
984
+ connection.style.width = '60px';
985
+ connection.style.left = (30 + i * 0) + 'px'; // Adjusted for display
986
+ connection.style.top = (20 + i * 10) + 'px'; // Simplified positioning
987
+ layersContainer.appendChild(connection);
988
+ }
989
+ }
990
+
991
+ // Hidden layer
992
+ const hiddenLayer = document.createElement('div');
993
+ hiddenLayer.className = 'flex flex-col items-center mx-2';
994
+ const hiddenLabel = document.createElement('div');
995
+ hiddenLabel.className = 'text-xs text-gray-400 mb-1';
996
+ hiddenLabel.textContent = 'Hidden Layer';
997
+ hiddenLayer.appendChild(hiddenLabel);
998
+
999
+ const hiddenNeurons = document.createElement('div');
1000
+ hiddenNeurons.className = 'flex flex-col items-center';
1001
+ for (let i = 0; i < this.model.hiddenSize; i++) {
1002
+ const neuron = document.createElement('div');
1003
+ neuron.className = 'neuron';
1004
+ if (hiddenActivations) {
1005
+ const activation = hiddenActivations[i];
1006
+ const intensity = Math.min(255, Math.floor(activation * 200));
1007
+ neuron.style.backgroundColor = `rgba(167, 119, 227, ${activation})`;
1008
+ if (activation > 0.6) neuron.classList.add('active');
1009
+ }
1010
+ hiddenNeurons.appendChild(neuron);
1011
+ }
1012
+ hiddenLayer.appendChild(hiddenNeurons);
1013
+ layersContainer.appendChild(hiddenLayer);
1014
+
1015
+ // Connections between hidden and output
1016
+ for (let i = 0; i < this.model.hiddenSize; i++) {
1017
+ for (let j = 0; j < this.model.outputSize; j++) {
1018
+ const connection = document.createElement('div');
1019
+ connection.className = 'connection';
1020
+ connection.style.width = '60px';
1021
+ layersContainer.appendChild(connection);
1022
+ }
1023
+ }
1024
+
1025
+ // Output layer
1026
+ const outputLayer = document.createElement('div');
1027
+ outputLayer.className = 'flex flex-col items-center mx-2';
1028
+ const outputLabel = document.createElement('div');
1029
+ outputLabel.className = 'text-xs text-gray-400 mb-1';
1030
+ outputLabel.textContent = 'Output';
1031
+ outputLayer.appendChild(outputLabel);
1032
+
1033
+ const outputNeurons = document.createElement('div');
1034
+ outputNeurons.className = 'flex flex-col items-center';
1035
+ for (let i = 0; i < this.model.outputSize; i++) {
1036
+ const neuron = document.createElement('div');
1037
+ neuron.className = 'neuron';
1038
+
1039
+ if (outputIndex >= 0) {
1040
+ if (i === outputIndex) {
1041
+ neuron.style.backgroundColor = `rgba(74, 222, 128, ${confidence / 100})`;
1042
+ if (confidence > 50) neuron.classList.add('active');
1043
+ } else {
1044
+ neuron.style.opacity = '0.3';
1045
+ }
1046
+ }
1047
+
1048
+ outputNeurons.appendChild(neuron);
1049
+
1050
+ // Add command labels
1051
+ if (this.commands[i]) {
1052
+ const label = document.createElement('div');
1053
+ label.className = 'text-xs text-center mt-1';
1054
+ label.textContent = this.commands[i];
1055
+ outputNeurons.appendChild(label);
1056
+ }
1057
+ }
1058
+ outputLayer.appendChild(outputNeurons);
1059
+ layersContainer.appendChild(outputLayer);
1060
+ }
1061
+
1062
+ // Command list rendering
1063
+ renderCommandList() {
1064
+ this.commandList.innerHTML = '';
1065
+
1066
+ this.commands.forEach(cmd => {
1067
+ const samples = this.trainingData[cmd] ? this.trainingData[cmd].length : 0;
1068
+ const statusColor = samples >= this.minSamples ? 'bg-green-500' :
1069
+ samples > 0 ? 'bg-yellow-500' : 'bg-red-500';
1070
+ const statusText = samples >= this.minSamples ? 'Ready' :
1071
+ samples > 0 ? `${samples}/${this.minSamples}` : 'New';
1072
+
1073
+ const card = document.createElement('div');
1074
+ card.className = `command-card bg-gray-700 rounded-lg p-4 cursor-pointer ${this.currentCommand === cmd ? 'glow' : ''}`;
1075
+ card.innerHTML = `
1076
+ <div class="flex justify-between items-center">
1077
+ <h3 class="font-medium">${cmd}</h3>
1078
+ <span class="text-xs ${statusColor} px-2 py-1 rounded-full">${statusText}</span>
1079
+ </div>
1080
+ <div class="waveform mt-2 rounded"></div>
1081
+ <div class="confidence-meter mt-2">
1082
+ <div class="confidence-fill" style="width: ${samples / this.minSamples * 100}%"></div>
1083
+ </div>
1084
+ <div class="text-xs text-gray-400 mt-1">${samples} samples</div>
1085
+ `;
1086
+
1087
+ card.addEventListener('click', () => {
1088
+ this.currentCommand = cmd;
1089
+ this.currentCommandDisplay.textContent = `"${cmd}"`;
1090
+ this.updateTrainingUI();
1091
+
1092
+ // Highlight selected card
1093
+ document.querySelectorAll('.command-card').forEach(c => c.classList.remove('glow'));
1094
+ card.classList.add('glow');
1095
+ });
1096
+
1097
+ this.commandList.appendChild(card);
1098
+ });
1099
+
1100
+ if (this.commands.length === 0) {
1101
+ this.commandList.innerHTML = '<div class="text-center py-8 text-gray-400">No commands added yet</div>';
1102
+ }
1103
+ }
1104
+
1105
+ updateTrainingUI() {
1106
+ if (!this.currentCommand) {
1107
+ this.sampleCount.textContent = '0';
1108
+ return;
1109
+ }
1110
+
1111
+ const samples = this.trainingData[this.currentCommand] ? this.trainingData[this.currentCommand].length : 0;
1112
+ this.sampleCount.textContent = samples;
1113
+
1114
+ // Update training button state
1115
+ this.trainBtn.disabled = this.commands.every(cmd =>
1116
+ !this.trainingData[cmd] || this.trainingData[cmd].length < this.minSamples
1117
+ );
1118
+ }
1119
+
1120
+ // Storage methods
1121
+ saveToStorage() {
1122
+ try {
1123
+ localStorage.setItem('audioCommands', JSON.stringify(this.commands));
1124
+ localStorage.setItem('trainingData', JSON.stringify(this.trainingData));
1125
+
1126
+ if (this.model) {
1127
+ localStorage.setItem('nnModel', JSON.stringify(this.model.toJSON()));
1128
+ }
1129
+ } catch (e) {
1130
+ console.error('Failed to save data:', e);
1131
+ }
1132
+ }
1133
+
1134
+ loadFromStorage() {
1135
+ try {
1136
+ const commands = localStorage.getItem('audioCommands');
1137
+ const trainingData = localStorage.getItem('trainingData');
1138
+ const modelData = localStorage.getItem('nnModel');
1139
+
1140
+ if (commands) this.commands = JSON.parse(commands);
1141
+ if (trainingData) this.trainingData = JSON.parse(trainingData);
1142
+ if (modelData) this.model = NeuralNetwork.fromJSON(JSON.parse(modelData));
1143
+ } catch (e) {
1144
+ console.error('Failed to load data:', e);
1145
+ }
1146
+ }
1147
+ }
1148
+
1149
+ // Initialize the app when DOM is loaded
1150
+ document.addEventListener('DOMContentLoaded', () => {
1151
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
1152
+ alert('Your browser doesn\'t support audio recording. Please try Chrome or Firefox.');
1153
+ return;
1154
+ }
1155
+
1156
+ const app = new AudioCommandApp();
1157
+ window.app = app; // For debugging
1158
+ });
1159
+ </script>
1160
+ <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=LukasBe/voice-command" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
1161
+ </html>