jorisvaneyghen commited on
Commit
34bac53
·
1 Parent(s): 9eb2aa3

refactor bars data struct; first and last bar fixing

Browse files
Files changed (4) hide show
  1. README.md +2 -2
  2. index.html +41 -33
  3. logits_to_bars.py +54 -1
  4. sw.js +1 -1
README.md CHANGED
@@ -19,11 +19,11 @@ python app.py
19
  open in browser: http://localhost:5000/
20
 
21
  ## Todo's
22
- - Fix first and last measure length
23
  - Save loop to file
24
  - Custom Bar fix (half time, double time, time measure, force constant tempo/time measure)
25
  - Add custom labels to bars (intro, chorus1)
26
  - Implement cancel
27
  - highlight loop section in bar list + higlight current played bar
28
  - adjust available choice buttons on tempo and time signature
29
- - alternative loop sections selection based on labeled bars (then click bar in list will not change start loop section, but current played bar)
 
 
19
  open in browser: http://localhost:5000/
20
 
21
  ## Todo's
 
22
  - Save loop to file
23
  - Custom Bar fix (half time, double time, time measure, force constant tempo/time measure)
24
  - Add custom labels to bars (intro, chorus1)
25
  - Implement cancel
26
  - highlight loop section in bar list + higlight current played bar
27
  - adjust available choice buttons on tempo and time signature
28
+ - alternative loop sections selection based on labeled bars (then click bar in list will not change start loop section, but current played bar)
29
+ - share the same audio context in AudioStretchPlayer as in BeatDetectorApp (SR fixed on 44100). And avoid needless endcode / decode for the audio buffer
index.html CHANGED
@@ -274,7 +274,7 @@
274
  this.stepSize = 2;
275
  this.upbeat = 0;
276
  this.countIn = 0;
277
- this.barsArray = [];
278
 
279
  // Cache storage key
280
  this.CACHE_STORAGE_KEY = 'beatDetectionCache';
@@ -469,7 +469,13 @@
469
 
470
  getCachedResults(file) {
471
  const fileKey = this.generateFileKey(file);
472
- return this.cache[fileKey] || null;
 
 
 
 
 
 
473
  }
474
 
