@musodojo/music-theory-data 20.3.0 → 20.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/_dnt.test_shims.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/almost_equals.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/array_includes.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/assert.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/assertion_error.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/equal.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/equals.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/exists.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/fail.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/false.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/greater.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/greater_or_equal.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/instance_of.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/is_error.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/less.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/less_or_equal.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/match.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/not_equals.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/not_instance_of.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/not_match.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/not_strict_equals.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/object_match.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/rejects.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/strict_equals.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/string_includes.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/throws.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/unimplemented.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/assert/1.0.18/unreachable.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/internal/1.0.12/build_message.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/internal/1.0.12/diff.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/internal/1.0.12/diff_str.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/internal/1.0.12/format.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/internal/1.0.12/styles.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/internal/1.0.12/types.d.ts.map +1 -0
- package/esm/package.json +3 -0
- package/esm/src/data/chords/mod.d.ts +10 -0
- package/esm/src/data/chords/mod.d.ts.map +1 -0
- package/esm/src/data/chords/mod.js +72 -0
- package/esm/src/data/colors/mod.d.ts +51 -0
- package/esm/src/data/colors/mod.d.ts.map +1 -0
- package/esm/src/data/colors/mod.js +79 -0
- package/esm/src/data/labels/mod.d.ts +3 -0
- package/esm/src/data/labels/mod.d.ts.map +1 -0
- package/esm/src/data/labels/mod.js +2 -0
- package/esm/src/data/labels/note-label-collections.d.ts +62 -0
- package/esm/src/data/labels/note-label-collections.d.ts.map +1 -0
- package/esm/src/data/labels/note-label-collections.js +91 -0
- package/esm/src/data/labels/note-labels.d.ts +314 -0
- package/esm/src/data/labels/note-labels.d.ts.map +1 -0
- package/esm/src/data/labels/note-labels.js +502 -0
- package/esm/src/data/mod.d.ts +5 -0
- package/esm/src/data/mod.d.ts.map +1 -0
- package/esm/src/data/mod.js +4 -0
- package/esm/src/data/note-collections/augmented-variants.d.ts +8 -0
- package/esm/src/data/note-collections/augmented-variants.d.ts.map +1 -0
- package/esm/src/data/note-collections/augmented-variants.js +27 -0
- package/esm/src/data/note-collections/diatonic-modes.d.ts +14 -0
- package/esm/src/data/note-collections/diatonic-modes.d.ts.map +1 -0
- package/esm/src/data/note-collections/diatonic-modes.js +268 -0
- package/esm/src/data/note-collections/diminished-variants.d.ts +11 -0
- package/esm/src/data/note-collections/diminished-variants.d.ts.map +1 -0
- package/esm/src/data/note-collections/diminished-variants.js +72 -0
- package/esm/src/data/note-collections/dominant-variants.d.ts +11 -0
- package/esm/src/data/note-collections/dominant-variants.d.ts.map +1 -0
- package/esm/src/data/note-collections/dominant-variants.js +116 -0
- package/esm/src/data/note-collections/harmonic-minor-modes.d.ts +13 -0
- package/esm/src/data/note-collections/harmonic-minor-modes.d.ts.map +1 -0
- package/esm/src/data/note-collections/harmonic-minor-modes.js +246 -0
- package/esm/src/data/note-collections/major-variants.d.ts +12 -0
- package/esm/src/data/note-collections/major-variants.d.ts.map +1 -0
- package/esm/src/data/note-collections/major-variants.js +123 -0
- package/esm/src/data/note-collections/melodic-minor-modes.d.ts +13 -0
- package/esm/src/data/note-collections/melodic-minor-modes.d.ts.map +1 -0
- package/esm/src/data/note-collections/melodic-minor-modes.js +206 -0
- package/esm/src/data/note-collections/minor-variants.d.ts +13 -0
- package/esm/src/data/note-collections/minor-variants.d.ts.map +1 -0
- package/esm/src/data/note-collections/minor-variants.js +118 -0
- package/esm/src/data/note-collections/mod.d.ts +86 -0
- package/esm/src/data/note-collections/mod.d.ts.map +1 -0
- package/esm/src/data/note-collections/mod.js +86 -0
- package/esm/src/data/note-collections/other-collections.d.ts +10 -0
- package/esm/src/data/note-collections/other-collections.d.ts.map +1 -0
- package/esm/src/data/note-collections/other-collections.js +91 -0
- package/esm/src/data/note-collections/pentatonic-variants.d.ts +11 -0
- package/esm/src/data/note-collections/pentatonic-variants.d.ts.map +1 -0
- package/esm/src/data/note-collections/pentatonic-variants.js +86 -0
- package/esm/src/mod.d.ts +74 -0
- package/esm/src/mod.d.ts.map +1 -0
- package/{src/mod.ts → esm/src/mod.js} +2 -3
- package/esm/src/utils/chords.d.ts +14 -0
- package/esm/src/utils/chords.d.ts.map +1 -0
- package/esm/src/utils/chords.js +93 -0
- package/esm/src/utils/colors.d.ts +14 -0
- package/esm/src/utils/colors.d.ts.map +1 -0
- package/esm/src/utils/colors.js +47 -0
- package/esm/src/utils/intervals.d.ts +26 -0
- package/esm/src/utils/intervals.d.ts.map +1 -0
- package/esm/src/utils/intervals.js +61 -0
- package/esm/src/utils/midi-note-sequences.d.ts +23 -0
- package/esm/src/utils/midi-note-sequences.d.ts.map +1 -0
- package/esm/src/utils/midi-note-sequences.js +109 -0
- package/esm/src/utils/midi.d.ts +7 -0
- package/esm/src/utils/midi.d.ts.map +1 -0
- package/esm/src/utils/midi.js +26 -0
- package/esm/src/utils/mod.d.ts +9 -0
- package/esm/src/utils/mod.d.ts.map +1 -0
- package/esm/src/utils/mod.js +8 -0
- package/esm/src/utils/note-collections.d.ts +24 -0
- package/esm/src/utils/note-collections.d.ts.map +1 -0
- package/esm/src/utils/note-collections.js +132 -0
- package/esm/src/utils/note-names.d.ts +23 -0
- package/esm/src/utils/note-names.d.ts.map +1 -0
- package/esm/src/utils/note-names.js +127 -0
- package/esm/src/utils/rotate-array.d.ts +19 -0
- package/esm/src/utils/rotate-array.d.ts.map +1 -0
- package/{src/utils/rotate-array.ts → esm/src/utils/rotate-array.js} +10 -12
- package/esm/tests/get-chord-details.test.d.ts.map +1 -0
- package/esm/tests/get-note-names.test.d.ts.map +1 -0
- package/esm/tests/midi.test.d.ts.map +1 -0
- package/esm/tests/note-name-strings.test.d.ts.map +1 -0
- package/esm/tests/search-note-collections.test.d.ts.map +1 -0
- package/esm/tests/transform-intervals.test.d.ts.map +1 -0
- package/package.json +21 -6
- package/script/_dnt.test_shims.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/almost_equals.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/array_includes.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/assert.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/assertion_error.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/equal.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/equals.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/exists.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/fail.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/false.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/greater.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/greater_or_equal.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/instance_of.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/is_error.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/less.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/less_or_equal.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/match.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/mod.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/not_equals.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/not_instance_of.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/not_match.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/not_strict_equals.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/object_match.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/rejects.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/strict_equals.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/string_includes.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/throws.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/unimplemented.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/assert/1.0.18/unreachable.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/internal/1.0.12/build_message.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/internal/1.0.12/diff.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/internal/1.0.12/diff_str.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/internal/1.0.12/format.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/internal/1.0.12/styles.d.ts.map +1 -0
- package/script/deps/jsr.io/@std/internal/1.0.12/types.d.ts.map +1 -0
- package/script/package.json +3 -0
- package/script/src/data/chords/mod.d.ts +10 -0
- package/script/src/data/chords/mod.d.ts.map +1 -0
- package/script/src/data/chords/mod.js +75 -0
- package/script/src/data/colors/mod.d.ts +51 -0
- package/script/src/data/colors/mod.d.ts.map +1 -0
- package/script/src/data/colors/mod.js +82 -0
- package/script/src/data/labels/mod.d.ts +3 -0
- package/script/src/data/labels/mod.d.ts.map +1 -0
- package/script/src/data/labels/mod.js +18 -0
- package/script/src/data/labels/note-label-collections.d.ts +62 -0
- package/script/src/data/labels/note-label-collections.d.ts.map +1 -0
- package/script/src/data/labels/note-label-collections.js +94 -0
- package/script/src/data/labels/note-labels.d.ts +314 -0
- package/script/src/data/labels/note-labels.d.ts.map +1 -0
- package/script/src/data/labels/note-labels.js +505 -0
- package/script/src/data/mod.d.ts +5 -0
- package/script/src/data/mod.d.ts.map +1 -0
- package/script/src/data/mod.js +20 -0
- package/script/src/data/note-collections/augmented-variants.d.ts +8 -0
- package/script/src/data/note-collections/augmented-variants.d.ts.map +1 -0
- package/script/src/data/note-collections/augmented-variants.js +30 -0
- package/script/src/data/note-collections/diatonic-modes.d.ts +14 -0
- package/script/src/data/note-collections/diatonic-modes.d.ts.map +1 -0
- package/script/src/data/note-collections/diatonic-modes.js +271 -0
- package/script/src/data/note-collections/diminished-variants.d.ts +11 -0
- package/script/src/data/note-collections/diminished-variants.d.ts.map +1 -0
- package/script/src/data/note-collections/diminished-variants.js +75 -0
- package/script/src/data/note-collections/dominant-variants.d.ts +11 -0
- package/script/src/data/note-collections/dominant-variants.d.ts.map +1 -0
- package/script/src/data/note-collections/dominant-variants.js +119 -0
- package/script/src/data/note-collections/harmonic-minor-modes.d.ts +13 -0
- package/script/src/data/note-collections/harmonic-minor-modes.d.ts.map +1 -0
- package/script/src/data/note-collections/harmonic-minor-modes.js +249 -0
- package/script/src/data/note-collections/major-variants.d.ts +12 -0
- package/script/src/data/note-collections/major-variants.d.ts.map +1 -0
- package/script/src/data/note-collections/major-variants.js +126 -0
- package/script/src/data/note-collections/melodic-minor-modes.d.ts +13 -0
- package/script/src/data/note-collections/melodic-minor-modes.d.ts.map +1 -0
- package/script/src/data/note-collections/melodic-minor-modes.js +209 -0
- package/script/src/data/note-collections/minor-variants.d.ts +13 -0
- package/script/src/data/note-collections/minor-variants.d.ts.map +1 -0
- package/script/src/data/note-collections/minor-variants.js +121 -0
- package/script/src/data/note-collections/mod.d.ts +86 -0
- package/script/src/data/note-collections/mod.d.ts.map +1 -0
- package/script/src/data/note-collections/mod.js +99 -0
- package/script/src/data/note-collections/other-collections.d.ts +10 -0
- package/script/src/data/note-collections/other-collections.d.ts.map +1 -0
- package/script/src/data/note-collections/other-collections.js +94 -0
- package/script/src/data/note-collections/pentatonic-variants.d.ts +11 -0
- package/script/src/data/note-collections/pentatonic-variants.d.ts.map +1 -0
- package/script/src/data/note-collections/pentatonic-variants.js +89 -0
- package/script/src/mod.d.ts +74 -0
- package/script/src/mod.d.ts.map +1 -0
- package/script/src/mod.js +88 -0
- package/script/src/utils/chords.d.ts +14 -0
- package/script/src/utils/chords.d.ts.map +1 -0
- package/script/src/utils/chords.js +102 -0
- package/script/src/utils/colors.d.ts +14 -0
- package/script/src/utils/colors.d.ts.map +1 -0
- package/script/src/utils/colors.js +51 -0
- package/script/src/utils/intervals.d.ts +26 -0
- package/script/src/utils/intervals.d.ts.map +1 -0
- package/script/src/utils/intervals.js +69 -0
- package/script/src/utils/midi-note-sequences.d.ts +23 -0
- package/script/src/utils/midi-note-sequences.d.ts.map +1 -0
- package/script/src/utils/midi-note-sequences.js +112 -0
- package/script/src/utils/midi.d.ts +7 -0
- package/script/src/utils/midi.d.ts.map +1 -0
- package/script/src/utils/midi.js +32 -0
- package/script/src/utils/mod.d.ts +9 -0
- package/script/src/utils/mod.d.ts.map +1 -0
- package/script/src/utils/mod.js +24 -0
- package/script/src/utils/note-collections.d.ts +24 -0
- package/script/src/utils/note-collections.d.ts.map +1 -0
- package/script/src/utils/note-collections.js +137 -0
- package/script/src/utils/note-names.d.ts +23 -0
- package/script/src/utils/note-names.d.ts.map +1 -0
- package/script/src/utils/note-names.js +134 -0
- package/script/src/utils/rotate-array.d.ts +19 -0
- package/script/src/utils/rotate-array.d.ts.map +1 -0
- package/script/src/utils/rotate-array.js +32 -0
- package/script/tests/get-chord-details.test.d.ts.map +1 -0
- package/script/tests/get-note-names.test.d.ts.map +1 -0
- package/script/tests/midi.test.d.ts.map +1 -0
- package/script/tests/note-name-strings.test.d.ts.map +1 -0
- package/script/tests/search-note-collections.test.d.ts.map +1 -0
- package/script/tests/transform-intervals.test.d.ts.map +1 -0
- package/src/data/chords/mod.ts +0 -86
- package/src/data/colors/mod.ts +0 -109
- package/src/data/labels/mod.ts +0 -2
- package/src/data/labels/note-label-collections.ts +0 -124
- package/src/data/labels/note-labels.ts +0 -702
- package/src/data/mod.ts +0 -4
- package/src/data/note-collections/augmented-variants.ts +0 -35
- package/src/data/note-collections/diatonic-modes.ts +0 -281
- package/src/data/note-collections/diminished-variants.ts +0 -87
- package/src/data/note-collections/dominant-variants.ts +0 -131
- package/src/data/note-collections/harmonic-minor-modes.ts +0 -261
- package/src/data/note-collections/major-variants.ts +0 -135
- package/src/data/note-collections/melodic-minor-modes.ts +0 -221
- package/src/data/note-collections/minor-variants.ts +0 -131
- package/src/data/note-collections/mod.ts +0 -108
- package/src/data/note-collections/other-collections.ts +0 -107
- package/src/data/note-collections/pentatonic-variants.ts +0 -99
- package/src/types/chords.d.ts +0 -48
- package/src/types/midi.d.ts +0 -131
- package/src/types/mod.d.ts +0 -3
- package/src/types/note-collections.d.ts +0 -86
- package/src/utils/chords.ts +0 -168
- package/src/utils/colors.ts +0 -57
- package/src/utils/intervals.ts +0 -111
- package/src/utils/midi-note-sequences.ts +0 -171
- package/src/utils/midi.ts +0 -51
- package/src/utils/mod.ts +0 -8
- package/src/utils/note-collections.ts +0 -188
- package/src/utils/note-names.ts +0 -182
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type NoteCollectionKey,
|
|
3
|
-
noteCollections,
|
|
4
|
-
} from "../data/note-collections/mod.ts";
|
|
5
|
-
import type { Interval } from "../data/labels/note-labels.ts";
|
|
6
|
-
import type { NoteCollection } from "../types/note-collections.d.ts";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Checks if a given string is a valid `NoteCollectionKey`.
|
|
10
|
-
* @param key The string to check.
|
|
11
|
-
* @returns `true` if the key is a valid `NoteCollectionKey`, `false` otherwise.
|
|
12
|
-
*/
|
|
13
|
-
export function isValidNoteCollectionKey(
|
|
14
|
-
key: string,
|
|
15
|
-
): key is NoteCollectionKey {
|
|
16
|
-
return Object.prototype.hasOwnProperty.call(noteCollections, key);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const normalizationMap = new Map<string, string>();
|
|
20
|
-
|
|
21
|
-
const aliasSets: Record<string, string[]> = {
|
|
22
|
-
"♭": ["b", "flat"],
|
|
23
|
-
"♯": ["#", "sharp"],
|
|
24
|
-
"♮": ["n", "natural"],
|
|
25
|
-
"𝄫": ["bb", "doubleflat"],
|
|
26
|
-
"𝄪": ["##", "doublesharp"],
|
|
27
|
-
|
|
28
|
-
"M": ["maj", "major"],
|
|
29
|
-
"m": ["min", "minor"],
|
|
30
|
-
"°": ["dim", "diminished"],
|
|
31
|
-
"+": ["aug", "augmented"],
|
|
32
|
-
"ø": ["halfdiminished"],
|
|
33
|
-
|
|
34
|
-
"7": ["seventh"],
|
|
35
|
-
"dominant": ["dom"],
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
for (const [canonical, aliases] of Object.entries(aliasSets)) {
|
|
39
|
-
for (const alias of aliases) {
|
|
40
|
-
normalizationMap.set(alias, canonical);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Normalizes a search string by converting aliases to their canonical form,
|
|
46
|
-
* removing non-essential characters, and collapsing whitespace.
|
|
47
|
-
* @param str The string to normalize.
|
|
48
|
-
* @returns A normalized string for searching.
|
|
49
|
-
*/
|
|
50
|
-
function normalizeSearchTerm(str: string): string {
|
|
51
|
-
// Start with trimming, but preserve original case for now.
|
|
52
|
-
let normalized = str.trim();
|
|
53
|
-
|
|
54
|
-
// Iteratively replace all aliases, using a case-insensitive regex.
|
|
55
|
-
// This is crucial because we can't simply lowercase the entire string,
|
|
56
|
-
// as that would merge "M" (major) and "m" (minor) into the same character.
|
|
57
|
-
for (const [alias, canonical] of normalizationMap.entries()) {
|
|
58
|
-
// This regex looks for an alias that is either a whole word (\b)
|
|
59
|
-
// or is immediately followed by a digit (for cases like "b2", "maj7").
|
|
60
|
-
// The "i" flag makes the search case-insensitive.
|
|
61
|
-
const regex = new RegExp(`\\b${alias}(?=[0-9]|\\b)`, "gi");
|
|
62
|
-
normalized = normalized.replace(regex, canonical);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Final cleanup: remove non-essential characters and collapse whitespace.
|
|
66
|
-
return normalized.replace(/[-()]/g, "").replace(/\s+/g, " ").trim();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface SearchOptions {
|
|
70
|
-
query?: string;
|
|
71
|
-
intervals?: Interval[];
|
|
72
|
-
type?: string;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function searchNoteCollections(
|
|
76
|
-
options: SearchOptions,
|
|
77
|
-
): NoteCollection[] {
|
|
78
|
-
const { query, intervals, type } = options;
|
|
79
|
-
let candidates = Object.values(noteCollections);
|
|
80
|
-
|
|
81
|
-
// 1. Apply hard filters first to narrow down the candidate pool
|
|
82
|
-
if (type) {
|
|
83
|
-
const normalizedType = normalizeSearchTerm(type);
|
|
84
|
-
const searchWords = normalizedType.split(" ");
|
|
85
|
-
|
|
86
|
-
candidates = candidates.filter((theme) => {
|
|
87
|
-
const themeTypesString = theme.type.map(normalizeSearchTerm).join(" ");
|
|
88
|
-
return searchWords.every((word) => {
|
|
89
|
-
// Use case-sensitive search for 'M' and 'm'
|
|
90
|
-
const isCaseSensitiveWord = word === "M" || word === "m";
|
|
91
|
-
const regex = new RegExp(
|
|
92
|
-
`\\b${word}\\b`,
|
|
93
|
-
isCaseSensitiveWord ? "" : "i",
|
|
94
|
-
);
|
|
95
|
-
return regex.test(themeTypesString);
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (intervals && intervals.length > 0) {
|
|
101
|
-
candidates = candidates.filter((theme) =>
|
|
102
|
-
intervals.every((interval) => theme.intervals.includes(interval))
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// If there's no text query, the filtered list is the final result.
|
|
107
|
-
if (!query) {
|
|
108
|
-
return candidates;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// 2. Apply prioritized text search on the filtered candidates
|
|
112
|
-
const normalizedQuery = normalizeSearchTerm(query);
|
|
113
|
-
|
|
114
|
-
if (!normalizedQuery) {
|
|
115
|
-
return candidates;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const searchWords = normalizedQuery.split(" ");
|
|
119
|
-
|
|
120
|
-
// Filter candidates to those that contain all search words
|
|
121
|
-
const textFilteredCandidates = candidates.filter((theme) => {
|
|
122
|
-
const searchableText = [
|
|
123
|
-
theme.primaryName,
|
|
124
|
-
...theme.names,
|
|
125
|
-
...theme.type,
|
|
126
|
-
...theme.characteristics,
|
|
127
|
-
]
|
|
128
|
-
.map(normalizeSearchTerm)
|
|
129
|
-
.join(" ");
|
|
130
|
-
|
|
131
|
-
return searchWords.every((word) => {
|
|
132
|
-
const isCaseSensitiveWord = word === "M" || word === "m";
|
|
133
|
-
const regex = new RegExp(
|
|
134
|
-
`\\b${word}\\b`,
|
|
135
|
-
isCaseSensitiveWord ? "" : "i",
|
|
136
|
-
);
|
|
137
|
-
return regex.test(searchableText);
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
// 3. Prioritize the filtered results
|
|
142
|
-
const prioritizedResults = new Set<NoteCollection>();
|
|
143
|
-
|
|
144
|
-
const passes = [
|
|
145
|
-
// Pass 1: Exact match on primaryName
|
|
146
|
-
(theme: NoteCollection) =>
|
|
147
|
-
normalizeSearchTerm(theme.primaryName) === normalizedQuery,
|
|
148
|
-
// Pass 2: Exact match on any name
|
|
149
|
-
(theme: NoteCollection) =>
|
|
150
|
-
theme.names.some((name) => normalizeSearchTerm(name) === normalizedQuery),
|
|
151
|
-
// Pass 3: Primary name starts with the query
|
|
152
|
-
(theme: NoteCollection) =>
|
|
153
|
-
normalizeSearchTerm(theme.primaryName).startsWith(normalizedQuery),
|
|
154
|
-
// Pass 4: Any name starts with the query
|
|
155
|
-
(theme: NoteCollection) =>
|
|
156
|
-
theme.names.some((name) =>
|
|
157
|
-
normalizeSearchTerm(name).startsWith(normalizedQuery)
|
|
158
|
-
),
|
|
159
|
-
];
|
|
160
|
-
|
|
161
|
-
for (const pass of passes) {
|
|
162
|
-
for (const theme of textFilteredCandidates) {
|
|
163
|
-
if (pass(theme)) {
|
|
164
|
-
prioritizedResults.add(theme);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Add the remaining text-filtered candidates that didn't match a priority pass
|
|
170
|
-
for (const theme of textFilteredCandidates) {
|
|
171
|
-
prioritizedResults.add(theme);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return Array.from(prioritizedResults);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Finds the single best matching `NoteCollection` based on search options.
|
|
179
|
-
* This is a convenience wrapper around `searchNoteCollections` that returns only the first result.
|
|
180
|
-
* The search is prioritized to return the most relevant match first.
|
|
181
|
-
* @param options The search options.
|
|
182
|
-
* @returns The best matching `NoteCollection` or `undefined` if no match is found.
|
|
183
|
-
*/
|
|
184
|
-
export function findNoteCollection(
|
|
185
|
-
options: SearchOptions,
|
|
186
|
-
): NoteCollection | undefined {
|
|
187
|
-
return searchNoteCollections(options)[0];
|
|
188
|
-
}
|
package/src/utils/note-names.ts
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
enharmonicNoteNameGroups,
|
|
3
|
-
type Interval,
|
|
4
|
-
intervalToIntegerMap,
|
|
5
|
-
type NoteLetter,
|
|
6
|
-
noteLetters,
|
|
7
|
-
type NoteName,
|
|
8
|
-
noteNamesSet,
|
|
9
|
-
noteNameToIntegerMap,
|
|
10
|
-
type RootNote,
|
|
11
|
-
type RootNoteInteger,
|
|
12
|
-
rootNotesSet,
|
|
13
|
-
} from "../data/labels/note-labels.ts";
|
|
14
|
-
import {
|
|
15
|
-
type NoteCollectionKey,
|
|
16
|
-
noteCollections,
|
|
17
|
-
} from "../data/note-collections/mod.ts";
|
|
18
|
-
import {
|
|
19
|
-
transformIntervals,
|
|
20
|
-
type TransformIntervalsOptions,
|
|
21
|
-
} from "./intervals.ts";
|
|
22
|
-
import { isValidNoteCollectionKey } from "./note-collections.ts";
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Parses a string and returns a canonical `NoteName` if the string is a valid note name.
|
|
26
|
-
* This is useful for handling user input that may use ASCII characters like 'b' and '#',
|
|
27
|
-
* as well as double accidentals like 'x', '##', and 'bb'.
|
|
28
|
-
* @param name The string to parse.
|
|
29
|
-
* @returns A canonical `NoteName` (e.g., "B♭", "C𝄪") or `undefined` if the input is not a valid note.
|
|
30
|
-
*/
|
|
31
|
-
export function normalizeNoteNameString(name: string): NoteName | undefined {
|
|
32
|
-
if (typeof name !== "string" || name.length === 0) {
|
|
33
|
-
return undefined;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Check if the name is already a canonical note name (e.g., "C", "B♭")
|
|
37
|
-
// This check is case-sensitive.
|
|
38
|
-
if (noteNamesSet.has(name as NoteName)) {
|
|
39
|
-
return name as NoteName;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const noteLetterRegex = /^[A-Ga-g]/;
|
|
43
|
-
const accidentalRegex = /([#♯xX𝄪]+)|([b♭𝄫]+)/gu;
|
|
44
|
-
|
|
45
|
-
const noteLetterMatch = name.match(noteLetterRegex);
|
|
46
|
-
if (!noteLetterMatch) {
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const noteLetter = noteLetterMatch[0].toUpperCase() as NoteLetter;
|
|
51
|
-
const accidentalString = name.substring(noteLetterMatch[0].length);
|
|
52
|
-
let validAccidentalLength = 0;
|
|
53
|
-
let noteAlterInteger = 0;
|
|
54
|
-
|
|
55
|
-
for (const match of accidentalString.matchAll(accidentalRegex)) {
|
|
56
|
-
const sharps = match[1];
|
|
57
|
-
if (sharps) {
|
|
58
|
-
for (const char of sharps) {
|
|
59
|
-
noteAlterInteger += (char.toLowerCase() === "x" || char === "𝄪")
|
|
60
|
-
? 2
|
|
61
|
-
: 1;
|
|
62
|
-
}
|
|
63
|
-
validAccidentalLength += sharps.length;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const flats = match[2];
|
|
67
|
-
if (flats) {
|
|
68
|
-
for (const char of flats) {
|
|
69
|
-
noteAlterInteger -= (char === "𝄫") ? 2 : 1;
|
|
70
|
-
}
|
|
71
|
-
validAccidentalLength += flats.length;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Check if the entire accidental string had valid accidentals
|
|
76
|
-
if (accidentalString.length > validAccidentalLength) {
|
|
77
|
-
return undefined;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
let accidentalSymbols = "";
|
|
81
|
-
let currentAlteration = noteAlterInteger;
|
|
82
|
-
|
|
83
|
-
while (currentAlteration > 0) {
|
|
84
|
-
if (currentAlteration >= 2) {
|
|
85
|
-
accidentalSymbols += "𝄪";
|
|
86
|
-
currentAlteration -= 2;
|
|
87
|
-
} else {
|
|
88
|
-
accidentalSymbols += "♯";
|
|
89
|
-
currentAlteration -= 1;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
while (currentAlteration < 0) {
|
|
94
|
-
if (currentAlteration <= -2) {
|
|
95
|
-
accidentalSymbols += "𝄫";
|
|
96
|
-
currentAlteration += 2;
|
|
97
|
-
} else {
|
|
98
|
-
accidentalSymbols += "♭";
|
|
99
|
-
currentAlteration += 1;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const result = (noteLetter + accidentalSymbols) as NoteName;
|
|
104
|
-
|
|
105
|
-
return noteNamesSet.has(result) ? result : undefined;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Parses a string and returns a canonical `RootNote` if the string is a valid root note.
|
|
110
|
-
* This is useful for handling user input that may use ASCII characters for accidentals.
|
|
111
|
-
* It is stricter than `normalizeNoteNameString` as it only allows notes present in the `rootNotes` array.
|
|
112
|
-
* @param name The string to parse.
|
|
113
|
-
* @returns A canonical `RootNote` or `undefined` if the input is not a valid root note.
|
|
114
|
-
*/
|
|
115
|
-
export function normalizeRootNoteString(name: string): RootNote | undefined {
|
|
116
|
-
const normalizedNote = normalizeNoteNameString(name);
|
|
117
|
-
|
|
118
|
-
if (normalizedNote && rootNotesSet.has(normalizedNote as RootNote)) {
|
|
119
|
-
return normalizedNote as RootNote;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return undefined;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function getNoteIntegerFromString(
|
|
126
|
-
noteName: string,
|
|
127
|
-
): RootNoteInteger | undefined {
|
|
128
|
-
const normalized = normalizeNoteNameString(noteName);
|
|
129
|
-
if (!normalized) return undefined;
|
|
130
|
-
return noteNameToIntegerMap.get(normalized);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export function getNoteNamesFromRootAndIntervals(
|
|
134
|
-
rootNote: RootNote,
|
|
135
|
-
intervals: readonly Interval[],
|
|
136
|
-
options: TransformIntervalsOptions = {},
|
|
137
|
-
): NoteName[] {
|
|
138
|
-
const rootNoteInteger = noteNameToIntegerMap.get(rootNote);
|
|
139
|
-
if (rootNoteInteger === undefined) return [];
|
|
140
|
-
const rootNoteLetter = rootNote.charAt(0).toUpperCase();
|
|
141
|
-
const rootNoteLetterIndex = noteLetters.indexOf(rootNoteLetter as NoteLetter);
|
|
142
|
-
|
|
143
|
-
const intervalsToConvert = Object.keys(options).length > 0
|
|
144
|
-
? transformIntervals(intervals, options)
|
|
145
|
-
: intervals;
|
|
146
|
-
|
|
147
|
-
const noteNames: NoteName[] = intervalsToConvert.flatMap((interval) => {
|
|
148
|
-
const intervalInteger = intervalToIntegerMap.get(interval);
|
|
149
|
-
if (intervalInteger === undefined) return []; // skip over invalid intervals silently
|
|
150
|
-
|
|
151
|
-
const absoluteNoteInteger = (rootNoteInteger + intervalInteger) % 12;
|
|
152
|
-
|
|
153
|
-
const intervalNumberMatch = interval.match(/\d+/)!;
|
|
154
|
-
const intervalNumber = parseInt(intervalNumberMatch[0], 10);
|
|
155
|
-
|
|
156
|
-
const targetNoteLetter =
|
|
157
|
-
noteLetters[(rootNoteLetterIndex + intervalNumber - 1) % 7];
|
|
158
|
-
|
|
159
|
-
const enharmonicGroup = enharmonicNoteNameGroups[absoluteNoteInteger];
|
|
160
|
-
const selectedNote = enharmonicGroup.find((noteName) =>
|
|
161
|
-
noteName.startsWith(targetNoteLetter)
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
return [selectedNote ?? enharmonicGroup[0]];
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
return noteNames;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export function getNoteNamesFromRootAndCollectionKey(
|
|
171
|
-
rootNote: RootNote,
|
|
172
|
-
noteCollectionKey: NoteCollectionKey,
|
|
173
|
-
options: TransformIntervalsOptions = {},
|
|
174
|
-
): NoteName[] {
|
|
175
|
-
if (!isValidNoteCollectionKey(noteCollectionKey)) return [];
|
|
176
|
-
|
|
177
|
-
return getNoteNamesFromRootAndIntervals(
|
|
178
|
-
rootNote,
|
|
179
|
-
noteCollections[noteCollectionKey].intervals,
|
|
180
|
-
options,
|
|
181
|
-
);
|
|
182
|
-
}
|