feat(users/Profpatsch/lyric/ext): add bpm quantization
It’s a bit crappy and really depends on the input field opening quickly again (which it often doesn’t really do…), but it was the easiest way I figured how to do it haha. Aligning to eigth notes is pretty much the easiest way to sync everything up after tapping in the timestamps (for most songs). Change-Id: Ibbb072f62b6ee17d983e81b6c1554bc3516fa636 Reviewed-on: https://cl.tvl.fyi/c/depot/+/12551 Reviewed-by: Profpatsch <mail@profpatsch.de> Tested-by: BuildkiteCI
This commit is contained in:
parent
48d021de15
commit
686b141767
3 changed files with 186 additions and 1 deletions
82
users/Profpatsch/lyric/extension/src/quantize-lrc.ts
Normal file
82
users/Profpatsch/lyric/extension/src/quantize-lrc.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
export function bpmToEighthNoteDuration(bpm: number): number {
|
||||
// Convert BPM to eighth-note duration in milliseconds
|
||||
const quarterNoteDuration = (60 / bpm) * 1000; // in ms
|
||||
const eighthNoteDuration = quarterNoteDuration / 2;
|
||||
return eighthNoteDuration;
|
||||
}
|
||||
|
||||
function parseTimestamp(timestamp: string): number {
|
||||
// Parse [mm:ss.ms] format into milliseconds
|
||||
const [min, sec] = timestamp.split(':');
|
||||
|
||||
const minutes = parseInt(min, 10);
|
||||
const seconds = parseFloat(sec);
|
||||
|
||||
return minutes * 60 * 1000 + seconds * 1000;
|
||||
}
|
||||
|
||||
function formatTimestamp(ms: number): string {
|
||||
// Format milliseconds back into [mm:ss.ms]
|
||||
const minutes = Math.floor(ms / 60000);
|
||||
ms %= 60000;
|
||||
const seconds = (ms / 1000).toFixed(2);
|
||||
|
||||
return `${String(minutes).padStart(2, '0')}:${seconds}`;
|
||||
}
|
||||
|
||||
export function adjustTimestampToEighthNote(
|
||||
timestampMs: number,
|
||||
eighthNoteDuration: number,
|
||||
): number {
|
||||
// Find the closest multiple of the eighth-note duration
|
||||
return Math.round(timestampMs / eighthNoteDuration) * eighthNoteDuration;
|
||||
}
|
||||
|
||||
function adjustTimestamps(bpm: number, timestamps: string[]): string[] {
|
||||
const eighthNoteDuration = bpmToEighthNoteDuration(bpm);
|
||||
|
||||
return timestamps.map(timestamp => {
|
||||
const timestampMs = parseTimestamp(timestamp);
|
||||
const adjustedMs = adjustTimestampToEighthNote(timestampMs, eighthNoteDuration);
|
||||
return formatTimestamp(adjustedMs);
|
||||
});
|
||||
}
|
||||
|
||||
// Parse a .lrc file into an array of objects with timestamp and text
|
||||
// Then adjust the timestamps to the closest eighth note
|
||||
// Finally, format the adjusted timestamps back into [mm:ss.ms] format and put them back into the lrc object
|
||||
//
|
||||
// Example .lrc file:
|
||||
// [01:15.66] And the reviewers bewail
|
||||
// [01:18.18] There'll be no encore
|
||||
// [01:21.65] 'Cause you're not begging for more
|
||||
// [01:25.00]
|
||||
// [01:34.64] She may seem self-righteous and holier-than-thou
|
||||
// [01:39.77] She may sound like she has all the answers
|
||||
// [01:45.20] But beyond she may feel just a bit anyhow
|
||||
function parseLrc(lrc: string): { timestamp: string; text: string }[] {
|
||||
return lrc
|
||||
.trimEnd()
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
const match = line.match(/\[(\d+:\d+\.\d+)\](.*)/);
|
||||
const [, timestamp, text] = match!;
|
||||
return { timestamp, text };
|
||||
});
|
||||
}
|
||||
|
||||
function formatLrc(lrc: { timestamp: string; text: string }[]): string {
|
||||
return lrc.map(({ timestamp, text }) => `[${timestamp}] ${text}`).join('\n');
|
||||
}
|
||||
|
||||
function adjustLrc(lrc: string, bpm: number): string {
|
||||
const lrcArray = parseLrc(lrc);
|
||||
const timestamps = lrcArray.map(({ timestamp }) => timestamp);
|
||||
const adjustedTimestamps = adjustTimestamps(bpm, timestamps);
|
||||
|
||||
lrcArray.forEach((line, i) => {
|
||||
line.timestamp = adjustedTimestamps[i];
|
||||
});
|
||||
|
||||
return formatLrc(lrcArray);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue