jorisvaneyghen commited on
Commit
01f45fd
·
1 Parent(s): 6c4b783
Files changed (1) hide show
  1. index.html +129 -10
index.html CHANGED
@@ -163,6 +163,7 @@
163
  <button id="prevBar" class="action-button" style="padding: 8px 12px;">◀</button>
164
  <input type="number" id="startBar" value="0" min="0" style="text-align: center;">
165
  <button id="nextBar" class="action-button" style="padding: 8px 12px;">▶</button>
 
166
  </div>
167
  </div>
168
 
@@ -216,8 +217,6 @@
216
  </div>
217
  </div>
218
  </div>
219
- </div>
220
- </div>
221
 
222
  <script type="module">
223
  // Check if the browser supports service workers
@@ -244,8 +243,12 @@
244
  this.detector = new BeatDetector();
245
  this.isProcessing = false;
246
  this.startTime = null;
 
247
  this.audioBuffer = null;
 
 
248
  this.audioContext = null;
 
249
  this.logits = null;
250
  this.bars = null;
251
  this.estimatedBPM = null;
@@ -257,6 +260,7 @@
257
  this.barsToPlay = 4;
258
  this.stepSize = 2;
259
  this.upbeat = 0;
 
260
  this.barsArray = [];
261
 
262
  // Cache storage key
@@ -284,6 +288,14 @@
284
  // Initialize the detector with progress updates
285
  const success = await this.detector.init(pyodide, this.updateInitProgress.bind(this));
286
 
 
 
 
 
 
 
 
 
