midi notes avaiable on ui
This commit is contained in:
203
Assets/web/src/Components/PianoKeyboard.js
Normal file
203
Assets/web/src/Components/PianoKeyboard.js
Normal file
@ -0,0 +1,203 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React /*, { useRef, useEffect, useState }*/ from "react";
|
||||
|
||||
const NOTE_NAMES = [
|
||||
"C",
|
||||
"C♯",
|
||||
"D",
|
||||
"D♯",
|
||||
"E",
|
||||
"F",
|
||||
"F♯",
|
||||
"G",
|
||||
"G♯",
|
||||
"A",
|
||||
"A♯",
|
||||
"B",
|
||||
];
|
||||
const WHITE_KEYS = [0, 2, 4, 5, 7, 9, 11];
|
||||
|
||||
// C2 = 36, C5 = 72
|
||||
const LOWEST_MIDI = 36;
|
||||
const HIGHEST_MIDI = 72;
|
||||
|
||||
function getNoteName(midi) {
|
||||
const octave = Math.floor(midi / 12) - 1;
|
||||
const note = NOTE_NAMES[midi % 12];
|
||||
return `${note}${octave}`;
|
||||
}
|
||||
|
||||
function isWhiteKey(midi) {
|
||||
return WHITE_KEYS.includes(midi % 12);
|
||||
}
|
||||
|
||||
export default function PianoKeyboard({ heldNotes }) {
|
||||
const heldMap = {};
|
||||
heldNotes.forEach((n) => (heldMap[n.midi] = n.voice));
|
||||
|
||||
const keys = [];
|
||||
for (let midi = LOWEST_MIDI; midi <= HIGHEST_MIDI; midi++) {
|
||||
const white = isWhiteKey(midi);
|
||||
const held = heldMap[midi] !== undefined;
|
||||
keys.push({
|
||||
midi,
|
||||
white,
|
||||
held,
|
||||
voice: heldMap[midi],
|
||||
noteName: getNoteName(midi),
|
||||
});
|
||||
}
|
||||
|
||||
const whiteKeys = keys.filter((k) => k.white);
|
||||
const blackKeys = keys.filter((k) => !k.white);
|
||||
|
||||
// For responsive black key positioning
|
||||
const numWhite = whiteKeys.length;
|
||||
|
||||
// Map midi to white key index for black key positioning
|
||||
const midiToWhiteIndex = {};
|
||||
let whiteIdx = 0;
|
||||
for (let midi = LOWEST_MIDI; midi <= HIGHEST_MIDI; midi++) {
|
||||
if (isWhiteKey(midi)) {
|
||||
midiToWhiteIndex[midi] = whiteIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
// For each black key, find its position between white keys
|
||||
function getBlackKeyPercent(midi) {
|
||||
// Black keys are always after a white key except for the first key
|
||||
// For example, C# is between C and D
|
||||
// So, find the previous white key index, then add ~0.65 of a white key width
|
||||
const prevWhite = midi - 1;
|
||||
const idx = midiToWhiteIndex[prevWhite];
|
||||
if (idx === undefined) return 0;
|
||||
// Offset: (idx + 0.65) / numWhite * 100%
|
||||
return ((idx + 0.65) / numWhite) * 100;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
height: 80,
|
||||
userSelect: "none",
|
||||
width: "100%",
|
||||
minWidth: 200,
|
||||
maxWidth: 900,
|
||||
margin: "0 auto",
|
||||
}}
|
||||
>
|
||||
{/* White keys */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
position: "relative",
|
||||
zIndex: 1,
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{whiteKeys.map((k) => (
|
||||
<div
|
||||
key={k.midi}
|
||||
style={{
|
||||
flex: "1 1 0",
|
||||
height: "100%",
|
||||
border: "1px solid #888",
|
||||
background: k.held ? "#2a82caff" : "#fff",
|
||||
position: "relative",
|
||||
boxSizing: "border-box",
|
||||
marginRight: -1,
|
||||
display: "flex",
|
||||
flexDirection: "column-reverse",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
fontSize: 10,
|
||||
fontFamily: "monospace",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column-reverse",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<span style={{ opacity: 0.5 }}>{k.noteName}</span>
|
||||
{k.held && (
|
||||
<span
|
||||
style={{
|
||||
color: "#fff",
|
||||
fontWeight: "bold",
|
||||
fontSize: 14,
|
||||
lineHeight: "14px",
|
||||
marginBottom: 2,
|
||||
}}
|
||||
>
|
||||
{k.voice}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Black keys */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: "62%",
|
||||
width: "100%",
|
||||
zIndex: 2,
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
>
|
||||
{blackKeys.map((k) => (
|
||||
<div
|
||||
key={k.midi}
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: `${getBlackKeyPercent(k.midi)}%`,
|
||||
width: `${(100 / numWhite) * 0.65}%`,
|
||||
height: "100%",
|
||||
background: k.held ? "#2a82caff" : "#222",
|
||||
border: "1px solid #444",
|
||||
borderRadius: 3,
|
||||
display: "flex",
|
||||
flexDirection: "column-reverse",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
fontSize: 10,
|
||||
fontFamily: "monospace",
|
||||
boxSizing: "border-box",
|
||||
transform: "translateX(-50%)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column-reverse",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{k.held && (
|
||||
<span
|
||||
style={{
|
||||
color: "#fff",
|
||||
fontWeight: "bold",
|
||||
fontSize: 14,
|
||||
lineHeight: "14px",
|
||||
marginBottom: 2,
|
||||
}}
|
||||
>
|
||||
{k.voice}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user