12tet.umm.gay/pages/index.tsx

137 lines
3.7 KiB
TypeScript

import Head from "next/head";
import { ChangeEventHandler, useState } from "react";
const calculateFrequency = (root: number, noteDescriptor: string) => {
const match = noteDescriptor.match(
/([a-gA-G])\s*(#?)(b?)\s*(-?\d+)([+-]\d+)?/
);
if (match == null) return undefined;
const [value, note, sharp, flat, octave, cents] = match;
const dists: { [key: string]: number | undefined } = {
C: -9,
D: -7,
E: -5,
F: -4,
G: -2,
A: 0,
B: 2,
};
let distanceFromA = dists[note.toUpperCase()];
if (distanceFromA == null) return;
if (sharp) distanceFromA += 1;
if (flat) distanceFromA -= 1;
if (cents) distanceFromA += parseInt(cents) / 100;
let octaveDistance = parseInt(octave) - 4;
return root * Math.pow(2, octaveDistance + distanceFromA / 12);
};
const calculateNote = (root: number, frequency: number) => {
const distanceFromA = 12 * Math.log2(frequency / root);
const semis = Math.round(distanceFromA);
const cents = Math.round((distanceFromA - semis) * 100);
let octave = 4 + Math.floor(semis / 12);
const octaveBoundSemis = ((semis % 12) + 12) % 12;
if (octaveBoundSemis >= 3) octave += 1;
const notes = [
"A",
"A#",
"B",
"C",
"C#",
"D",
"D#",
"E",
"F",
"F#",
"G",
"G#",
];
const note = notes[octaveBoundSemis];
let centsStr = "";
if (cents <= -1) {
centsStr = cents.toString().padStart(2, "0");
} else if (cents >= 1) {
centsStr = "+" + cents.toString().padStart(2, "0");
}
return `${note}${octave}${centsStr}`;
};
export default function Main() {
const [root, setRoot] = useState(440);
const [note, setNote] = useState("C5");
const [frequency, setFrequency] = useState("523.251");
const setRootValue: ChangeEventHandler<HTMLInputElement> = (e) => {
const newRoot = parseFloat(e.target.value);
if (!isNaN(newRoot)) setRoot(newRoot);
const frequency = calculateFrequency(newRoot, note);
if (frequency != null) setFrequency(frequency.toFixed(3));
};
const setNoteValue: ChangeEventHandler<HTMLInputElement> = (e) => {
setNote(e.target.value);
const frequency = calculateFrequency(root, e.target.value);
if (frequency != null) setFrequency(frequency.toFixed(3));
};
const setFrequencyValue: ChangeEventHandler<HTMLInputElement> = (e) => {
setFrequency(e.target.value);
const newFrequency = parseFloat(e.target.value);
if (!isNaN(newFrequency)) {
const noteDescriptor = calculateNote(root, newFrequency);
setNote(noteDescriptor);
}
};
return (
<>
<Head>
<title>12 tone equal temperament :)</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<main>
<header>
(by <a href="https://som.codes/">charlotte som</a>)
</header>
<section>
{"if A4 is "}
<input
type="text"
pattern="\d+(\.\d+)"
value={root}
placeholder="440"
onChange={setRootValue}
/>
{" Hz"}, then
</section>
<section>
<input
type="text"
value={note}
placeholder="C5"
onChange={setNoteValue}
/>
{" is "}
<input
type="text"
pattern="\d+(\.\d+)"
value={frequency}
placeholder="523.251"
onChange={setFrequencyValue}
/>
{" Hz "}
<aside>
(wavelength: {(1000 / parseFloat(frequency)).toFixed(4)} ms)
</aside>
</section>
</main>
</>
);
}