Spaces:
Sleeping
Sleeping
Commit ·
34bac53
1
Parent(s): 9eb2aa3
refactor bars data struct; first and last bar fixing
Browse files- README.md +2 -2
- index.html +41 -33
- logits_to_bars.py +54 -1
- 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.
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 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 &&
|
| 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 ||
|
| 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 |
-
//
|
| 1301 |
-
this.
|
| 1302 |
-
|
| 1303 |
-
.
|
|
|
|
| 1304 |
|
| 1305 |
// Update start bar input max value
|
| 1306 |
const startBarInput = document.getElementById('startBar');
|
| 1307 |
-
const maxStartBar = Math.max(1, this.
|
| 1308 |
startBarInput.max = maxStartBar;
|
| 1309 |
this.currentStartBar = 0;
|
| 1310 |
startBarInput.value = 1;
|
| 1311 |
|
| 1312 |
// Display bars in list
|
| 1313 |
-
this.
|
| 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.
|
| 1322 |
-
<span>${this.formatTime(bar.
|
| 1323 |
`;
|
| 1324 |
|
| 1325 |
// Highlight current selected bar
|
|
@@ -1386,7 +1393,7 @@
|
|
| 1386 |
}
|
| 1387 |
|
| 1388 |
initializeAudioPlayer() {
|
| 1389 |
-
if (!this.audioBuffer || this.
|
| 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.
|
| 1407 |
|
| 1408 |
const startBar = this.currentStartBar;
|
| 1409 |
-
const endBar = Math.min(startBar + this.barsToPlay, this.
|
| 1410 |
|
| 1411 |
-
if (startBar >= this.
|
| 1412 |
console.warn('Invalid bar selection');
|
| 1413 |
return;
|
| 1414 |
}
|
| 1415 |
|
| 1416 |
-
let startTime = this.
|
| 1417 |
-
let endTime = this.
|
| 1418 |
const duration = endTime - startTime;
|
| 1419 |
|
| 1420 |
// Calculate upbeat duration if upbeat is specified
|
| 1421 |
if (this.upbeat !== 0) {
|
| 1422 |
-
const durationFirstBar = this.
|
| 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
|
|
|
|
| 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.
|
| 1573 |
this.currentStartBar = barIndex;
|
| 1574 |
-
|
| 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 |
-
|
| 1585 |
-
document.getElementById('startBar').value = barNum;
|
| 1586 |
this.updateAudioPlayer();
|
| 1587 |
}
|
| 1588 |
}
|
| 1589 |
|
| 1590 |
nextBar() {
|
| 1591 |
const newStartBar = Math.min(
|
| 1592 |
-
this.
|
| 1593 |
this.currentStartBar + this.stepSize
|
| 1594 |
);
|
| 1595 |
if (newStartBar !== this.currentStartBar && newStartBar >= 0) {
|
| 1596 |
this.currentStartBar = newStartBar;
|
| 1597 |
-
|
| 1598 |
-
document.getElementById('startBar').value = barNum;
|
| 1599 |
this.updateAudioPlayer();
|
| 1600 |
}
|
| 1601 |
}
|
|
@@ -1669,10 +1674,13 @@
|
|
| 1669 |
}
|
| 1670 |
|
| 1671 |
formatTime(seconds) {
|
| 1672 |
-
const
|
| 1673 |
-
const
|
| 1674 |
-
const
|
| 1675 |
-
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 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
|