about summary refs log tree commit diff
diff options
context:
space:
mode:
authorProfpatsch <mail@profpatsch.de>2024-10-01T01·24+0200
committerProfpatsch <mail@profpatsch.de>2024-10-05T13·49+0000
commit102c9b30a7ca3b41b0cfcd48f52bdf6c06f259ff (patch)
tree0af8ed3730dd5ada3c0fb544df93df41d64ba9b1
parentc014e39dfd35915fc19ba28e4c170774d1aad5c0 (diff)
feat(users/Profpatsch/lyric/ext): add bpm on quantization r/8765
If the bpm header already exists, overwrite it with the new value.
Also use an existing header as suggestion.

Change-Id: If6431e8056504db437c31313d885b5ba0d0e55d5
Reviewed-on: https://cl.tvl.fyi/c/depot/+/12553
Tested-by: BuildkiteCI
Reviewed-by: Profpatsch <mail@profpatsch.de>
-rw-r--r--users/Profpatsch/lyric/extension/src/extension.ts110
1 files changed, 102 insertions, 8 deletions
diff --git a/users/Profpatsch/lyric/extension/src/extension.ts b/users/Profpatsch/lyric/extension/src/extension.ts
index f09be13d04bd..1272b3c1b80d 100644
--- a/users/Profpatsch/lyric/extension/src/extension.ts
+++ b/users/Profpatsch/lyric/extension/src/extension.ts
@@ -164,12 +164,6 @@ async function shiftLyricsUp() {
 
 /** first ask the user for the BPM of the track, then quantize the timestamps in the active text editor to the closest eighth note based on the given BPM */
 async function quantizeLrc() {
-  const bpm = await timeInputBpm();
-
-  if (bpm === undefined) {
-    return;
-  }
-
   const editor = vscode.window.activeTextEditor;
 
   if (!editor) {
@@ -179,6 +173,22 @@ async function quantizeLrc() {
 
   const ext = new Ext(editor.document);
 
+  const startBpmStr = ext.findHeader('bpm')?.value;
+  let startBpm;
+  if (startBpmStr !== undefined) {
+    startBpm = parseInt(startBpmStr, 10);
+    if (isNaN(startBpm)) {
+      startBpm = undefined;
+    }
+  }
+  const bpm = await timeInputBpm(startBpm);
+
+  if (bpm === undefined) {
+    return;
+  }
+
+  await ext.writeHeader('bpm', bpm.toString());
+
   const getLine = (line: number) => ({
     number: line,
     range: editor.document.lineAt(line),
@@ -214,9 +224,21 @@ async function quantizeLrc() {
   });
 }
 
+// convert the given bpm to miliseconds
+function bpmToMs(bpm: number) {
+  return Math.floor((60 / bpm) * 1000);
+}
+
 // Show input boxes in a loop, and record the time between each input, averaging the last 5 inputs over a sliding window, then calculate the BPM of the average
-async function timeInputBpm() {
-  const timeDifferences: number[] = [500, 500, 500, 500, 500];
+async function timeInputBpm(startBpm?: number) {
+  const startBpmMs = bpmToMs(startBpm ?? 120);
+  const timeDifferences: number[] = [
+    startBpmMs,
+    startBpmMs,
+    startBpmMs,
+    startBpmMs,
+    startBpmMs,
+  ];
   // assign a weight to the time differences, so that the most recent time differences have more weight
   const weights = [0.1, 0.1, 0.2, 0.3, 0.3];
 
@@ -235,6 +257,7 @@ async function timeInputBpm() {
     const res = await vscode.window.showInputBox({
       prompt: `Press enter to record BPM (current BPM: ${calculateBPM()}), enter the final BPM once you know, or press esc to finish`,
       placeHolder: 'BPM',
+      value: startBpm !== undefined ? startBpm.toString() : undefined,
     });
     if (res === undefined) {
       return undefined;
@@ -398,6 +421,77 @@ class Ext {
     const seconds = milliseconds / 1000;
     return { milliseconds, seconds, text };
   }
+
+  // Find a header line of the format
+  // [header:value]
+  // at the beginning of the lrc file (before the first empty line)
+  findHeader(headerName: string) {
+    for (let line = 0; line < this.document.lineCount; line++) {
+      const text = this.document.lineAt(line).text;
+      if (text.trim() === '') {
+        return;
+      }
+      const match = text.match(/^\[(\w+):(.*)\]$/);
+      if (match && match[1] === headerName) {
+        return { key: match[1], value: match[2], line: line };
+      }
+    }
+  }
+
+  // check if the given line is a header line
+  isHeaderLine(line: string) {
+    return (
+      line.trim() !== '' &&
+      line.match(/^\[(\w+):(.*)\]$/) !== null &&
+      line.match(/^\[\d\d:\d\d.\d+\]/) === null
+    );
+  }
+
+  // write the given header to the lrc file, if the header already exists, update the value
+  async writeHeader(headerName: string, value: string) {
+    const header = this.findHeader(headerName);
+    const editor = findActiveEditor(this.document);
+    if (!editor) {
+      return;
+    }
+    if (header) {
+      const lineRange = this.document.lineAt(header.line).range;
+      await editor.edit(editBuilder => {
+        editBuilder.replace(lineRange, `[${headerName}: ${value}]`);
+      });
+    } else {
+      // insert before the first timestamp line if no header is found, or after the last header if there are multiple headers
+      let insertLine = 0;
+      let extraNewline = '';
+      for (let line = 0; line < this.document.lineCount; line++) {
+        const text = this.document.lineAt(line).text;
+        // check if header
+        if (this.isHeaderLine(text)) {
+          insertLine = line + 1;
+        } else if (text.trim() === '') {
+          insertLine = line;
+          break;
+        } else {
+          insertLine = line;
+          if (line == 0) {
+            extraNewline = '\n';
+          }
+          break;
+        }
+      }
+      await editor.edit(editBuilder => {
+        editBuilder.insert(
+          new vscode.Position(insertLine, 0),
+          `[${headerName}: ${value}]\n${extraNewline}`,
+        );
+      });
+    }
+  }
+}
+
+// find an active editor that has the given document opened
+function findActiveEditor(document: vscode.TextDocument) {
+  return vscode.window.visibleTextEditors.find(editor => editor.document === document);
 }
 
 function parseTimestamp(timestamp: string): number {