475
  async cacheResults(file, results) {
@@ -778,7 +784,7 @@
778
 
779
  startBarInput.addEventListener('change', (e) => {
780
  let barNum = parseInt(e.target.value)
781
- this.currentStartBar = this.barsArray.findIndex(bar => bar.barNum === barNum);
782
  this.updateAudioPlayer();
783
  });
784
 
@@ -1251,7 +1257,7 @@
1251
  detectedBeatsPerBar.textContent = this.detectedBeatsPerBar !== null ? this.detectedBeatsPerBar : '--';
1252
 
1253
  // Display bars if available and initialize destroy
1254
- if (this.bars && Object.keys(this.bars).length > 0) {
1255
  this.displayBars();
1256
  document.getElementById('barsResults').style.display = 'block';
1257
 
@@ -1287,7 +1293,7 @@
1287
  const barsList = document.getElementById('barsList');
1288
  barsList.innerHTML = '';
1289
 
1290
- if (!this.bars || Object.keys(this.bars).length === 0) {
1291
  barsList.innerHTML = '<div class="bar-item">No bars detected</div>';
1292
 
1293
  // Initialize player with full audio if no bars but audio exists
@@ -1297,20 +1303,21 @@
1297
  return;
1298
  }
1299
 
1300
- // Convert bars object to array and sort by bar number
1301
- this.barsArray = Object.entries(this.bars)
1302
- .map(([barNum, time]) => ({barNum: parseInt(barNum), time: parseFloat(time)}))
1303
- .sort((a, b) => a.barNum - b.barNum);
 
1304
 
1305
  // Update start bar input max value
1306
  const startBarInput = document.getElementById('startBar');
1307
- const maxStartBar = Math.max(1, this.barsArray.length + 1 - this.barsToPlay);
1308
  startBarInput.max = maxStartBar;
1309
  this.currentStartBar = 0;
1310
  startBarInput.value = 1;
1311
 
1312
  // Display bars in list
1313
- this.barsArray.forEach((bar, index) => {
1314
  const barItem = document.createElement('div');
1315
  barItem.className = 'bar-item';
1316
  barItem.style.cursor = 'pointer';
@@ -1318,8 +1325,8 @@
1318
  barItem.style.borderRadius = '4px';
1319
  barItem.style.transition = 'background-color 0.2s';
1320
  barItem.innerHTML = `
1321
- <span>Bar ${bar.barNum}</span>
1322
- <span>${this.formatTime(bar.time)}</span>
1323
  `;
1324
 
1325
  // Highlight current selected bar
@@ -1386,7 +1393,7 @@
1386
  }
1387
 
1388
  initializeAudioPlayer() {
1389
- if (!this.audioBuffer || this.barsArray.length === 0) return;
1390
 
1391
  // Create or update the AudioStretchPlayer
1392
  if (!this.audioPlayer) {
@@ -1403,23 +1410,23 @@
1403
  }
1404
 
1405
  updateAudioPlayer() {
1406
- if (!this.audioPlayer || !this.audioBuffer || this.barsArray.length === 0) return;
1407
 
1408
  const startBar = this.currentStartBar;
1409
- const endBar = Math.min(startBar + this.barsToPlay, this.barsArray.length - 1);
1410
 
1411
- if (startBar >= this.barsArray.length || endBar >= this.barsArray.length) {
1412
  console.warn('Invalid bar selection');
1413
  return;
1414
  }
1415
 
1416
- let startTime = this.barsArray[startBar].time;
1417
- let endTime = this.barsArray[endBar].time;
1418
  const duration = endTime - startTime;
1419
 
1420
  // Calculate upbeat duration if upbeat is specified
1421
  if (this.upbeat !== 0) {
1422
- const durationFirstBar = this.barsArray[startBar + 1].time - startTime;
1423
  const upbeatDuration = durationFirstBar * this.upbeat / this.detectedBeatsPerBar;
1424
 
1425
  // Adjust startTime and endTime by subtracting the upbeatDuration
@@ -1438,7 +1445,8 @@
1438
 
1439
  // Append countIn
1440
  if (this.countIn > 0) {
1441
- const beat_duration = duration / ((endBar - startBar) * this.detectedBeatsPerBar)
 
1442
  audioSegment = this.appendCountIn(audioSegment, beat_duration, this.countIn);
1443
  }
1444
 
@@ -1569,10 +1577,9 @@
1569
  }
1570
 
1571
  selectBar(barIndex) {
1572
- if (barIndex >= 0 && barIndex < this.barsArray.length) {
1573
  this.currentStartBar = barIndex;
1574
- let barNum = this.barsArray[this.currentStartBar].barNum
1575
- document.getElementById('startBar').value = barNum;
1576
  this.updateAudioPlayer();
1577
  }
1578
  }
@@ -1581,21 +1588,19 @@
1581
  const newStartBar = Math.max(0, this.currentStartBar - this.stepSize);
1582
  if (newStartBar !== this.currentStartBar) {
1583
  this.currentStartBar = newStartBar;
1584
- let barNum = this.barsArray[this.currentStartBar].barNum
1585
- document.getElementById('startBar').value = barNum;
1586
  this.updateAudioPlayer();
1587
  }
1588
  }
1589
 
1590
  nextBar() {
1591
  const newStartBar = Math.min(
1592
- this.barsArray.length - this.barsToPlay,
1593
  this.currentStartBar + this.stepSize
1594
  );
1595
  if (newStartBar !== this.currentStartBar && newStartBar >= 0) {
1596
  this.currentStartBar = newStartBar;
1597
- let barNum = this.barsArray[this.currentStartBar].barNum
1598
- document.getElementById('startBar').value = barNum;
1599
  this.updateAudioPlayer();
1600
  }
1601
  }
@@ -1669,10 +1674,13 @@
1669
  }
1670
 
1671
  formatTime(seconds) {
1672
- const mins = Math.floor(seconds / 60);
1673
- const secs = Math.floor(seconds % 60);
1674
- const ms = Math.floor((seconds * 1000) % 1000);
1675
- return `${mins}:${secs.toString().padStart(2, '0')}:${ms.toString().padStart(3, '0')}`;
 
 
 
1676
  }
1677
 
1678
  formatFileSize(bytes) {
 
274
  this.stepSize = 2;
275
  this.upbeat = 0;
276
  this.countIn = 0;
277
+ this.barsMap = {};
278
 
279
  // Cache storage key
280
  this.CACHE_STORAGE_KEY = 'beatDetectionCache';
 
469
 
470
  getCachedResults(file) {
471
  const fileKey = this.generateFileKey(file);
472
+ const cachedResults = this.cache[fileKey]
473
+ //check bars is an array:
474
+ if (cachedResults && Array.isArray(cachedResults.bars)){
475
+ return cachedResults;
476
+ } else{
477
+ return null;
478
+ }
479
  }
480
 
481
  async cacheResults(file, results) {
 
784
 
785
  startBarInput.addEventListener('change', (e) => {
786
  let barNum = parseInt(e.target.value)
787
+ this.currentStartBar = this.barsMap[barNum];
788
  this.updateAudioPlayer();
789
  });
790
 
 
1257
  detectedBeatsPerBar.textContent = this.detectedBeatsPerBar !== null ? this.detectedBeatsPerBar : '--';
1258
 
1259
  // Display bars if available and initialize destroy
1260
+ if (this.bars && this.bars.length > 0) {
1261
  this.displayBars();
1262
  document.getElementById('barsResults').style.display = 'block';
1263
 
 
1293
  const barsList = document.getElementById('barsList');
1294
  barsList.innerHTML = '';
1295
 
1296
+ if (!this.bars || this.bars.length === 0) {
1297
  barsList.innerHTML = '<div class="bar-item">No bars detected</div>';
1298
 
1299
  // Initialize player with full audio if no bars but audio exists
 
1303
  return;
1304
  }
1305
 
1306
+ // Create barsMap that maps nb to idx for easy lookup
1307
+ this.barsMap = {};
1308
+ this.bars.forEach(bar => {
1309
+ this.barsMap[bar.nb] = bar.idx;
1310
+ });
1311
 
1312
  // Update start bar input max value
1313
  const startBarInput = document.getElementById('startBar');
1314
+ const maxStartBar = Math.max(1, this.bars.length + 1 - this.barsToPlay);
1315
  startBarInput.max = maxStartBar;
1316
  this.currentStartBar = 0;
1317
  startBarInput.value = 1;
1318
 
1319
  // Display bars in list
1320
+ this.bars.forEach((bar, index) => {
1321
  const barItem = document.createElement('div');
1322
  barItem.className = 'bar-item';
1323
  barItem.style.cursor = 'pointer';
 
1325
  barItem.style.borderRadius = '4px';
1326
  barItem.style.transition = 'background-color 0.2s';
1327
  barItem.innerHTML = `
1328
+ <span>Bar ${bar.nb}</span>
1329
+ <span>${this.formatTime(bar.start)}</span>
1330
  `;
1331
 
1332
  // Highlight current selected bar
 
1393
  }
1394
 
1395
  initializeAudioPlayer() {
1396
+ if (!this.audioBuffer || this.bars.length === 0) return;
1397
 
1398
  // Create or update the AudioStretchPlayer
1399
  if (!this.audioPlayer) {
 
1410
  }
1411
 
1412
  updateAudioPlayer() {
1413
+ if (!this.audioPlayer || !this.audioBuffer || this.bars.length === 0) return;
1414
 
1415
  const startBar = this.currentStartBar;
1416
+ const endBar = Math.min(startBar + this.barsToPlay - 1, this.bars.length - 1);
1417
 
1418
+ if (startBar >= this.bars.length || endBar >= this.bars.length) {
1419
  console.warn('Invalid bar selection');
1420
  return;
1421
  }
1422
 
1423
+ let startTime = this.bars[startBar].start;
1424
+ let endTime = this.bars[endBar].end;
1425
  const duration = endTime - startTime;
1426
 
1427
  // Calculate upbeat duration if upbeat is specified
1428
  if (this.upbeat !== 0) {
1429
+ const durationFirstBar = this.bars[startBar].end - startTime;
1430
  const upbeatDuration = durationFirstBar * this.upbeat / this.detectedBeatsPerBar;
1431
 
1432
  // Adjust startTime and endTime by subtracting the upbeatDuration
 
1445
 
1446
  // Append countIn
1447
  if (this.countIn > 0) {
1448
+ const nb_beats = (endBar + 1 - startBar) * this.detectedBeatsPerBar;
1449
+ const beat_duration = duration / nb_beats;
1450
  audioSegment = this.appendCountIn(audioSegment, beat_duration, this.countIn);
1451
  }
1452
 
 
1577
  }
1578
 
1579
  selectBar(barIndex) {
1580
+ if (barIndex >= 0 && barIndex < this.bars.length) {
1581
  this.currentStartBar = barIndex;
1582
+ document.getElementById('startBar').value = this.bars[this.currentStartBar].nb;
 
1583
  this.updateAudioPlayer();
1584
  }
1585
  }
 
1588
  const newStartBar = Math.max(0, this.currentStartBar - this.stepSize);
1589
  if (newStartBar !== this.currentStartBar) {
1590
  this.currentStartBar = newStartBar;
1591
+ document.getElementById('startBar').value = this.bars[this.currentStartBar].nb;
 
1592
  this.updateAudioPlayer();
1593
  }
1594
  }
1595
 
1596
  nextBar() {
1597
  const newStartBar = Math.min(
1598
+ this.bars.length - this.barsToPlay,
1599
  this.currentStartBar + this.stepSize
1600
  );
1601
  if (newStartBar !== this.currentStartBar && newStartBar >= 0) {
1602
  this.currentStartBar = newStartBar;
1603
+ document.getElementById('startBar').value = this.bars[this.currentStartBar].nb;
 
1604
  this.updateAudioPlayer();
1605
  }
1606
  }
 
1674
  }
1675
 
1676
  formatTime(seconds) {
1677
+ const absSeconds = Math.abs(seconds);
1678
+ const mins = Math.floor(absSeconds / 60);
1679
+ const secs = Math.floor(absSeconds % 60);
1680
+ const ms = Math.floor((absSeconds * 1000) % 1000);
1681
+ const sign = seconds < 0 ? '-' : '';
1682
+
1683
+ return `${sign}${mins}:${secs.toString().padStart(2, '0')}:${ms.toString().padStart(3, '0')}`;
1684
  }
1685
 
1686
  formatFileSize(bytes) {
logits_to_bars.py CHANGED
@@ -507,7 +507,60 @@ def logits_to_bars(beat_logits: List[float], downbeat_logits: List[float],
507
  print(f"[logits] estimated_bpm={estimated_bpm:.2f}, detected_beats_per_bar={detected_bp}")
508
  print(f"[logits] corrected_downbeats count={len(corrected_downbeats)}")
509
 
510
- bars = {i + 1: t for i, t in enumerate(corrected_downbeats)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
 
512
  return {
513
  "bars": bars,
 
507
  print(f"[logits] estimated_bpm={estimated_bpm:.2f}, detected_beats_per_bar={detected_bp}")
508
  print(f"[logits] corrected_downbeats count={len(corrected_downbeats)}")
509
 
510
+ # bars = {i + 1: t for i, t in enumerate(corrected_downbeats)}
511
+ bars = []
512
+ nb = 1
513
+ idx = 0
514
+ for i, t in enumerate(corrected_downbeats):
515
+ start = t
516
+ if i < len(corrected_downbeats) - 2:
517
+ end = corrected_downbeats[i+1]
518
+ else:
519
+ end = beats[-1]
520
+ nb_beats = len([b for b in beats if start <= b < end])
521
+
522
+ if nb == 1:
523
+ if i==0 and nb_beats < detected_bp :
524
+ # skip fist bar if it has not all beats
525
+ continue
526
+ else:
527
+ # if there are beats before the first bar, we start with an upbeat bar (bar nb = 0)
528
+ nb_up_beats = len([b for b in beats if 0 <= b < start])
529
+ if nb_up_beats > 0:
530
+ if nb_beats == detected_bp:
531
+ # if first bar has all his beats, we use its duration to guess the position of the downbeat of the upbeat bar (can be negative)
532
+ start_bar_0 = start - (end - start)
533
+ nb_beats_0 = detected_bp
534
+ else:
535
+ start_bar_0 = beats[0]
536
+ nb_beats_0 = nb_up_beats
537
+ end_bar_0 = start
538
+ bpm = 60 * nb_up_beats / (end_bar_0 - start_bar_0)
539
+ bar = {
540
+ 'idx': idx,
541
+ 'nb': 0,
542
+ 'start': start_bar_0,
543
+ 'end': end_bar_0,
544
+ 'bpm': bpm,
545
+ 'beats': nb_beats_0,
546
+ }
547
+ bars.append(bar)
548
+ idx += 1
549
+
550
+
551
+ if nb_beats > 1:
552
+ bpm = 60 * nb_beats / (end - start)
553
+ bar = {
554
+ 'idx': idx,
555
+ 'nb': nb,
556
+ 'start': start,
557
+ 'end': end,
558
+ 'bpm': bpm,
559
+ 'nb_beats': nb_beats,
560
+ }
561
+ bars.append(bar)
562
+ idx += 1
563
+ nb += 1
564
 
565
  return {
566
  "bars": bars,
sw.js CHANGED
@@ -1,5 +1,5 @@
1
  // Use a version that you can update with each release
2
- const APP_VERSION = '0.1.21';
3
  const CACHE_NAME = `my-pwa-cache-${APP_VERSION}`;
4
 
5
  // List of files to cache
 
1
  // Use a version that you can update with each release
2
+ const APP_VERSION = '0.1.22';
3
  const CACHE_NAME = `my-pwa-cache-${APP_VERSION}`;
4
 
5
  // List of files to cache