Spaces:
Sleeping
Sleeping
Commit ·
01f45fd
1
Parent(s): 6c4b783
count in
Browse files- 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.
|
| 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 |
-
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|