midi-segmentizer
Advanced tools
+87
-6
@@ -31,3 +31,3 @@ "use strict"; | ||
| const midi_1 = __importDefault(require("./midi")); | ||
| const X_EXPAND = 100; | ||
| const X_EXPAND = 200; | ||
| function buildNote(note) { | ||
@@ -86,2 +86,7 @@ var _a, _b; | ||
| } | ||
| if (note.lyric) { | ||
| ctx.fillStyle = "#000000"; | ||
| ctx.font = "30px Arial"; | ||
| ctx.fillText(note.lyric, note.x, note.y, note.width); | ||
| } | ||
| } | ||
@@ -124,4 +129,8 @@ function renderBox(ctx, hi, lo, l, r, col) { | ||
| const [keyPressed, setKeyPressed] = (0, react_1.useState)(false); | ||
| function downHandler({ key }) { | ||
| if (key === targetKey) { | ||
| function downHandler(evt) { | ||
| // special case for tab key in this application... | ||
| if (evt.key === "Tab") { | ||
| evt.preventDefault(); | ||
| } | ||
| if (evt.key === targetKey) { | ||
| setKeyPressed(true); | ||
@@ -151,2 +160,4 @@ } | ||
| const [notes, setNotes] = (0, react_1.useState)([]); | ||
| const [addingLyric, setAddingLyric] = (0, react_1.useState)(null); | ||
| const [lyric, setLyric] = (0, react_1.useState)(""); | ||
| const [bpm, setBpm] = (0, react_1.useState)(); | ||
@@ -159,6 +170,34 @@ const [bars, setBars] = (0, react_1.useState)([]); | ||
| const escapePressed = useKeyPress("Escape"); | ||
| const enterPressed = useKeyPress("Enter"); | ||
| const tabPressed = useKeyPress("Tab"); | ||
| const tickPressed = useKeyPress("`"); | ||
| (0, react_1.useEffect)(() => { | ||
| if (enterPressed) { | ||
| handleSaveLyric(); | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [enterPressed]); | ||
| (0, react_1.useEffect)(() => { | ||
| if (tabPressed) { | ||
| const lastNote = handleSaveLyric(); | ||
| // find next lyric | ||
| if (lastNote) { | ||
| const possibleNextNotes = notes.filter((n) => n.time > lastNote.time); | ||
| const score = (note) => { | ||
| const midiDistance = Math.abs(note.midi - lastNote.midi) || 1; | ||
| const timeDifference = Math.abs(note.time - lastNote.time) * 50; | ||
| return midiDistance + timeDifference; | ||
| }; | ||
| if (possibleNextNotes.length) { | ||
| const nextNotes = possibleNextNotes.sort((a, b) => score(b) > score(a) ? 1 : -1); | ||
| setAddingLyric(nextNotes[nextNotes.length - 1].id); | ||
| } | ||
| } | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [tabPressed]); | ||
| (0, react_1.useEffect)(() => { | ||
| if (escapePressed) { | ||
| handleClearSelection(); | ||
| setAddingLyric(null); | ||
| } | ||
@@ -183,2 +222,3 @@ // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| .getAllNotes() | ||
| .map((n) => (Object.assign(Object.assign({}, n), { id: (0, uuid_1.v4)() }))) | ||
| .map(buildNote) | ||
@@ -200,2 +240,21 @@ .sort((a, b) => (b.time > a.time ? -1 : 1)); | ||
| }, [props.data, ref]); | ||
| function handleSaveLyric() { | ||
| const tempNotes = []; | ||
| let res = null; | ||
| if (addingLyric && lyric) { | ||
| for (const note of notes) { | ||
| if (note.id === addingLyric) { | ||
| tempNotes.push(Object.assign(Object.assign({}, note), { lyric })); | ||
| res = note; | ||
| } | ||
| else { | ||
| tempNotes.push(note); | ||
| } | ||
| } | ||
| setNotes(tempNotes); | ||
| setLyric(""); | ||
| setAddingLyric(null); | ||
| } | ||
| return res; | ||
| } | ||
| function handleMouseDown(evt) { | ||
@@ -210,3 +269,7 @@ if (ref.current) { | ||
| function handleClearSelection() { | ||
| setNotes(notes.map((note) => (Object.assign(Object.assign({}, note), { selected: false })))); | ||
| const tempNotes = []; | ||
| for (const note of notes) { | ||
| tempNotes.push(note.selected ? Object.assign(Object.assign({}, note), { selected: false, lyric: undefined }) : note); | ||
| } | ||
| setNotes(tempNotes); | ||
| } | ||
@@ -237,2 +300,18 @@ function handleSaveSegment() { | ||
| } | ||
| function handleDoubleClick(evt) { | ||
| if (ref.current) { | ||
| const rect = ref.current.getBoundingClientRect(); | ||
| const x = evt.clientX - rect.left; | ||
| const y = evt.clientY - rect.top; | ||
| notes.forEach((note) => { | ||
| const { x: noteX, y: noteY, width, height } = note; | ||
| if (x >= noteX && | ||
| x <= noteX + width && | ||
| y >= noteY && | ||
| y <= noteY + height) { | ||
| setAddingLyric(note.id); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| function handleMouseUp(evt) { | ||
@@ -322,2 +401,3 @@ if (ref.current && mouseDown) { | ||
| .map((note) => (Object.assign(Object.assign({}, note), { offset: segment.offset }))) | ||
| .map((n) => (Object.assign(Object.assign({}, n), { id: (0, uuid_1.v4)() }))) | ||
| .map(buildNote), | ||
@@ -330,6 +410,7 @@ }))); | ||
| react_1.default.createElement("button", { onClick: handleLucky }, "i'm feeling lucky"), | ||
| react_1.default.createElement("button", { disabled: !segments.length, onClick: handleSave }, "save segments")), | ||
| react_1.default.createElement("canvas", { onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, id: "react-midiVisualizer-canvas", ref: ref, height: height, width: width }), | ||
| react_1.default.createElement("button", { disabled: !segments.length, onClick: handleSave }, "save segments"), | ||
| addingLyric ? (react_1.default.createElement("input", { autoFocus: true, value: lyric, onChange: (e) => setLyric(e.target.value), tabIndex: 1 })) : null), | ||
| react_1.default.createElement("canvas", { onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, onDoubleClick: handleDoubleClick, id: "react-midiVisualizer-canvas", ref: ref, height: height, width: width }), | ||
| renderButtons())); | ||
| } | ||
| exports.default = Segmentizer; |
+1
-1
| { | ||
| "name": "midi-segmentizer", | ||
| "version": "2.2.0", | ||
| "version": "2.2.1-0", | ||
| "description": "break a simple midi file into monophonic segments", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
+3
-0
@@ -5,2 +5,5 @@ # midi-segmentizer | ||
| This is a package I made to support [carryoaky.com](carryoaky.com). | ||
| project board is [here](https://www.notion.so/1fab52dab44846839b5965650f6de2a5?v=74a5f448a3244a168f4b09085bd998fb) | ||
| # todo | ||
@@ -7,0 +10,0 @@ |
+108
-10
@@ -13,3 +13,3 @@ import React, { useEffect, useRef, useState } from "react"; | ||
| const X_EXPAND = 100; | ||
| const X_EXPAND = 200; | ||
@@ -19,2 +19,3 @@ type Coord = { x: number; y: number }; | ||
| interface DrawableNote extends Note, Coord { | ||
| id: string; | ||
| width: number; | ||
@@ -29,3 +30,3 @@ height: number; | ||
| function buildNote( | ||
| note: Note & { selected?: boolean; offset?: number } | ||
| note: Note & { selected?: boolean; offset?: number; id: string } | ||
| ): DrawableNote { | ||
@@ -100,2 +101,8 @@ // NOTE: offset is a unit of time | ||
| } | ||
| if (note.lyric) { | ||
| ctx.fillStyle = "#000000"; | ||
| ctx.font = "30px Arial"; | ||
| ctx.fillText(note.lyric, note.x, note.y, note.width); | ||
| } | ||
| } | ||
@@ -151,4 +158,9 @@ | ||
| const [keyPressed, setKeyPressed] = useState(false); | ||
| function downHandler({ key }: any): void { | ||
| if (key === targetKey) { | ||
| function downHandler(evt: any): void { | ||
| // special case for tab key in this application... | ||
| if (evt.key === "Tab") { | ||
| evt.preventDefault(); | ||
| } | ||
| if (evt.key === targetKey) { | ||
| setKeyPressed(true); | ||
@@ -183,2 +195,4 @@ } | ||
| const [notes, setNotes] = useState<DrawableNote[]>([]); | ||
| const [addingLyric, setAddingLyric] = useState<string | null>(null); | ||
| const [lyric, setLyric] = useState(""); | ||
| const [bpm, setBpm] = useState<number | null>(); | ||
@@ -191,7 +205,40 @@ const [bars, setBars] = useState<number[]>([]); | ||
| const escapePressed = useKeyPress("Escape"); | ||
| const enterPressed = useKeyPress("Enter"); | ||
| const tabPressed = useKeyPress("Tab"); | ||
| const tickPressed = useKeyPress("`"); | ||
| useEffect(() => { | ||
| if (enterPressed) { | ||
| handleSaveLyric(); | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [enterPressed]); | ||
| useEffect(() => { | ||
| if (tabPressed) { | ||
| const lastNote = handleSaveLyric(); | ||
| // find next lyric | ||
| if (lastNote) { | ||
| const possibleNextNotes = notes.filter((n) => n.time > lastNote.time); | ||
| const score = (note: DrawableNote) => { | ||
| const midiDistance = Math.abs(note.midi - lastNote.midi) || 1; | ||
| const timeDifference = Math.abs(note.time - lastNote.time) * 50; | ||
| return midiDistance + timeDifference; | ||
| }; | ||
| if (possibleNextNotes.length) { | ||
| const nextNotes = possibleNextNotes.sort((a, b) => | ||
| score(b) > score(a) ? 1 : -1 | ||
| ); | ||
| setAddingLyric(nextNotes[nextNotes.length - 1].id); | ||
| } | ||
| } | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [tabPressed]); | ||
| useEffect(() => { | ||
| if (escapePressed) { | ||
| handleClearSelection(); | ||
| setAddingLyric(null); | ||
| } | ||
@@ -218,2 +265,3 @@ // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| .getAllNotes() | ||
| .map((n) => ({ ...n, id: uuidv4() })) | ||
| .map(buildNote) | ||
@@ -236,2 +284,22 @@ .sort((a, b) => (b.time > a.time ? -1 : 1)); | ||
| function handleSaveLyric() { | ||
| const tempNotes: DrawableNote[] = []; | ||
| let res: DrawableNote | null = null; | ||
| if (addingLyric && lyric) { | ||
| for (const note of notes) { | ||
| if (note.id === addingLyric) { | ||
| tempNotes.push({ ...note, lyric }); | ||
| res = note; | ||
| } else { | ||
| tempNotes.push(note); | ||
| } | ||
| } | ||
| setNotes(tempNotes); | ||
| setLyric(""); | ||
| setAddingLyric(null); | ||
| } | ||
| return res; | ||
| } | ||
| function handleMouseDown(evt: any) { | ||
@@ -247,8 +315,9 @@ if (ref.current) { | ||
| function handleClearSelection() { | ||
| setNotes( | ||
| notes.map((note) => ({ | ||
| ...note, | ||
| selected: false, | ||
| })) | ||
| ); | ||
| const tempNotes = []; | ||
| for (const note of notes) { | ||
| tempNotes.push( | ||
| note.selected ? { ...note, selected: false, lyric: undefined } : note | ||
| ); | ||
| } | ||
| setNotes(tempNotes); | ||
| } | ||
@@ -282,2 +351,21 @@ | ||
| function handleDoubleClick(evt: any) { | ||
| if (ref.current) { | ||
| const rect = (ref.current as any).getBoundingClientRect(); | ||
| const x = evt.clientX - rect.left; | ||
| const y = evt.clientY - rect.top; | ||
| notes.forEach((note) => { | ||
| const { x: noteX, y: noteY, width, height } = note; | ||
| if ( | ||
| x >= noteX && | ||
| x <= noteX + width && | ||
| y >= noteY && | ||
| y <= noteY + height | ||
| ) { | ||
| setAddingLyric(note.id); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| function handleMouseUp(evt: any) { | ||
@@ -402,2 +490,3 @@ if (ref.current && mouseDown) { | ||
| })) | ||
| .map((n) => ({ ...n, id: uuidv4() })) | ||
| .map(buildNote), | ||
@@ -416,2 +505,10 @@ })) | ||
| </button> | ||
| {addingLyric ? ( | ||
| <input | ||
| autoFocus | ||
| value={lyric} | ||
| onChange={(e) => setLyric(e.target.value)} | ||
| tabIndex={1} | ||
| /> | ||
| ) : null} | ||
| </div> | ||
@@ -421,2 +518,3 @@ <canvas | ||
| onMouseUp={handleMouseUp} | ||
| onDoubleClick={handleDoubleClick} | ||
| id="react-midiVisualizer-canvas" | ||
@@ -423,0 +521,0 @@ ref={ref} |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
112465
5.98%2053
9.09%18
20%3
50%