diff options
author | Brian Cully <bjc@spork.org> | 2025-03-10 12:26:55 -0400 |
---|---|---|
committer | Brian Cully <bjc@spork.org> | 2025-03-10 12:26:55 -0400 |
commit | 33e376806164072f94cdf9a682262db19ff590d3 (patch) | |
tree | 1bc6da506fd4188557cba4de27139a87fe82b8a4 | |
parent | efae088a0f78eaa0e483c7709bb331809db9cbff (diff) | |
download | chords-33e376806164072f94cdf9a682262db19ff590d3.tar.gz chords-33e376806164072f94cdf9a682262db19ff590d3.zip |
js: add ‘Note’ class
-rw-r--r-- | scale.mjs | 86 |
1 files changed, 86 insertions, 0 deletions
@@ -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); |