287
  if (success) {
288
  this.hideInitProgress();
289
  this.enableUploadComponent();
@@ -530,6 +542,7 @@
530
  const stepSizeInput = document.getElementById('stepSize');
531
  const startBarInput = document.getElementById('startBar');
532
  const upbeatInput = document.getElementById('upbeat');
 
533
 
534
  // File System Access API for file selection
535
  uploadArea.addEventListener('click', async () => {
@@ -567,6 +580,26 @@
567
 
568
  cancelButton.addEventListener('click', () => this.cancelProcessing());
569
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
570
  // Bars to play buttons
571
  document.querySelectorAll('.button-group .choice-button[data-value]').forEach(button => {
572
  if (button.closest('.form-group:nth-child(2)')) { // Bars to play group
@@ -1062,16 +1095,21 @@
1062
  });
1063
  };
1064
 
1065
- this.audioContext = new AudioContext({sampleRate: 22050});
1066
  const arrayBuffer = await file.arrayBuffer();
1067
  this.audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
 
 
 
 
1068
 
1069
  await updateProgress(5, 'Starting beat detection...');
1070
 
1071
  this.logits = await this.detector.processAudio(
1072
- this.audioBuffer,
1073
  updateProgress
1074
  );
 
 
1075
 
1076
  // Automatically run logits_to_bars after preprocessing is complete
1077
  await updateProgress(95, 'Running bar detection...');
@@ -1132,7 +1170,6 @@
1132
 
1133
  // Load the audio file for playback (we still need the audio buffer)
1134
  try {
1135
- this.audioContext = new AudioContext({sampleRate: 22050});
1136
  const arrayBuffer = await file.arrayBuffer();
1137
  this.audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
1138
  } catch (error) {
@@ -1355,6 +1392,7 @@
1355
 
1356
  let startTime = this.barsArray[startBar].time;
1357
  let endTime = this.barsArray[endBar].time;
 
1358
 
1359
  // Calculate upbeat duration if upbeat is specified
1360
  if (this.upbeat !== 0) {
@@ -1363,17 +1401,23 @@
1363
 
1364
  // Adjust startTime and endTime by subtracting the upbeatDuration
1365
  startTime = Math.max(0, startTime - upbeatDuration);
1366
- endTime = Math.max(0, endTime - upbeatDuration);
1367
-
 
1368
  console.log(`Upbeat adjustment: ${upbeatDuration.toFixed(2)}s applied`);
1369
  }
1370
 
1371
- const duration = endTime - startTime;
1372
-
1373
  console.log(`Loading audio from ${startTime.toFixed(2)}s to ${endTime.toFixed(2)}s (${duration.toFixed(2)}s)`);
1374
 
1375
  // Extract audio segment
1376
- const audioSegment = this.extractAudioSegment(startTime, endTime);
 
 
 
 
 
 
 
1377
 
1378
  // Convert to WAV and create blob URL
1379
  const wavBlob = this.audioBufferToWav(audioSegment);
@@ -1388,6 +1432,7 @@
1388
 
1389
  extractAudioSegment(startTime, endTime, overlay = true) {
1390
  const sampleRate = this.audioBuffer.sampleRate;
 
1391
  const startSample = Math.floor(startTime * sampleRate);
1392
  const endSample = Math.floor(endTime * sampleRate);
1393
  const segmentLength = endSample - startSample;
@@ -1432,6 +1477,75 @@
1432
  return segmentBuffer;
1433
  }
1434
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1435
  selectBar(barIndex) {
1436
  if (barIndex >= 0 && barIndex < this.barsArray.length) {
1437
  this.currentStartBar = barIndex;
@@ -1546,6 +1660,11 @@
1546
  const i = Math.floor(Math.log(bytes) / Math.log(k));
1547
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
1548
  }
 
 
 
 
 
1549
  }
1550
 
1551
  // Initialize the app when page loads
 
163
  <button id="prevBar" class="action-button" style="padding: 8px 12px;">◀</button>
164
  <input type="number" id="startBar" value="0" min="0" style="text-align: center;">
165
  <button id="nextBar" class="action-button" style="padding: 8px 12px;">▶</button>
166
+ <button id="countIn" class="choice-button" data-value="0">𝄩</button>
167
  </div>
168
  </div>
169
 
 
217
  </div>
218
  </div>
219
  </div>
 
 
220
 
221
  <script type="module">
222
  // Check if the browser supports service workers
 
243
  this.detector = new BeatDetector();
244
  this.isProcessing = false;
245
  this.startTime = null;
246
+ this.audioBuffer_22050 = null;
247
  this.audioBuffer = null;
248
+ this.claves_low_audio_buffer = null;
249
+ this.claves_high_audio_buffer = null;
250
  this.audioContext = null;
251
+ this.audioContext_22050 = null;
252
  this.logits = null;
253
  this.bars = null;
254
  this.estimatedBPM = null;
 
260
  this.barsToPlay = 4;
261
  this.stepSize = 2;
262
  this.upbeat = 0;
263
+ this.countIn = 0;
264
  this.barsArray = [];
265
 
266
  // Cache storage key
 
288
  // Initialize the detector with progress updates
289
  const success = await this.detector.init(pyodide, this.updateInitProgress.bind(this));
290
 
291
+ this.audioContext = new AudioContext({sampleRate: 44100});
292
+ const response_claves_low = await fetch('/claves_low.mp3');
293
+ const arrayBuffer_claves_low = await response_claves_low.arrayBuffer();
294
+ this.claves_low_audio_buffer = await this.audioContext.decodeAudioData(arrayBuffer_claves_low);
295
+ const response_claves_high = await fetch('/claves_high.mp3');
296
+ const arrayBuffer_claves_high = await response_claves_high.arrayBuffer();
297
+ this.claves_high_audio_buffer = await this.audioContext.decodeAudioData(arrayBuffer_claves_high);
298
+
299
  if (success) {
300
  this.hideInitProgress();
301
  this.enableUploadComponent();
 
542
  const stepSizeInput = document.getElementById('stepSize');
543
  const startBarInput = document.getElementById('startBar');
544
  const upbeatInput = document.getElementById('upbeat');
545
+ const countInButton = document.getElementById('countIn');
546
 
547
  // File System Access API for file selection
548
  uploadArea.addEventListener('click', async () => {
 
580
 
581
  cancelButton.addEventListener('click', () => this.cancelProcessing());
582
 
583
+ // Count In button
584
+ countInButton.addEventListener('click', (e) => {
585
+ const value = parseInt(e.target.dataset.value);
586
+ if (value === 0) {
587
+ this.countIn = 2;
588
+ e.target.textContent = '𝄩²';
589
+ e.target.dataset.value = '2';
590
+ } else if (value === 2) {
591
+ this.countIn = 1;
592
+ e.target.textContent = '𝄩¹';
593
+ e.target.dataset.value = '1';
594
+ } else if (value === 1) {
595
+ this.countIn = 0;
596
+ e.target.textContent = '𝄩';
597
+ e.target.dataset.value = '0';
598
+ }
599
+ e.target.classList.toggle('active', this.countIn > 0);
600
+ this.updateAudioPlayer()
601
+ });
602
+
603
  // Bars to play buttons
604
  document.querySelectorAll('.button-group .choice-button[data-value]').forEach(button => {
605
  if (button.closest('.form-group:nth-child(2)')) { // Bars to play group
 
1095
  });
1096
  };
1097
 
 
1098
  const arrayBuffer = await file.arrayBuffer();
1099
  this.audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
1100
+ //create audioBuffer with SR=22050 for beat detection
1101
+ const arrayBuffer_22050 = await file.arrayBuffer();
1102
+ this.audioContext_22050 = new AudioContext({sampleRate: 22050});
1103
+ this.audioBuffer_22050 = await this.audioContext_22050.decodeAudioData(arrayBuffer_22050);
1104
 
1105
  await updateProgress(5, 'Starting beat detection...');
1106
 
1107
  this.logits = await this.detector.processAudio(
1108
+ this.audioBuffer_22050,
1109
  updateProgress
1110
  );
1111
+ this.audioContext_22050 = null;
1112
+ this.audioBuffer_22050 = null;
1113
 
1114
  // Automatically run logits_to_bars after preprocessing is complete
1115
  await updateProgress(95, 'Running bar detection...');
 
1170
 
1171
  // Load the audio file for playback (we still need the audio buffer)
1172
  try {
 
1173
  const arrayBuffer = await file.arrayBuffer();
1174
  this.audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
1175
  } catch (error) {
 
1392
 
1393
  let startTime = this.barsArray[startBar].time;
1394
  let endTime = this.barsArray[endBar].time;
1395
+ const duration = endTime - startTime;
1396
 
1397
  // Calculate upbeat duration if upbeat is specified
1398
  if (this.upbeat !== 0) {
 
1401
 
1402
  // Adjust startTime and endTime by subtracting the upbeatDuration
1403
  startTime = Math.max(0, startTime - upbeatDuration);
1404
+ if (!this.countIn) {
1405
+ endTime = Math.max(0, endTime - upbeatDuration);
1406
+ }
1407
  console.log(`Upbeat adjustment: ${upbeatDuration.toFixed(2)}s applied`);
1408
  }
1409
 
 
 
1410
  console.log(`Loading audio from ${startTime.toFixed(2)}s to ${endTime.toFixed(2)}s (${duration.toFixed(2)}s)`);
1411
 
1412
  // Extract audio segment
1413
+ const overlay = !this.countIn
1414
+ let audioSegment = this.extractAudioSegment(startTime, endTime, overlay);
1415
+
1416
+ // Append countIn
1417
+ if (this.countIn > 0) {
1418
+ const beat_duration = duration / ((endBar - startBar) * this.detectedBeatsPerBar)
1419
+ audioSegment = this.appendCountIn(audioSegment, beat_duration, this.countIn);
1420
+ }
1421
 
1422
  // Convert to WAV and create blob URL
1423
  const wavBlob = this.audioBufferToWav(audioSegment);
 
1432
 
1433
  extractAudioSegment(startTime, endTime, overlay = true) {
1434
  const sampleRate = this.audioBuffer.sampleRate;
1435
+ console.log("sr=", sampleRate);
1436
  const startSample = Math.floor(startTime * sampleRate);
1437
  const endSample = Math.floor(endTime * sampleRate);
1438
  const segmentLength = endSample - startSample;
 
1477
  return segmentBuffer;
1478
  }
1479
 
1480
+ appendCountIn(segmentBuffer, beatDuration, nbCountInBars = 2) {
1481
+ if (!this.claves_high_audio_buffer || !this.claves_low_audio_buffer) {
1482
+ console.warn('Claves audio buffers not available for countIn');
1483
+ return segmentBuffer;
1484
+ }
1485
+
1486
+ const sampleRate = segmentBuffer.sampleRate;
1487
+ const beatDurationSamples = Math.floor(beatDuration * sampleRate);
1488
+
1489
+ // Calculate number of beats to skip for upbeat
1490
+ const beatsToSkip = this.upbeat > 0 ? parseInt(this.upbeat) : 0;
1491
+
1492
+ // Calculate total countIn beats
1493
+ const totalCountInBeats = nbCountInBars * this.detectedBeatsPerBar - beatsToSkip;
1494
+
1495
+ if (totalCountInBeats <= 0) {
1496
+ console.warn('No countIn beats to add after upbeat adjustment');
1497
+ return segmentBuffer;
1498
+ }
1499
+
1500
+ // Calculate the position where the original segment should starts
1501
+ const positionOriginal = (nbCountInBars * this.detectedBeatsPerBar - this.upbeat) * beatDuration;
1502
+ const positionOriginalSamples = Math.floor(positionOriginal * sampleRate);
1503
+
1504
+ // Create new buffer with countIn + original segment
1505
+ const totalLength = positionOriginalSamples + segmentBuffer.length;
1506
+ const newBuffer = this.audioContext.createBuffer(
1507
+ segmentBuffer.numberOfChannels,
1508
+ totalLength,
1509
+ sampleRate
1510
+ );
1511
+
1512
+ for (let channel = 0; channel < segmentBuffer.numberOfChannels; channel++) {
1513
+ const originalData = segmentBuffer.getChannelData(channel);
1514
+ const newData = newBuffer.getChannelData(channel);
1515
+
1516
+ let currentPosition = 0;
1517
+
1518
+ // Add countIn beats
1519
+ for (let beat = 0; beat < totalCountInBeats; beat++) {
1520
+ const isDownbeat = (beat % this.detectedBeatsPerBar) === 0;
1521
+ const clavesBuffer = isDownbeat ? this.claves_high_audio_buffer : this.claves_low_audio_buffer;
1522
+
1523
+ // Copy claves sound at the beat position
1524
+ const clavesData = clavesBuffer.getChannelData(Math.min(channel, clavesBuffer.numberOfChannels - 1));
1525
+ const clavesLength = Math.min(clavesBuffer.length, beatDurationSamples);
1526
+
1527
+ for (let i = 0; i < clavesLength; i++) {
1528
+ if (currentPosition + i < totalLength) {
1529
+ newData[currentPosition + i] += clavesData[i];
1530
+ }
1531
+ }
1532
+
1533
+ currentPosition += beatDurationSamples;
1534
+ }
1535
+
1536
+ // Copy original segment after countIn
1537
+ currentPosition = positionOriginalSamples
1538
+ for (let i = 0; i < originalData.length; i++) {
1539
+ if (currentPosition + i < totalLength) {
1540
+ newData[currentPosition + i] = originalData[i];
1541
+ }
1542
+ }
1543
+ }
1544
+
1545
+ console.log(`Added ${totalCountInBeats} countIn beats (${nbCountInBars} bars, skipped ${beatsToSkip} upbeat beats)`);
1546
+ return newBuffer;
1547
+ }
1548
+
1549
  selectBar(barIndex) {
1550
  if (barIndex >= 0 && barIndex < this.barsArray.length) {
1551
  this.currentStartBar = barIndex;
 
1660
  const i = Math.floor(Math.log(bytes) / Math.log(k));
1661
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
1662
  }
1663
+
1664
+ cancelProcessing() {
1665
+ //todo
1666
+ return undefined;
1667
+ }
1668
  }
1669
 
1670
  // Initialize the app when page loads