Initial commit
This commit is contained in:
commit
f39fb3214e
12 changed files with 2350 additions and 0 deletions
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
@ -0,0 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = crlf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = true
|
3
.eslintrc.json
Normal file
3
.eslintrc.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
36
.gitignore
vendored
Normal file
36
.gitignore
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# 12tet.umm.gay
|
||||
|
||||
two-way calculator for 12 tone equal temperament values
|
6
next.config.js
Normal file
6
next.config.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
22
package.json
Normal file
22
package.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "12tet.umm.gay",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "18.14.6",
|
||||
"@types/react": "18.0.28",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"eslint": "8.35.0",
|
||||
"eslint-config-next": "13.2.3",
|
||||
"next": "13.2.3",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"typescript": "4.9.5"
|
||||
}
|
||||
}
|
6
pages/_app.tsx
Normal file
6
pages/_app.tsx
Normal file
|
@ -0,0 +1,6 @@
|
|||
import "@/styles/globals.css";
|
||||
import type { AppProps } from "next/app";
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
13
pages/_document.tsx
Normal file
13
pages/_document.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { Html, Head, Main, NextScript } from "next/document";
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
130
pages/index.tsx
Normal file
130
pages/index.tsx
Normal file
|
@ -0,0 +1,130 @@
|
|||
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);
|
||||
if (semis % 12 >= 3) octave += 1;
|
||||
const notes = [
|
||||
"A",
|
||||
"A#",
|
||||
"B",
|
||||
"C",
|
||||
"C#",
|
||||
"D",
|
||||
"D#",
|
||||
"E",
|
||||
"F",
|
||||
"F#",
|
||||
"G",
|
||||
"G#",
|
||||
];
|
||||
const note = notes[semis % 12];
|
||||
|
||||
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 = parseInt(e.target.value);
|
||||
if (!isNaN(newRoot)) setRoot(newRoot);
|
||||
};
|
||||
|
||||
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+"
|
||||
value={root}
|
||||
placeholder="440"
|
||||
onChange={setRootValue}
|
||||
/>
|
||||
{" Hz"}, then
|
||||
</section>
|
||||
<section>
|
||||
<input
|
||||
type="text"
|
||||
pattern=""
|
||||
value={note}
|
||||
placeholder="C5"
|
||||
onChange={setNoteValue}
|
||||
/>
|
||||
{" is "}
|
||||
<input
|
||||
type="text"
|
||||
pattern="\d+"
|
||||
value={frequency}
|
||||
placeholder="523.251"
|
||||
onChange={setFrequencyValue}
|
||||
/>
|
||||
{" Hz"}
|
||||
</section>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
50
styles/globals.css
Normal file
50
styles/globals.css
Normal file
|
@ -0,0 +1,50 @@
|
|||
:root {
|
||||
font-family: sans-serif;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #19151b;
|
||||
color: #ffe3fb;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 960px;
|
||||
padding: 1em;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #b57fdc;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid #b57fdc;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
padding: 0.25em;
|
||||
margin: 0.125em;
|
||||
font-size: 1.125rem;
|
||||
|
||||
outline: none;
|
||||
border: none;
|
||||
|
||||
border-radius: 0.25em;
|
||||
|
||||
background-color: #ffffff;
|
||||
border: 2px solid #ffffff;
|
||||
color: black;
|
||||
}
|
||||
|
||||
input[type="text"]:focus {
|
||||
border-color: rgb(141, 255, 208);
|
||||
}
|
23
tsconfig.json
Normal file
23
tsconfig.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Loading…
Reference in a new issue