From 33e376806164072f94cdf9a682262db19ff590d3 Mon Sep 17 00:00:00 2001 From: Brian Cully Date: Mon, 10 Mar 2025 12:26:55 -0400 Subject: =?UTF-8?q?js:=20add=20=E2=80=98Note=E2=80=99=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scale.mjs | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/scale.mjs b/scale.mjs index de25d14..9bec03b 100644 --- a/scale.mjs +++ b/scale.mjs @@ -8,6 +8,92 @@ function notesBetween(a, b) { } } +export class Note { + static natural = '♮'; + static sharp = '♯'; + static flat = '♭'; + + static range = ['A', 'B', 'C', 'D', 'E', 'F', 'G']; + static get first() { + return this.range.at(0); + } + static get last() { + return this.range.at(-1); + } + + static noSharps = ['B', 'E']; + static get noFlats() { + return this.noSharps.map(n => String.fromCharCode(n.charCodeAt()+1)) + } + + static fromString(string) { + const [root, accidental] = [string[0], string[1]]; + switch (accidental) { + case this.sharp: + if (this.noSharps.includes(root)) { + return new this(String.fromCharCode(root.charCodeAt()+1)) + } else { + return new this(root, true); + } + case this.flat: + if (this.noFlats.includes(root)) { + return new this(String.fromCharCode(root.charCodeAt()-1)); + } else if (root === Note.first) { + // i don't want to make a ring buffer that accepts + // negative indices, ok??? + return new this(Note.last, true); + } else { + const code = root.charCodeAt(); + return new this(String.fromCharCode(code-1), true); + } + case undefined: + case this.natural: + return new this(root); + default: + throw new Error('wtf is that accidental', accidental); + } + } + + constructor(root, isSharp=false) { + if (isSharp && Note.noSharps.includes(root)) { + throw new Error(`bad note: ${root}${Note.sharp}`); + } + this.root = root; + this.isSharp = isSharp; + } + + toString() { + return this.root + (this.isSharp ? Note.sharp : ''); + } + + toAlternateString() { + if (this.isSharp) { + const next = (this.root === Note.last) ? Note.first : String.fromCharCode(this.root.charCodeAt()+1); + if (Note.noSharps.includes(this.root)) { + return next; + } else { + return next + Note.flat; + } + } else if (Note.noFlats.includes(this.root)) { + const prev = (this.root === Note.first) ? Note.last : String.fromCharCode(this.root.charCodeAt()-1); + return prev + Note.sharp; + } else { + return this.toString(); + } + } + + toRelativeString(other) { + const alternate = this.toAlternateString(); + const otherStr = other.toString(); + const delta = otherStr.charCodeAt() - alternate.charCodeAt(); + if (delta === -1 || delta === 1) { + return alternate; + } else { + return this.toString(); + } + } +} + const ringHandler = { get(target, prop) { return target[prop % target.length] || Reflect.get(...arguments); -- cgit v1.2.3